Search code examples
unity-game-engineshaderhlsl

Should I recalculate vertex position for each pass?


im currently learning HLSL and shader language in general, and I was wondering if I should recalculate position for each pass or if there was a way around because it seems to me like a lot of redundant calculation.

Bellow is an exemple of the problem, in this code I instantiate a lot of tree mesh and calculate a random position for each of them based on there instance ID. But I have to calculate there position both in the forward pass and in the shadow casting pass. Thank you in advance for your time. (+ Any feedback on the code would be highly appreciated)

Shader "Tree Shader"
{
    CGINCLUDE
    #include "UnityCG.cginc"
    #include "UnityLightingCommon.cginc"
    #pragma vertex vertex_shader
    #pragma fragment fragment_shader
    #pragma target 5.0
    uniform float _Radius;
    uniform float _WaveStartHeight;
    uniform int _InstanceCount;
    uniform sampler2D _MainTex;
    uniform float waveAmount;
    ENDCG

        Properties
    {
        _MainTex("Texture", 2D) = "white" {}
    }
        SubShader
    {
        Tags { "IgnoreProjector" = "False" "DisableBatching" = "True" }

        // Forward Rendering Pass
        Pass
        {
            Tags { "LightMode" = "ForwardBase" }
            Cull Off
            CGPROGRAM
            #pragma multi_compile_fwdbase

            struct APPDATA
            {
                half4 vertex : POSITION;
                half2 uv : TEXCOORD0;
                uint instanceID : SV_InstanceID;
                half3 normal : NORMAL;
            };

            struct SHADERDATA
            {
                half4 vertex : SV_POSITION;
                half2 uv : TEXCOORD0;
                fixed4 diff : COLOR0;
            };

            half4 ComputeScreenPos(half4 p)
            {
                half4 o = p * 0.5;
                return half4(o.x + o.w, o.y * _ProjectionParams.x + o.w, p.zw);
            }

            half3 GenerateRandomPosition(fixed instanceID)
            {
                half3 position;
                position.x = frac(sin(dot(half2(instanceID, 12.345), half2(12.345, 67.890))) * 123.456);
                position.y = frac(sin(dot(half2(instanceID, 98.765), half2(43.210, 9.876))) * 654.321);
                position.z = frac(sin(dot(half2(instanceID, 0.123), half2(0.987, 6.543))) * 987.654);
                return position * _Radius;
            }

            SHADERDATA vertex_shader(APPDATA data)
            {
                SHADERDATA vs;
                half3 randomPosition = GenerateRandomPosition(data.instanceID);
                half waveAmount = 0.0;

                if (data.vertex.y >= _WaveStartHeight)
                {
                    half fadeX = 1.0 - abs(data.vertex.x) / (_Radius * 0.5);
                    half fadeZ = 1.0 - abs(data.vertex.z) / (_Radius * 0.5);
                    half fade = max(fadeX, fadeZ);
                    fade = saturate(fade * 2.0 - 1.0);
                    waveAmount = sin(data.instanceID * 0.1 + _Time.y * 2.0) * 0.1 * fade;
                }
                vs.vertex = mul(UNITY_MATRIX_VP, half4(randomPosition.x + waveAmount, 0, randomPosition.z + waveAmount, 0) + data.vertex);
                vs.uv = data.uv;
                half3 worldNormal = UnityObjectToWorldNormal(data.normal);
                half nl = max(0, dot(worldNormal, _WorldSpaceLightPos0.xyz));
                vs.diff = nl * _LightColor0;
                vs.diff.rgb += ShadeSH9(half4(worldNormal, 1));
                return vs;
            }

            half4 fragment_shader(SHADERDATA ps) : SV_Target
            {
                half4 color = tex2D(_MainTex, ps.uv);
                return color;
            }
            ENDCG
        }
        Pass
        {
            Tags { "LightMode" = "ShadowCaster" }
            Cull Off
            ZWrite On
            ZTest LEqual
            Blend Zero One

            CGPROGRAM
            #pragma multi_compile_shadowcaster

            struct APPDATA
            {
                half4 vertex : POSITION;
                uint instanceID : SV_InstanceID;
            };

            struct SHADERDATA
            {
                half4 vertex : SV_POSITION;
            };

            half3 GenerateRandomPosition(fixed instanceID)
            {
                half3 position;
                position.x = frac(sin(dot(half2(instanceID, 12.345), half2(12.345, 67.890))) * 123.456);
                position.y = frac(sin(dot(half2(instanceID, 98.765), half2(43.210, 9.876))) * 654.321);
                position.z = frac(sin(dot(half2(instanceID, 0.123), half2(0.987, 6.543))) * 987.654);
                return position * _Radius;
            }

            SHADERDATA vertex_shader(APPDATA data)
            {
                SHADERDATA vs;
                half3 randomPosition = GenerateRandomPosition(data.instanceID);
                half waveAmount = 0.0;
                if (data.vertex.y >= _WaveStartHeight)
                {
                    half fadeX = 1.0 - abs(data.vertex.x) / (_Radius * 0.5);
                    half fadeZ = 1.0 - abs(data.vertex.z) / (_Radius * 0.5);
                    half fade = max(fadeX, fadeZ);
                    fade = saturate(fade * 2.0 - 1.0);
                    waveAmount = sin(data.instanceID * 0.1 + _Time.y * 2.0) * 0.1 * fade;
                }
                vs.vertex = UnityObjectToClipPos(half4(randomPosition.x + waveAmount, 0, randomPosition.z + waveAmount, 0) + data.vertex);
                return vs;
            }

            half4 fragment_shader() : SV_Target
            {
                return half4(0, 0, 0, 0);
            }
            ENDCG
        }
    }
}

Solution

  • If you want correct shadows, then the vertex position recaculation is necessary. You can extract the common code into SubShader and wrap it inside CGINCLUDE/ENDCG.