Search Unity

Question Achieving a parallax effect?

Discussion in 'Shaders' started by CyanSlinky, Sep 6, 2022.

  1. CyanSlinky

    CyanSlinky

    Joined:
    Feb 27, 2014
    Posts:
    28
    I'm trying to achieve a parallax effect similar to this:



    There are also several shadertoy shaders that are like this.
    https://www.shadertoy.com/view/XssXz4
    https://www.shadertoy.com/view/4dyfDt
    https://www.shadertoy.com/view/stBfRz

    I've converted shadertoy shaders in the past but I'm not entirely sure how to convert these shaders...

    Here's my current 2D shader that creates a grid of dots that I'd like to parallax:

    Code (Shader):
    1. Shader "Unlit/Grid"
    2. {
    3.     Properties
    4.     {
    5.         _BackgroundColor ("Background Color", Color) = (0,0,0,0)
    6.         _DotColor ("Dot Color", Color) = (1,1,1,1)
    7.  
    8.         _DotOffset("Dot Offset", Vector) = (0,0,0,0)
    9.         _DotRadius("Dot Radius", Float) = 0.04
    10.         _DotSpacing("Dot Spacing", Float) = 1.0
    11.         _DotSmoothing("Dot Smoothing", Float) = 1.0
    12.     }
    13.     SubShader
    14.     {
    15.         Tags { "RenderType"="Transparent" "Queue" = "Transparent" "IgnoreProjector" = "True"}
    16.         Blend SrcAlpha OneMinusSrcAlpha
    17.         Cull Off ZWrite Off
    18.  
    19.         Pass
    20.         {
    21.             CGPROGRAM
    22.             #pragma vertex vert
    23.             #pragma fragment frag
    24.             // make fog work
    25.             #pragma multi_compile_fog
    26.  
    27.             #include "UnityCG.cginc"
    28.  
    29.             uniform float4 _BackgroundColor;
    30.             uniform float4 _DotColor;
    31.  
    32.             uniform float2 _DotOffset;
    33.             uniform float _DotRadius;
    34.             uniform float _DotSpacing;
    35.             uniform float _DotSmoothing;
    36.  
    37.             struct appdata
    38.             {
    39.                 float2 uv : TEXCOORD0;
    40.                 float4 vertex : POSITION;
    41.                 float3 worldPos : TEXCOORD1;
    42.             };
    43.  
    44.             struct v2f
    45.             {
    46.                 float2 uv : TEXCOORD0;
    47.                 float4 vertex : SV_POSITION;
    48.                 float3 worldPos : TEXCOORD1;
    49.             };
    50.  
    51.             float dots( float2 p, float2 offset, float spacing, float radius, float smoothing )
    52.             {
    53.                 // Divide into squares, create grid
    54.                 float2 dst = fmod(abs(p - offset), spacing);
    55.                 //float2 dst = opRep(abs(p - offset), spacing);
    56.  
    57.                 // Create circles
    58.                 float2 c = distance(dst, float2(0.5, 0.5));
    59.                 float2 dc = fwidth(c) * smoothing;
    60.                 float2 f = smoothstep(-dc+radius, dc+radius, c);
    61.                 float result = saturate(f.x * f.y);
    62.  
    63.                 return 1-result;
    64.             }
    65.  
    66.             v2f vert (appdata v)
    67.             {
    68.                 v2f o;
    69.                 o.vertex = UnityObjectToClipPos(v.vertex);
    70.                 o.uv = v.uv;
    71.                 o.worldPos = mul(unity_ObjectToWorld, v.vertex);
    72.                 return o;
    73.             }
    74.  
    75.             fixed4 frag (v2f i) : SV_Target
    76.             {
    77.                 float3 p = i.worldPos.xyz;
    78.  
    79.                 fixed4 col = _BackgroundColor;
    80.  
    81.                 float gridDots = dots(p, _DotOffset, _DotSpacing, _DotRadius, _DotSmoothing);
    82.                 col = lerp(col, _DotColor, gridDots);
    83.  
    84.                 return col;
    85.             }
    86.             ENDCG
    87.         }
    88.     }
    89. }
    90.  
    I tried doing something like this:


    for(int i = 0; i < 3; i++)
    {
    float gridDots = dots(p, _DotOffset, _DotSpacing, _DotRadius, _DotSmoothing);
    p -= (p * 0.2) * i;
    col = lerp(col, _DotColor, gridDots);
    }

    but it does a strange effect and doesn't offset it "into the mesh" so I guess I'm going to need the camera position in the shader or something?

    Any help would be appreciated.
     
  2. bgolus

    bgolus

    Joined:
    Dec 7, 2012
    Posts:
    12,352
    An infinite mirror effect Rocket League is using is relatively straight forward to achieve. It's just using a basic parallax offset in a loop. Something like this:

    Code (csharp):
    1. // basic parallax offset function
    2. float2 ParallaxOffsetUV(float2 uv, float3 tangentSpaceViewDir, float offsetScale)
    3. {
    4.   float2 uvOffset = tangentSpaceViewDir.xy / tangentSpaceViewDir.z;
    5.   uvOffset *= offsetScale;
    6.   return uv - uvOffset;
    7. }
    8.  
    9. // in the shader
    10. float4 col = 0;
    11. for (int iter=0; iter<_NumLayers; iter++)
    12. {
    13.   float2 layerUV = ParallaxOffsetUV(i.uv, tangentSpaceViewDir, _LayerOffset * float(iter));
    14.   float layerFade = 1.0 - (float(iter) / _NumLayers);
    15.   col += tex2D(_MainTex, layerUV) * layerFade;
    16. }
    The only "tricky bit" is that tangent space view direction. For that you need to pass the world normal, and world tangent from the vertex shader to the fragment shader (and optionally the world bitangent, or reconstruct the bitangent in the fragment shader). This is going to be the same thing you would need to do to support normal mapping btw. Usually you would use those three vectors to construct a rotation matrix to transform the tangent space normal into world space. But instead you apply the transpose matrix to approximate the world to tangent space transform.

    That's a fancy way of saying you do this:
    Code (csharp):
    1. // vertex shader
    2. o.worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1.0)).xyz;
    3. o.worldNormal = UnityObjectToWorldNormal(v.normal);
    4. o.worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
    5. o.worldBitangent = cross(o.worldNormal, o.worldTangent) * v.tangent.w * unity_WorldTransformParams.w;
    6.  
    7. // fragment shader
    8. float3 worldSpaceViewDir = UnityWorldSpaceViewDir(i.worldPos);
    9. float3x3 tbn = float3x3(i.worldTangent, i.worldBitangent, i.worldNormal);
    10. float3 tangentSpaceViewDir = mul(tbn, worldSpaceViewDir);
    Put together it should look something like this:
    Code (csharp):
    1. Shader "Unlit/InfinityMirror"
    2. {
    3.     Properties
    4.     {
    5.         _Color ("Color", Color) = (0.5,0.5,0.5,1)
    6.         [NoScaleOffset] _MainTex ("Texture", 2D) = "white" {}
    7.         _NumLayers ("Number of Layers", Float) = 10
    8.         _LayerOffset ("Layer Offset Scale", Float) = 0.025
    9.     }
    10.     SubShader
    11.     {
    12.         Tags { "RenderType"="Opaque" }
    13.         LOD 100
    14.  
    15.         Pass
    16.         {
    17.             CGPROGRAM
    18.             #pragma vertex vert
    19.             #pragma fragment frag
    20.  
    21.             #include "UnityCG.cginc"
    22.  
    23.             struct v2f
    24.             {
    25.                 float4 vertex : SV_POSITION;
    26.                 float2 uv : TEXCOORD0;
    27.                 float3 worldPos : TEXCOORD1;
    28.                 float3 worldNormal : TEXCOORD2;
    29.                 float3 worldTangent : TEXCOORD3;
    30.                 float3 worldBitangent : TEXCOORD4;
    31.             };
    32.  
    33.             sampler2D _MainTex;
    34.  
    35.             v2f vert (appdata_full v)
    36.             {
    37.                 v2f o;
    38.                 o.vertex = UnityObjectToClipPos(v.vertex);
    39.                 o.uv = v.texcoord;
    40.                 o.worldPos = mul(unity_ObjectToWorld, float4(v.vertex.xyz, 1.0)).xyz;
    41.                 o.worldNormal = UnityObjectToWorldNormal(v.normal);
    42.                 o.worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
    43.                 o.worldBitangent = cross(o.worldNormal, o.worldTangent) * v.tangent.w * unity_WorldTransformParams.w;
    44.                 return o;
    45.             }
    46.  
    47.             float2 ParallaxOffsetUV(float2 uv, float3 tangentSpaceViewDir, float offsetScale)
    48.             {
    49.                 float2 uvOffset = tangentSpaceViewDir.xy / tangentSpaceViewDir.z;
    50.                 uvOffset *= offsetScale;
    51.                 return uv - uvOffset;
    52.             }
    53.  
    54.             float4 _Color;
    55.             float _NumLayers;
    56.             float _LayerOffset;
    57.  
    58.             fixed4 frag (v2f i) : SV_Target
    59.             {
    60.                 float3 worldSpaceViewDir = UnityWorldSpaceViewDir(i.worldPos);
    61.                 float3x3 tbn = float3x3(i.worldTangent, i.worldBitangent, i.worldNormal);
    62.                 float3 tangentSpaceViewDir = mul(tbn, worldSpaceViewDir);
    63.  
    64.                 float4 col = 0;
    65.                 for (int iter=0; iter<_NumLayers; iter++)
    66.                 {
    67.                     float2 layerUV = ParallaxOffsetUV(i.uv, tangentSpaceViewDir, _LayerOffset * float(iter));
    68.                     float layerFade = 1.0 - (float(iter) / _NumLayers);
    69.                     col += tex2D(_MainTex, layerUV) * layerFade;
    70.                 }
    71.                 return col * _Color;
    72.             }
    73.             ENDCG
    74.         }
    75.     }
    76. }
     
    lilacsky824 and CyanSlinky like this.
  3. CyanSlinky

    CyanSlinky

    Joined:
    Feb 27, 2014
    Posts:
    28
    Thank you so much, I really appreciate the in-depth reply!

    I'd also like to thank you for all of your solutions that have helped me in the past. Keep up the good work :D
     
    Last edited: Sep 7, 2022