Search Unity

Gaussian Blur not working on regular texture

Discussion in 'Shaders' started by Mr_Admirals, Feb 2, 2019.

  1. Mr_Admirals

    Mr_Admirals

    Joined:
    May 13, 2017
    Posts:
    86
    Hey,

    So, I'm building a tool for an artist, and one of the features requested was the ability to blur a texture. I have a blur shader, but am running into issues. Namely, the blur loop runs, but no blur is occurring. I can't quite pinpoint the issue, however.

    Here's the blur shader I'm using:

    Code (CSharp):
    1. //SOURCE: https://github.com/keijiro/GaussianBlur
    2. Shader "Hidden/GaussianTextureBlur"
    3. {
    4.     Properties
    5.     {
    6.         _MainTex("-", 2D) = "white" {}
    7.     }
    8.  
    9.     CGINCLUDE
    10.  
    11.     #include "UnityCG.cginc"
    12.  
    13.     sampler2D_float _MainTex;
    14.     float4 _MainTex_TexelSize;
    15.  
    16.     // 9-tap Gaussian filter with linear sampling
    17.     // http://rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/
    18.     half gaussian_filter(float2 uv, float2 stride)
    19.     {
    20.         half s = tex2D(_MainTex, float4(uv, 0, 0)).r * 0.2270270270;
    21.  
    22.         float2 d1 = stride * 1.3846153846;
    23.         s += tex2D(_MainTex, uv + d1).r * 0.3162162162;
    24.         s += tex2D(_MainTex, uv - d1).r * 0.3162162162;
    25.  
    26.         float2 d2 = stride * 3.2307692308;
    27.         s += tex2D(_MainTex, uv + d2).r * 0.0702702703;
    28.         s += tex2D(_MainTex, uv - d2).r * 0.0702702703;
    29.  
    30.         return s;
    31.     }
    32.  
    33.     // Quarter downsampler
    34.     half4 frag_quarter(v2f_img i) : SV_Target
    35.     {
    36.         float4 d = _MainTex_TexelSize.xyxy * float4(1, 1, -1, -1);
    37.         half4 s;
    38.         s = tex2D(_MainTex, i.uv + d.xy);
    39.         s += tex2D(_MainTex, i.uv + d.xw);
    40.         s += tex2D(_MainTex, i.uv + d.zy);
    41.         s += tex2D(_MainTex, i.uv + d.zw);
    42.         return s * 0.25;
    43.     }
    44.  
    45.     // Separable Gaussian filters
    46.     half4 frag_blur_h(v2f_img i) : SV_Target
    47.     {
    48.         return gaussian_filter(i.uv, float2(_MainTex_TexelSize.x, 0));
    49.     }
    50.  
    51.     half4 frag_blur_v(v2f_img i) : SV_Target
    52.     {
    53.         return gaussian_filter(i.uv, float2(0, _MainTex_TexelSize.y));
    54.     }
    55.  
    56.     ENDCG
    57.  
    58.     Subshader
    59.     {
    60.         // Quarter Downsample
    61.         Pass
    62.         {
    63.             ZTest Always Cull Off ZWrite On
    64.             CGPROGRAM
    65.             #pragma vertex vert_img
    66.             #pragma fragment frag_quarter
    67.             ENDCG
    68.         }
    69.  
    70.         // Horizontal Blur
    71.         Pass
    72.         {
    73.             ZTest Always Cull Off ZWrite On
    74.             CGPROGRAM
    75.             #pragma vertex vert_img
    76.             #pragma fragment frag_blur_h
    77.             #pragma target 3.0
    78.             ENDCG
    79.         }
    80.  
    81.         // Vertical Blur
    82.         Pass
    83.         {
    84.             ZTest Always Cull Off ZWrite On
    85.             CGPROGRAM
    86.             #pragma vertex vert_img
    87.             #pragma fragment frag_blur_v
    88.             #pragma target 3.0
    89.             ENDCG
    90.         }
    91.     }
    92. }
    And here's the code that runs it:

    Code (CSharp):
    1. private void BlurHeightMap()
    2.     {
    3.         Material blurMat = new Material(blurShader);
    4.         blurMat.hideFlags = HideFlags.HideAndDontSave;
    5.  
    6.         RenderTexture rt1, rt2;
    7.  
    8.         if (downSampleMode == DownSampleMode.Half)
    9.         {
    10.             rt1 = RenderTexture.GetTemporary(heightMap.width / 2, heightMap.height / 2, 0, RenderTextureFormat.ARGB32);
    11.             rt2 = RenderTexture.GetTemporary(heightMap.width / 2, heightMap.height / 2, 0, RenderTextureFormat.ARGB32);
    12.             Graphics.Blit(heightMap, rt1);
    13.         }
    14.         else if (downSampleMode == DownSampleMode.Quarter)
    15.         {
    16.             rt1 = RenderTexture.GetTemporary(heightMap.width / 4, heightMap.height / 4, 0, RenderTextureFormat.ARGB32);
    17.             rt2 = RenderTexture.GetTemporary(heightMap.width / 4, heightMap.height / 4, 0, RenderTextureFormat.ARGB32);
    18.             Graphics.Blit(heightMap, rt1, blurMat, 0);
    19.         }
    20.         else
    21.         {
    22.             rt1 = RenderTexture.GetTemporary(heightMap.width, heightMap.height, 0, RenderTextureFormat.ARGB32);
    23.             rt2 = RenderTexture.GetTemporary(heightMap.width, heightMap.height, 0, RenderTextureFormat.ARGB32);
    24.             Graphics.Blit(heightMap, rt1);
    25.         }
    26.  
    27.         for (var i = 0; i < blurIterations; i++)
    28.         {
    29.             Graphics.Blit(rt1, rt2, blurMat, 1);
    30.             Graphics.Blit(rt2, rt1, blurMat, 2);
    31.         }
    32.  
    33.         Graphics.CopyTexture(rt1, heightMap);
    34.  
    35.         RenderTexture.ReleaseTemporary(rt1);
    36.         RenderTexture.ReleaseTemporary(rt2);
    37.     }
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    If you’re expecting to see the blur update a texture asset, that won’t do it. That may update the texture on the GPU, but not the texture on disk or on CPU side memory.

    You likely at the very least need to use ReadPixels, and may need to write it back to disk:
    https://docs.unity3d.com/ScriptReference/ImageConversion.EncodeToPNG.html
     
  3. Mr_Admirals

    Mr_Admirals

    Joined:
    May 13, 2017
    Posts:
    86
    Hmm... It still doesn't seem to be working.

    Basically, I can see if the blur is being applied correctly because I'm using the blurred texture as a height map to generate terrain. With 0 blurs, the terrain comes out very angular. When cranking up the blur iterations and trying again, the terrain doesn't change.
     
  4. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    Height maps for terrain should be at least RHalf. The 256 steps of an 8 bit color channel aren't usually enough for terrain.
     
  5. Mr_Admirals

    Mr_Admirals

    Joined:
    May 13, 2017
    Posts:
    86
    Good call!

    Also, I did end up figuring it out. I was forgetting to call the blur function (though your ReadPixels suggestion was the right call) I set while making a bunch of changes. Silly me.

    UPDATE: Tried setting it to RHalf, and it seems to have broken it. The mesh is very flat. When I crank up the height scale, it ends up looking very geometric.
     
    Last edited: Feb 3, 2019
  6. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    What is the original texture's format? What format is the texture the blur spits out? It should be RHalf all the way through for it to work properly, or at least output to an RHalf. I'm also not sure how you're then passing that texture to the terrain system.
     
  7. Mr_Admirals

    Mr_Admirals

    Joined:
    May 13, 2017
    Posts:
    86
    It was on automatic before. My Texture Type has also been Single Channel - Red.

    Here's what it's currently set to:
    upload_2019-2-3_19-2-7.png

    Here's how I blur and sample the height map:
    Code (CSharp):
    1. private void BlurHeightMap()
    2.     {
    3.         Material blurMat = new Material(blurShader);
    4.         blurMat.hideFlags = HideFlags.HideAndDontSave;
    5.  
    6.         RenderTexture rt1, rt2;
    7.  
    8.         if (downSampleMode == DownSampleMode.Half)
    9.         {
    10.             rt1 = RenderTexture.GetTemporary(heightMap.width / 2, heightMap.height / 2, 0, RenderTextureFormat.RHalf);
    11.             rt2 = RenderTexture.GetTemporary(heightMap.width / 2, heightMap.height / 2, 0, RenderTextureFormat.RHalf);
    12.             Graphics.Blit(heightMap, rt1);
    13.         }
    14.         else if (downSampleMode == DownSampleMode.Quarter)
    15.         {
    16.             rt1 = RenderTexture.GetTemporary(heightMap.width / 4, heightMap.height / 4, 0, RenderTextureFormat.RHalf);
    17.             rt2 = RenderTexture.GetTemporary(heightMap.width / 4, heightMap.height / 4, 0, RenderTextureFormat.RHalf);
    18.             Graphics.Blit(heightMap, rt1, blurMat, 0);
    19.         }
    20.         else
    21.         {
    22.             rt1 = RenderTexture.GetTemporary(heightMap.width, heightMap.height, 0, RenderTextureFormat.RHalf);
    23.             rt2 = RenderTexture.GetTemporary(heightMap.width, heightMap.height, 0, RenderTextureFormat.RHalf);
    24.             Graphics.Blit(heightMap, rt1);
    25.         }
    26.  
    27.         for (var i = 0; i < blurIterations; i++)
    28.         {
    29.             Graphics.Blit(rt1, rt2, blurMat, 1);
    30.             Graphics.Blit(rt2, rt1, blurMat, 2);
    31.         }
    32.  
    33.         RenderTexture.active = rt1;
    34.  
    35.         internalHeightMap = new Texture2D(rt1.width, rt2.width);
    36.         internalHeightMap.ReadPixels(new Rect(0, 0, rt1.width, rt1.height), 0, 0);
    37.         internalHeightMap.Apply();
    38.  
    39.         RenderTexture.active = null;
    40.  
    41.         RenderTexture.ReleaseTemporary(rt1);
    42.         RenderTexture.ReleaseTemporary(rt2);
    43.     }
    44.  
    45.     private void SampleHeightMap()
    46.     {
    47.         if(internalHeightMap == null)
    48.         {
    49.             return;
    50.         }
    51.  
    52.         for(int i = 0; i < vertices.Length; i++)
    53.         {
    54.             int x = Mathf.FloorToInt(UVs[i].x * internalHeightMap.width);
    55.             int y = Mathf.FloorToInt(UVs[i].y * internalHeightMap.height);
    56.             float height;
    57.             if(invertMap)
    58.             {
    59.                 height = 1 - (internalHeightMap.GetPixel(x, y).r - 0.5f) * heightScale;
    60.             }
    61.             else
    62.             {
    63.                 height = (internalHeightMap.GetPixel(x, y).r - 0.5f) * heightScale;
    64.             }
    65.  
    66.             vertices[i] = new Vector3(vertices[i].x, height, vertices[i].z);
    67.         }
    68.         mesh.vertices = vertices;
    69.     }
    This is the output:

    upload_2019-2-3_19-4-20.png

    It honestly looks really cool, just not the style I'm going for, haha.

    Here's what I was getting with the Render Textures set to ARGB32 or default:

    upload_2019-2-3_19-6-40.png
     
  8. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    That's going to create an RGBA32 by default. You need to assign the format there too. It's also using a mismatched height and width.

    new Texture2D(rt1.width, rt1.height, TextureFormat.RHalf, false, true);
     
  9. Mr_Admirals

    Mr_Admirals

    Joined:
    May 13, 2017
    Posts:
    86
    Oh geeze. How did I miss that (width, width)?

    Okay, that's all set now. But it looks like ReadPixels doesn't support RHalf, so I don't quite know how to get the information into the texture.
     
  10. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,348
    Hmm. You might be able to use a ARGBHalf instead and read from that.
     
  11. Mr_Admirals

    Mr_Admirals

    Joined:
    May 13, 2017
    Posts:
    86
    Hmm... Still getting the same issue with ARGBHalf.

    upload_2019-2-4_9-3-14.png