Hi everyone! I'm working on a fake motion blur solution for the common "wagon wheel" problem in Unity 5. At the beginning I wanted to create a local radial motion blur as Image Effect but it's too hard for me, so I'm trying different approaches. As now, I just need an shader to perform a radial blur on a texture using a float value (angle) as input, but I'm quite noob on shaders. Here is an example of what I would like to reproduce: Does anyone have some suggestions? I'd really appreciate some help!
If you want to try using the 5.4 betas you can use this: https://github.com/keijiro/KinoMotion However doing this as an effect on the texture itself can be quite expensive. It requires you sample the texture multiple times in your shader. You can do some hacks like lowering the mip map to reduce the number of texture samples, but it will also blur details that wouldn't blur normally. The really cheap method of doing this is have two textures, one blurred and one not, and fade between them. This is what a lot of racing games of the PS2/Xbox through PS3/Xbox 360 eras did, and even what the original Halo games did for the warthog.
or just some blur shader on that texture, but the fade one sounds better though.. http://unitycoder.com/blog/2012/10/06/radial-blur-shader/ *not exactly same effect
That blur shader works by sampling the texture 100 times ... which is very expensive. However the idea is the same for a spin blur, just rotating the texture each time instead of scaling. Something like 8 texture samples is okay, even 16 isn't totally crazy, but that's a total count so if you've got normal maps, and metallic / smoothness maps, and occlusion maps you can only realistically do 4 samples of each texture which doesn't produce a very pleasing blur (also a normal map blurred this way produces kind of bad results).
Hi! I have some news. After a lot of fails, and with your good suggestions I finally have something working!!!! This shader is based on the "FakeRadialBlur" shader made by @mgear, including the UV rotation function by @bgolus Spoiler: Shader Code (csharp): Shader "Custom/SpinBlur"{ Properties{ tDiffuse("Color (RGB) Alpha (A)", 2D) = "white" angle ("Angle", Int) = 1 } SubShader { Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" } LOD 200 Cull Off CGPROGRAM #pragma target 3.0 #pragma surface surf Lambert alpha sampler2D tDiffuse; int angle; struct Input { float2 uvtDiffuse; float4 screenPos; }; float2 rotateUV(float2 uv, float degrees) { const float Deg2Rad = (UNITY_PI * 2.0) / 360.0; float rotationRadians = degrees * Deg2Rad; float s = sin(rotationRadians); float c = cos(rotationRadians); float2x2 rotationMatrix = float2x2(c, -s, s, c); uv -= 0.5; uv = mul(rotationMatrix, uv); uv += 0.5; return uv; } void surf (Input IN, inout SurfaceOutput o){ const float Deg2Rad = (UNITY_PI * 2.0) / 360.0; const float Rad2Deg = 180.0 / UNITY_PI; float2 vUv = IN.uvtDiffuse; float2 coord = vUv; float illuminationDecay = 1.0; float4 FragColor = float4(0.0, 0.0, 0.0, 0.0); int samp = angle; if (samp <= 0) samp = 1; for(float i=0; i < samp; i++){ coord = rotateUV(coord, angle/samp); float4 texel = tex2D(tDiffuse, coord); texel *= illuminationDecay * 1 / samp; FragColor += texel; } float4 c = FragColor; o.Albedo = c.rgb; o.Alpha = c.a; } ENDCG } FallBack "Diffuse" } And that's what I get for different values of the angle. This is not cheap at all, because I use one sample for each degree of the angle. For example, in the last image (360°) I use 360 samples, which is so expensive. Maybe I should make it tweakable with some kind of downsampling. And here's some videos: Thanks, guys!!!
Hi!! I need your help again. I modified the shader so now it supports downsampling and some other optimizations. I'm trying to make an Image Effect with it, and I know how to do it (using the Blit function), but it seems that Blit doesn't work with surface shaders, producing black textures. So, how can I convert this surface shader in a fragment shader with ColorMask RGBA? Code (csharp): Shader "Custom/SpinBlur"{ Properties{ _Color("Main Color", Color) = (1,1,1,1) _Samples("Samples", Range(0,360)) = 100 _Angle("Angle", Range(0,360)) = 10 _MainTex("Color (RGB) Alpha (A)", 2D) = "white" } SubShader{ Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" } LOD 200 Cull Off CGPROGRAM #pragma target 3.0 #pragma surface surf Lambert alpha sampler2D _MainTex; int _Angle; int _Samples; float4 _Color; struct Input { float2 uv_MainTex; float4 screenPos; }; float2 rotateUV(float2 uv, float degrees) { const float Deg2Rad = (UNITY_PI * 2.0) / 360.0; float rotationRadians = degrees * Deg2Rad; float s = sin(rotationRadians); float c = cos(rotationRadians); float2x2 rotationMatrix = float2x2(c, -s, s, c); uv -= 0.5; uv = mul(rotationMatrix, uv); uv += 0.5; return uv; } void surf(Input IN, inout SurfaceOutput o) { const float Deg2Rad = (UNITY_PI * 2.0) / 360.0; const float Rad2Deg = 180.0 / UNITY_PI; float2 vUv = IN.uv_MainTex; float2 coord = vUv; float4 FragColor = float4(0.0, 0.0, 0.0, 0.0); int samp = _Samples; if (samp <= 0) samp = 1; for (float i = 0; i < samp; i++) { float a = (float)_Angle / (float)samp; coord = rotateUV(coord, a); float4 texel = tex2D(_MainTex, coord); texel *= 1.0 / samp; FragColor += texel; } float4 c = FragColor*_Color; o.Albedo = c.rgb; o.Alpha = c.a; } ENDCG } FallBack "Diffuse" } Thanks again!
For a blit shader you want as basic of a vertex / fragment shader as possible. Create a new unlit shader in Unity and you can mostly copy your surf function into that then fix the issues. Main things are you're directly outputting a fixed4 instead of o.Albedo and o.Alpha, and the "IN.uv_MainTex" is going to be "i.uv" or something like that.
So basically I've been working on a HDRP project and I had to convert the code to Shader Graph but instead of using nodes I put the code inside a Custom Function Node All you need is add this code with the following inputs and outputs Inputs: MainTex: Texture2D UV: Vector2 SS: Sampler State Angle: Vector1 Outputs: Out: Vector4 Code (CSharp): const float Deg2Rad = 0.017453293; const float Rad2Deg = 57.295777937; float2 coord = UV; float4 FragColor = float4(0.0, 0.0, 0.0, 0.0); uint samp = Angle; if (samp < 1) samp = 1; for (float i = 0; i < samp; i++) { float rotationRadians = i * Deg2Rad / samp; float s = sin(rotationRadians); float c = cos(rotationRadians); float2x2 rotationMatrix = float2x2(c, -s, s, c); coord -= 0.5; coord = mul(rotationMatrix, coord); coord += 0.5; float4 texel = SAMPLE_TEXTURE2D(MainTex, SS, coord); texel *= 1.0 / samp; FragColor += texel; } Out = FragColor;
Thank you for this contribution! Just a performance consideration for both the text and the graph version of this shader: I wouldn't use it for realtime rendering but only for baking a radially blurred pool/array/spritesheet of textures. Francesco
that's what I am exactly doing! Baking textures and saving them using a RenderTexture buffer along with Graphics.Blit to a Texture2D
just adding to original topic, new spin blur asset is coming: https://80.lv/articles/a-new-spin-blur-asset-for-unity/ and theres free version https://assetstore.unity.com/packages/tools/integration/simple-spin-blur-202273
Very interesting technique, basically it blurs the meshes by sampling them multiple times with lowered alpha, exactly the same way we blur textures. I'll definitely give it a try.