Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Radial Blur Shader - Texture

Discussion in 'Shaders' started by fra3point, May 26, 2016.

  1. fra3point

    fra3point

    Joined:
    Aug 20, 2012
    Posts:
    269
    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! :)
     
    Last edited: Nov 3, 2020
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,317
    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.
     
    hippocoder and fra3point like this.
  3. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    9,285
    fra3point likes this.
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,317
    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).
     
    mgear and fra3point like this.
  5. fra3point

    fra3point

    Joined:
    Aug 20, 2012
    Posts:
    269
    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

    Code (csharp):
    1. Shader "Custom/SpinBlur"{
    2.     Properties{
    3.         tDiffuse("Color (RGB) Alpha (A)", 2D) = "white"
    4.         angle ("Angle", Int) = 1
    5.     }
    6.     SubShader {
    7.         Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" }
    8.  
    9.         LOD 200
    10.         Cull Off
    11.             CGPROGRAM
    12.             #pragma target 3.0
    13.             #pragma surface surf Lambert alpha
    14.             sampler2D tDiffuse;
    15.             int angle;
    16.             struct Input {
    17.             float2 uvtDiffuse;
    18.             float4 screenPos;
    19.             };
    20.  
    21.             float2 rotateUV(float2 uv, float degrees) {
    22.                 const float Deg2Rad = (UNITY_PI * 2.0) / 360.0;
    23.                 float rotationRadians = degrees * Deg2Rad;
    24.                 float s = sin(rotationRadians);
    25.                 float c = cos(rotationRadians);
    26.                 float2x2 rotationMatrix = float2x2(c, -s, s, c);
    27.                 uv -= 0.5;
    28.                 uv = mul(rotationMatrix, uv);
    29.                 uv += 0.5;
    30.                 return uv;
    31.             }
    32.  
    33.             void surf (Input IN, inout SurfaceOutput o){
    34.                 const float Deg2Rad = (UNITY_PI * 2.0) / 360.0;
    35.                 const float Rad2Deg = 180.0 / UNITY_PI;
    36.                 float2 vUv = IN.uvtDiffuse;
    37.                 float2 coord = vUv;
    38.                 float illuminationDecay = 1.0;
    39.                 float4 FragColor = float4(0.0, 0.0, 0.0, 0.0);
    40.                 int samp = angle;
    41.                 if (samp <= 0) samp = 1;
    42.                 for(float i=0; i < samp; i++){      
    43.                     coord = rotateUV(coord, angle/samp);
    44.                     float4 texel = tex2D(tDiffuse, coord);
    45.                     texel *= illuminationDecay * 1 / samp;
    46.                     FragColor += texel;
    47.                 }
    48.                 float4 c = FragColor;
    49.                 o.Albedo = c.rgb;
    50.                 o.Alpha = c.a;
    51.             }
    52.             ENDCG
    53.         }
    54.     FallBack "Diffuse"
    55. }


    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.

    upload_2016-5-31_0-12-55.png

    And here's some videos:




    Thanks, guys!!! :) :) :)
     
    Last edited: May 31, 2016
    Bunny83 and dyupa like this.
  6. fra3point

    fra3point

    Joined:
    Aug 20, 2012
    Posts:
    269
    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):
    1. Shader "Custom/SpinBlur"{
    2.     Properties{
    3.         _Color("Main Color", Color) = (1,1,1,1)
    4.         _Samples("Samples", Range(0,360)) = 100
    5.         _Angle("Angle", Range(0,360)) = 10
    6.         _MainTex("Color (RGB) Alpha (A)", 2D) = "white"
    7.     }
    8.     SubShader{
    9.     Tags{ "Queue" = "Transparent" "RenderType" = "Transparent" }
    10.  
    11.     LOD 200
    12.     Cull Off
    13.  
    14.     CGPROGRAM
    15.     #pragma target 3.0
    16.     #pragma surface surf Lambert alpha
    17.  
    18.     sampler2D _MainTex;
    19.     int _Angle;
    20.     int _Samples;
    21.     float4 _Color;
    22.    
    23.     struct Input {
    24.         float2 uv_MainTex;
    25.         float4 screenPos;
    26.     };
    27.  
    28.     float2 rotateUV(float2 uv, float degrees) {
    29.         const float Deg2Rad = (UNITY_PI * 2.0) / 360.0;
    30.         float rotationRadians = degrees * Deg2Rad;
    31.         float s = sin(rotationRadians);
    32.         float c = cos(rotationRadians);
    33.         float2x2 rotationMatrix = float2x2(c, -s, s, c);
    34.         uv -= 0.5;
    35.         uv = mul(rotationMatrix, uv);
    36.         uv += 0.5;
    37.         return uv;
    38.     }
    39.  
    40.     void surf(Input IN, inout SurfaceOutput o) {
    41.         const float Deg2Rad = (UNITY_PI * 2.0) / 360.0;
    42.         const float Rad2Deg = 180.0 / UNITY_PI;
    43.         float2 vUv = IN.uv_MainTex;
    44.         float2 coord = vUv;
    45.         float4 FragColor = float4(0.0, 0.0, 0.0, 0.0);
    46.         int samp = _Samples;
    47.         if (samp <= 0) samp = 1;
    48.         for (float i = 0; i < samp; i++) {
    49.             float a = (float)_Angle / (float)samp;
    50.             coord = rotateUV(coord, a);
    51.             float4 texel = tex2D(_MainTex, coord);
    52.             texel *= 1.0 / samp;
    53.             FragColor += texel;
    54.         }
    55.         float4 c = FragColor*_Color;
    56.         o.Albedo = c.rgb;
    57.         o.Alpha = c.a;
    58.     }
    59.     ENDCG
    60.     }
    61.         FallBack "Diffuse"
    62. }

    Thanks again!
     
  7. willemsenzo

    willemsenzo

    Joined:
    Nov 15, 2012
    Posts:
    585
    Thanks for sharing it works perfect.
     
    fra3point likes this.
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,317
    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.
     
    fra3point likes this.
  9. fra3point

    fra3point

    Joined:
    Aug 20, 2012
    Posts:
    269
    Thanks, @bgolus, that worked!! :)
     
  10. RecepBala

    RecepBala

    Joined:
    May 23, 2017
    Posts:
    4
    Thanks for sharing it works perfect.
     
    fra3point likes this.
  11. mediamax07

    mediamax07

    Joined:
    May 22, 2017
    Posts:
    7
    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):
    1.  
    2. const float Deg2Rad = 0.017453293;
    3. const float Rad2Deg = 57.295777937;
    4. float2 coord = UV;
    5. float4 FragColor = float4(0.0, 0.0, 0.0, 0.0);
    6. uint samp = Angle;
    7.  
    8. if (samp < 1)
    9.         samp = 1;
    10.  
    11. for (float i = 0; i < samp; i++)
    12. {
    13.         float rotationRadians = i * Deg2Rad / samp;
    14.         float s = sin(rotationRadians);
    15.         float c = cos(rotationRadians);
    16.         float2x2 rotationMatrix = float2x2(c, -s, s, c);
    17.  
    18.         coord -= 0.5;
    19.         coord = mul(rotationMatrix, coord);
    20.         coord += 0.5;
    21.  
    22.         float4 texel = SAMPLE_TEXTURE2D(MainTex, SS, coord);
    23.  
    24.         texel *= 1.0 / samp;
    25.         FragColor += texel;
    26. }
    27.  
    28. Out = FragColor;
    29.  
    Screenshot_2.png Screenshot_1.png
     
    fra3point likes this.
  12. fra3point

    fra3point

    Joined:
    Aug 20, 2012
    Posts:
    269
    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
     
    Last edited: Nov 3, 2020
  13. mediamax07

    mediamax07

    Joined:
    May 22, 2017
    Posts:
    7
    that's what I am exactly doing! Baking textures and saving them using a RenderTexture buffer along with Graphics.Blit to a Texture2D
     
    Last edited: Jan 26, 2021
  14. mgear

    mgear

    Joined:
    Aug 3, 2010
    Posts:
    9,285
  15. fra3point

    fra3point

    Joined:
    Aug 20, 2012
    Posts:
    269
    Last edited: Oct 26, 2021
    blueivy likes this.
  16. Reanimate_L

    Reanimate_L

    Joined:
    Oct 10, 2009
    Posts:
    2,788