Post processing: blurry edges

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.

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:

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.

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:

Shader "Hidden/EdgeBlur"
{
    HLSLINCLUDE

        #include "Packages/com.unity.postprocessing/PostProcessing/Shaders/StdLib.hlsl"

        TEXTURE2D_SAMPLER2D (_MainTex, sampler_MainTex);
        float _EdgeDistance;
        float _BlurSize;
        float _StandardDeviation;


        #define PI 3.14159265359
        #define E 2.71828182846
        #define SAMPLES 50


        float4 Frag (VaryingsDefault i) : SV_Target
        {
            float4 col = 0;
            float sum = 0;

            if (i.texcoord.x < _EdgeDistance || i.texcoord.x > (1 - _EdgeDistance) || i.texcoord.y < _EdgeDistance || i.texcoord.y > (1 - _EdgeDistance)) {
                float stDevSquared = _StandardDeviation * _StandardDeviation;

                for (float index = 0; index < SAMPLES; index++) {
                    float myOffset = (index/(SAMPLES-1) - 0.5) * _BlurSize;
                    float2 uv = i.texcoord.xy + float2 (0, myOffset);
                    float gauss = (1 / sqrt (2*PI*stDevSquared)) * pow (E, -((myOffset*myOffset)/(2*stDevSquared)));
                    sum += gauss;
                    col += SAMPLE_TEXTURE2D (_MainTex, sampler_MainTex, uv) * gauss;
                }

                for (float index = 0; index < SAMPLES; index++) {
                    float myOffset = (index/(SAMPLES-1) - 0.5) * _BlurSize;
                    float2 uv = i.texcoord.xy + float2 (myOffset, 0);
                    float gauss = (1 / sqrt (2*PI*stDevSquared)) * pow (E, -((myOffset*myOffset)/(2*stDevSquared)));
                    sum += gauss;
                    col += SAMPLE_TEXTURE2D (_MainTex, sampler_MainTex, uv) * gauss;
                }

                col = col / sum;
            } else {
                col = SAMPLE_TEXTURE2D (_MainTex, sampler_MainTex, i.texcoord);
            }

            return col;
        }

    ENDHLSL

    SubShader
    {
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            HLSLPROGRAM

                #pragma vertex VertDefault
                #pragma fragment Frag

            ENDHLSL
        }
    }
}

Visualization:

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

                float myXDistance = min (i.texcoord.x, 1-i.texcoord.x);
                float myYDistance = min (i.texcoord.y, 1-i.texcoord.y);
                float minDistance = min (myXDistance, myYDistance);
                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. :wink:

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

Properties
   {
      _MainTex ("Source", 2D) = "white" {} // blit will connect to this (TEXCOORD0) and fill with source
      _Mask("Mask", 2D)= "white" {} // controls how strong to apply blur
   }

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.

float orig = SAMPLE_TEXTURE2D (_MainTex, sampler_MainTex, i.texcoord);
float mask = SAMPLE_TEXTURE2D(_Mask, sampler_Mask, i.maskCoord); // you need to define these first
float unblurred = mask.r; // use red channel since r = g = b
float blurred = (1.0 - unblurred)
result = (orig * unblurred) + (col * blurred); // blend blurred and unblurred by mask value
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.

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:

Shader "Hidden/image blur"
{

    // simple masked blur shader.
    // MainTex is used by unity to input camera image
    // Amount is the amount of blur
    // Mask is an image that uses brightness of pixel
    // to determine how much of the blur is applied to pixel
    // white = no blur, black = full blur, values inbetween = mix

    // written by christian franz, you have permission to use as you
    // see fit.

    Properties
    {
        _MainTex ("Texture", 2D) = "white" {} // bit input from Unity
        _Amount ("Amount", Range(1, 10)) = 7 // how much blurring
        _Mask("Mask", 2D) = "white" {} // blend mask, white = no blur, black = full blur
    }

    SubShader
    {
        // No culling or depth
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

           

            struct appdata   {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f  {
                float2 uv : TEXCOORD0;
                float2 muv : TEXCOORD1;
                float4 vertex : SV_POSITION;
            };


            sampler2D _MainTex;
            sampler2D _Mask;
            float _Amount;
            // implicit defines
            float4 _MainTex_ST, _Mask_ST, _MainTex_TexelSize;


            v2f vert (appdata v) {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.muv = TRANSFORM_TEX(v.uv, _Mask);
                return o;
            }


            fixed4 frag (v2f i) : SV_Target {
                fixed4 col = fixed4(0, 0, 0, 1.0);
                // sum all pixels from -Amount to Amount
                // horizontally
                for (int index = -_Amount; index <= _Amount; index++) {
                    float2 uv = i.uv + float2(index * _MainTex_TexelSize.x, 0);
                    col += tex2D(_MainTex, uv);
                }

                // vertically
                for (int index = -_Amount; index <= _Amount; index++) {
                    float2 uv = i.uv + float2(0, index * _MainTex_TexelSize.y);
                    col += tex2D(_MainTex, uv);
                }
                // now divide by the number of samples. Samples is 2 * 2 * amount + 1: Amount = 1: -1, 0, 1 = 3 samples * 2 (horizontally, vertically)
                col /= (_Amount * 4 + 2);

                // now sample the original value
                float4 orig = tex2D(_MainTex, i.uv);
                float4 mask = tex2D(_Mask, i.muv);
                float unblurred = mask.r;
                float blurred = (1.0 - unblurred);
                col = col * blurred + orig * unblurred;
                return col;
            }
            ENDCG
        }
    }
}

This is a horizontal mask that will progressively blur more from left (white) to right (black)

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

Oh, and here is the camera script

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[RequireComponent(typeof(Camera))]
[ExecuteInEditMode]


public class camerafx : MonoBehaviour
{
    public Material material;

    void Start()
    {
        if (!SystemInfo.supportsImageEffects || null == material ||
           null == material.shader || !material.shader.isSupported)
        {
            enabled = false;
            return;
        }
    }

    void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        Graphics.Blit(source, destination, material);
    }
}