Search Unity

Post processing: blurry edges

Discussion in 'Scripting' started by Karrzun, Jan 15, 2020.

  1. Karrzun

    Karrzun

    Joined:
    Oct 26, 2017
    Posts:
    82
    Hi everyone,

    I want to create an effect that blurs the edges of my screen. I did some research and stumbled upon various hacky ways that would achieve the desired result but the cleanest way seems to be the post processing stack with a custom effect. Unfortunately, I'm not super familiar with that and would appricate any help.

    What I'm trying to achieve is that the center of my screen is rendered normally but the further you get to the edge of the screen, the blurrier it gets. So in the following picture, everything in white is't blurred at all, everything in light grey is slightly blurred, everything in dark grey a bit more and everything in black is heavily blurred.
    BlurMask.png


    Can anybody help me out how to do that or where to find a tutorial for that? The stuff I found didn't lead to anything.


    Thank you in advance!


    For reference, here's what I looked at so far:
    https://forum.unity.com/threads/legacy-blur-for-post-processing-stack-v2.488222/
    https://github.com/Unity-Technologies/PostProcessing/wiki/Writing-Custom-Effects
    https://pastebin.com/wVxpxpvt
     
  2. csofranz

    csofranz

    Joined:
    Apr 29, 2017
    Posts:
    1,025
    I'm not sure how fluent you are in Unity shaders (post-processing is always shader work), but if you are, this is straightforward.

    What you want it an effect that is 'masked' by the color of a texture, here your image with white center and increasing black tone at the edges. The method to do this is to take the original pixel value, and calculate a blurred value (using box blur, gauss blur or whatever you prefer). Then get the corresponding 'mask' value (i.e. the pixel value that corresponds in the mask to the one you just processed). depending on it's 'brightness' value (or any red, green, or blue value since they are all the same if your mask is a grey mask), mix the blurred and unblurred values as follows:

    result = brightness * original + (1-brightness) * blurred

    You'll need to base this off an image (camera effect) shader. You can get a good blur shader tutorial from many sources on the net. Use that as a base, then add the mask texture as the *second* texture (as for image effects, the first texture (often called _MainTex) is the input camera image). the fifficulty isn't so much the masking, but getting a good, performant blur shader if you havent done shaders before. This should get you started.
     
  3. Karrzun

    Karrzun

    Joined:
    Oct 26, 2017
    Posts:
    82
    Thank you for your comment. I did some further research about shaders, including the link you posted and made some progress. Currently I can detect the pixels I want to be blurred and blur them by a desired amount. I am stuck when it comes to changing the strength of the effect between different pixels, however.
    As mentioned above, I would like to "lerp" the effect towards the center (if you can call it like that). At the moment, all my blured pixels are blurred by the same amount creating a hard cut at the edge between blurred and unblurred pixels.

    Code:
    Code (CSharp):
    1. Shader "Hidden/EdgeBlur"
    2. {
    3.     HLSLINCLUDE
    4.  
    5.         #include "Packages/com.unity.postprocessing/PostProcessing/Shaders/StdLib.hlsl"
    6.  
    7.         TEXTURE2D_SAMPLER2D (_MainTex, sampler_MainTex);
    8.         float _EdgeDistance;
    9.         float _BlurSize;
    10.         float _StandardDeviation;
    11.  
    12.  
    13.         #define PI 3.14159265359
    14.         #define E 2.71828182846
    15.         #define SAMPLES 50
    16.  
    17.  
    18.         float4 Frag (VaryingsDefault i) : SV_Target
    19.         {
    20.             float4 col = 0;
    21.             float sum = 0;
    22.  
    23.             if (i.texcoord.x < _EdgeDistance || i.texcoord.x > (1 - _EdgeDistance) || i.texcoord.y < _EdgeDistance || i.texcoord.y > (1 - _EdgeDistance)) {
    24.                 float stDevSquared = _StandardDeviation * _StandardDeviation;
    25.  
    26.                 for (float index = 0; index < SAMPLES; index++) {
    27.                     float myOffset = (index/(SAMPLES-1) - 0.5) * _BlurSize;
    28.                     float2 uv = i.texcoord.xy + float2 (0, myOffset);
    29.                     float gauss = (1 / sqrt (2*PI*stDevSquared)) * pow (E, -((myOffset*myOffset)/(2*stDevSquared)));
    30.                     sum += gauss;
    31.                     col += SAMPLE_TEXTURE2D (_MainTex, sampler_MainTex, uv) * gauss;
    32.                 }
    33.  
    34.                 for (float index = 0; index < SAMPLES; index++) {
    35.                     float myOffset = (index/(SAMPLES-1) - 0.5) * _BlurSize;
    36.                     float2 uv = i.texcoord.xy + float2 (myOffset, 0);
    37.                     float gauss = (1 / sqrt (2*PI*stDevSquared)) * pow (E, -((myOffset*myOffset)/(2*stDevSquared)));
    38.                     sum += gauss;
    39.                     col += SAMPLE_TEXTURE2D (_MainTex, sampler_MainTex, uv) * gauss;
    40.                 }
    41.  
    42.                 col = col / sum;
    43.             } else {
    44.                 col = SAMPLE_TEXTURE2D (_MainTex, sampler_MainTex, i.texcoord);
    45.             }
    46.  
    47.             return col;
    48.         }
    49.  
    50.     ENDHLSL
    51.  
    52.     SubShader
    53.     {
    54.         Cull Off ZWrite Off ZTest Always
    55.  
    56.         Pass
    57.         {
    58.             HLSLPROGRAM
    59.  
    60.                 #pragma vertex VertDefault
    61.                 #pragma fragment Frag
    62.  
    63.             ENDHLSL
    64.         }
    65.     }
    66. }

    Visualization:
    BlurEffect_2020_01_18.PNG


    How would I go about softening the blur effect towards the center instead of having this edge? I tried assigning the number of samples dynamically by using something like this

    Code (CSharp):
    1.                 float myXDistance = min (i.texcoord.x, 1-i.texcoord.x);
    2.                 float myYDistance = min (i.texcoord.y, 1-i.texcoord.y);
    3.                 float minDistance = min (myXDistance, myYDistance);
    4.                 float samples = (_EdgeDistance - minDistance) * (1 / _EdgeDistance) * SAMPLES;
    and then using samples sinstead of SAMPLES within the for loops but that results in an error, stating that the loops won't terminate within 1024 iterations and are, thus, prohibited.

    What else can I do? Thank you in advance!


    Edit: I'm well aware that my code is probably highly unoptimized, but that's not my focus right now tbh. ;)
     
    Last edited: Jan 18, 2020
  4. csofranz

    csofranz

    Joined:
    Apr 29, 2017
    Posts:
    1,025
    The easiest and most flexible way to control the blur is by using another texture, and image just like you originally proposed. If you use Unity's property block in your code, you can even take advantage of Unity's Instector UI. Use a nother texture, let's call it _Mask

    Code (CSharp):
    1. Properties
    2.    {
    3.       _MainTex ("Source", 2D) = "white" {} // blit will connect to this (TEXCOORD0) and fill with source
    4.       _Mask("Mask", 2D)= "white" {} // controls how strong to apply blur
    5.    }
    Now, after you have calculated the color (i.e. the fully blurred pixel value), simply sample the mask texture at the same pixel location. We will only use the red channel, but if it is a mask using only grey values, any channel will do. Red will be a value between 0 and 1, and is directly the percentage of how clear the pixel is going to be since you defined your mask as black representing fully blurred. If the mask pixel value at that location is white, the pixel won't be blurred at all, if it is black is going to be fully blurred. The advantage of having a mask is that you can have any form drawn (e.g. an oval), and have a black-white transition just like you like it without having to code for it. The entire transition is defined by the mask image.

    so, we are going to use three color valus:
    orig -- which is the original texture value (i.e. screen pixel) at the current location
    col -- the blurred pixel value
    mask -- the mask sampled at the same uv as the input (screen) texture.

    you then calculate the definitive pixel value simply as a blend.

    Code (CSharp):
    1. float orig = SAMPLE_TEXTURE2D (_MainTex, sampler_MainTex, i.texcoord);
    2. float mask = SAMPLE_TEXTURE2D(_Mask, sampler_Mask, i.maskCoord); // you need to define these first
    3. float unblurred = mask.r; // use red channel since r = g = b
    4. float blurred = (1.0 - unblurred)
    5. result = (orig * unblurred) + (col * blurred); // blend blurred and unblurred by mask value
    6. return result;
    Above code should replace the 'return col' in your code, plus obviously, the code code you wrote to determine when to blur and when not to.
     
    Last edited: Jan 18, 2020
  5. csofranz

    csofranz

    Joined:
    Apr 29, 2017
    Posts:
    1,025
    OK, I've had some time and threw together a blend shader that uses a mask. Blurring is simply by sampleing horizontally and vertically (but nothing else not diagonally) and then dividing by samples. It then uses a mask image to see how much blending to apply to the original pixel

    This is the shader:
    Code (CSharp):
    1. Shader "Hidden/image blur"
    2. {
    3.  
    4.     // simple masked blur shader.
    5.     // MainTex is used by unity to input camera image
    6.     // Amount is the amount of blur
    7.     // Mask is an image that uses brightness of pixel
    8.     // to determine how much of the blur is applied to pixel
    9.     // white = no blur, black = full blur, values inbetween = mix
    10.  
    11.     // written by christian franz, you have permission to use as you
    12.     // see fit.
    13.  
    14.     Properties
    15.     {
    16.         _MainTex ("Texture", 2D) = "white" {} // bit input from Unity
    17.         _Amount ("Amount", Range(1, 10)) = 7 // how much blurring
    18.         _Mask("Mask", 2D) = "white" {} // blend mask, white = no blur, black = full blur
    19.     }
    20.  
    21.     SubShader
    22.     {
    23.         // No culling or depth
    24.         Cull Off ZWrite Off ZTest Always
    25.  
    26.         Pass
    27.         {
    28.             CGPROGRAM
    29.             #pragma vertex vert
    30.             #pragma fragment frag
    31.  
    32.             #include "UnityCG.cginc"
    33.  
    34.            
    35.  
    36.             struct appdata   {
    37.                 float4 vertex : POSITION;
    38.                 float2 uv : TEXCOORD0;
    39.             };
    40.  
    41.             struct v2f  {
    42.                 float2 uv : TEXCOORD0;
    43.                 float2 muv : TEXCOORD1;
    44.                 float4 vertex : SV_POSITION;
    45.             };
    46.  
    47.  
    48.             sampler2D _MainTex;
    49.             sampler2D _Mask;
    50.             float _Amount;
    51.             // implicit defines
    52.             float4 _MainTex_ST, _Mask_ST, _MainTex_TexelSize;
    53.  
    54.  
    55.             v2f vert (appdata v) {
    56.                 v2f o;
    57.                 o.vertex = UnityObjectToClipPos(v.vertex);
    58.                 o.uv = TRANSFORM_TEX(v.uv, _MainTex);
    59.                 o.muv = TRANSFORM_TEX(v.uv, _Mask);
    60.                 return o;
    61.             }
    62.  
    63.  
    64.             fixed4 frag (v2f i) : SV_Target {
    65.                 fixed4 col = fixed4(0, 0, 0, 1.0);
    66.                 // sum all pixels from -Amount to Amount
    67.                 // horizontally
    68.                 for (int index = -_Amount; index <= _Amount; index++) {
    69.                     float2 uv = i.uv + float2(index * _MainTex_TexelSize.x, 0);
    70.                     col += tex2D(_MainTex, uv);
    71.                 }
    72.  
    73.                 // vertically
    74.                 for (int index = -_Amount; index <= _Amount; index++) {
    75.                     float2 uv = i.uv + float2(0, index * _MainTex_TexelSize.y);
    76.                     col += tex2D(_MainTex, uv);
    77.                 }
    78.                 // now divide by the number of samples. Samples is 2 * 2 * amount + 1: Amount = 1: -1, 0, 1 = 3 samples * 2 (horizontally, vertically)
    79.                 col /= (_Amount * 4 + 2);
    80.  
    81.                 // now sample the original value
    82.                 float4 orig = tex2D(_MainTex, i.uv);
    83.                 float4 mask = tex2D(_Mask, i.muv);
    84.                 float unblurred = mask.r;
    85.                 float blurred = (1.0 - unblurred);
    86.                 col = col * blurred + orig * unblurred;
    87.                 return col;
    88.             }
    89.             ENDCG
    90.         }
    91.     }
    92. }
    This is a horizontal mask that will progressively blur more from left (white) to right (black)

    horizontal blend mask.jpg
    And this is how it looks with Amount = 7 and above mask applied to camera
    upload_2020-1-19_13-26-45.png

    Oh, and here is the camera script

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. [RequireComponent(typeof(Camera))]
    6. [ExecuteInEditMode]
    7.  
    8.  
    9. public class camerafx : MonoBehaviour
    10. {
    11.     public Material material;
    12.  
    13.     void Start()
    14.     {
    15.         if (!SystemInfo.supportsImageEffects || null == material ||
    16.            null == material.shader || !material.shader.isSupported)
    17.         {
    18.             enabled = false;
    19.             return;
    20.         }
    21.     }
    22.  
    23.     void OnRenderImage(RenderTexture source, RenderTexture destination)
    24.     {
    25.         Graphics.Blit(source, destination, material);
    26.     }
    27. }
    28.  
     
unityunity