Unity HLSL shader 基础实现

常用

  • Transform

    • TransformObjectToHClip()
    • TransformObjectToWorldNormal
    • TransformWorldToShadowCoord(float3 positionWS):计算阴影坐标
  • Light

    • GetMainLight():获得主光源信息
    • Light GetMainLight(float4 shadowCoord):计算阴影衰减
    • GetAdditionalLightsCount():获取额外光源个数
    • Light GetAdditionalLight(uint i, float3 positionWS):获取额外光源信息
    • Light GetAdditionalLight(uint i, float3 positionWS, half4 shadowMask):获取额外光源信息(带有shadow buffer)
  • Camera

    • _WorldSpaceCameraPos
  • Texture

    • TRANSFORM_TEX(uv, texName):对uv进行平移缩放
    • SAMPLE_TEXTURE2D(texName, samplerName, uv):采样
    • UnpackNormal(tex2D()):get normal in normal map(必须将Texture设为Normal map)
  • Depth

    • float Linear01Depth(float depth, float4 zBufferParam):将depth转换至linear space
  • Buffer

    • _CameraDepthTexture:depth buffer in viewport(类似于UE的scene depth,无视半透明)
    • _CameraColorTexture:color buffer in viewport

Half Lambert

Shader "URP/Stone"
{
// ------------------------------------------------------------------------------------------- Properties ------------------------------------------------------------------------------------
    Properties
    {
        _ColorTint ("Color Tint", Color) = (1,1,1,1)
        _AlbedoTex ("Albedo Tex", 2D) = "white" {}
    }
    
// ------------------------------------------------------------------------------------------- SubShader ------------------------------------------------------------------------------------
    SubShader
    {
// ------------------------------------------------------------------------------------------- Tag ------------------------------------------------------------------------------------
        Tags 
        { 
            "RenderType"="Opaque" 
            "RenderPipeline" = "UniversalRenderPipeline"
        }
        HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

        CBUFFER_START(UnityPerMaterial)
        half4 _ColorTint;
        float4 _AlbedoTex_ST;
        CBUFFER_END

        TEXTURE2D(_AlbedoTex);
        SAMPLER(sampler_AlbedoTex);        
        
        struct VSInput
        {
            float4 positionL : POSITION;
            float3 normalL : NORMAL;
            float2 uv : TEXCOORD0;
        };
        struct PSInput
        {
            float4 positionH : SV_POSITION;
            float2 uv : TEXCOORD0;
            float3 normalW : TEXCOORD1;
        };
        
        ENDHLSL
        
// ------------------------------------------------------------------------------------------- pass 1 ------------------------------------------------------------------------------------
        pass
        {
            Tags
            {
                "LightMode" = "UniversalForward"
            }
            
            HLSLPROGRAM
            #pragma vertex VS
            #pragma fragment PS

            PSInput VS(VSInput vsInput)
            {
                PSInput vsOutput;

                vsOutput.positionH = TransformObjectToHClip(vsInput.positionL);
                vsOutput.normalW = TransformObjectToWorldNormal(vsInput.normalL, true);
                vsOutput.uv = TRANSFORM_TEX(vsInput.uv, _AlbedoTex);

                return vsOutput;
            }

            half4 PS(PSInput psInput) : SV_TARGET
            {
                half4 texDiff = SAMPLE_TEXTURE2D(_AlbedoTex, sampler_AlbedoTex, psInput.uv) * _ColorTint;

                Light mainLight = GetMainLight();
                half3 lightColor = mainLight.color;
                half3 lightDir = normalize(mainLight.direction);
                
                float NoL = saturate(dot(lightDir, psInput.normalW) * 0.5 + 0.5);

                return half4(texDiff * NoL * lightColor, texDiff.a);
            }
            
            ENDHLSL
        }
    }        
}

Blinn Phone

{
    // ------------------------------------------------------------------------------------------- Properties -----------------------------------------------------------------------
    Properties
    {
        _ColorTint("color Tint", Color) = (1, 1, 1, 1)
        _TexAlbedo("Albedo tex", 2D) = "white" {}
        _SpecularColor("Specular Color", Color) = (1, 1, 1, 1)
        _SpecularRange("Specular Range", Range(10, 300)) = 10
    }
    
// ------------------------------------------------------------------------------------------- SubShader ----------------------------------------------------------------------------
    SubShader
    {
        Tags
        {
            "RendererType" = "Opaque"
            "RenderPipeline" = "UniversalRenderPipeline"
        }
        
        HLSLINCLUDE
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

        CBUFFER_START(UnityPerMaterial)
        float4 _ColorTint;
        float4 _TexAlbedo_ST;
        float4 _SpecularColor;
        float _SpecularRange;
        CBUFFER_END

        TEXTURE2D(_TexAlbedo);
        SAMPLER(sampler_TexAlbedo);

        struct VsInput
        {
            float4 positionL : position;
            float3 normalL : NORMAL;
            float2 uv : TEXCOORD0;
        };

        struct PsInput
        {
            float4 positionH : SV_POSITION;
            float3 normalW : NORMAL;
            float3 viewDirW : TEXCOORD1;
            float2 uv : TEXCOORD0;
        };
        
        ENDHLSL
        
// ------------------------------------------------------------------------------------------- pass 1 ------------------------------------------------------------------------------
        Pass
        {
            NAME"Main Pass"
            
            Tags
            {
                "LightMode" = "UniversalForward"
            }
            
            HLSLPROGRAM
            #pragma vertex VS
            #pragma fragment PS

            PsInput VS(VsInput vsInput)
            {
                PsInput psInput;

                psInput.positionH = TransformObjectToHClip(vsInput.positionL);
                psInput.normalW = TransformObjectToWorldNormal(vsInput.normalL, true);
                psInput.viewDirW = normalize(_WorldSpaceCameraPos.xyz - TransformObjectToWorld(vsInput.positionL.xyz));
                psInput.uv = TRANSFORM_TEX(vsInput.uv, _TexAlbedo);

                return psInput;
            }

            half4 PS(PsInput psInput) : SV_TARGET
            {
                half4 texAlbedo = SAMPLE_TEXTURE2D(_TexAlbedo, sampler_TexAlbedo, psInput.uv);

                Light mainLight = GetMainLight();
                half3 lightColor = mainLight.color;
                half3 lightDir = normalize(mainLight.direction);

                half3 halfVectorW = normalize(psInput.viewDirW + lightDir);
                half NoV = dot(psInput.normalW, lightDir) * 0.5 + 0.5;
                half NoH = saturate(dot(psInput.normalW, halfVectorW));

                half3 diffuseColor = texAlbedo * _ColorTint * mainLight.color * NoV;
                half3 specularColor = _SpecularColor * mainLight.color * pow(NoH, _SpecularRange);
                half3 ambient = half3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w);

                return half4(diffuseColor + specularColor + ambient, texAlbedo.a);
            }
            
            ENDHLSL
        }
    }
}

Normal Map

  • 当normal map texture在Unity中被设为"Normal map",此时其中的rgb分量将不再是tangent space下normal的xyz。因为这样Unity可以根据不同平台对normal map进行不同的压缩(只使用其中两个channel),所以尽量使用UnpackNormal()
  • "Create from Grayscale":使用height map生成normal map
    image-20231011224429128

在切线空间计算光照

{
    // ------------------------------------------------------------------------------------------- Properties -----------------------------------------------------------------------
    Properties
    {
        _AlbedoColorTint("Albedo Color Tint", Color) = (1, 1, 1, 1)
        _TexAlbedo("Albedo Tex", 2D) = "white"{}
        
        _SpecularColor("Specular Color", Color) = (1, 1, 1, 1)
        _SpecularRange("Specular Range", Range(1, 200)) = 10
        
        // // "bump" 是untiy内置的normal map,当没有提供Normal map时,untiy会内置的Normal map
        _TexNormal("Normal Tex", 2D) = "bump"{}
        _NormalScale("Normal Scale", float) = 1
    }
    
// ------------------------------------------------------------------------------------------- SubShader ----------------------------------------------------------------------------
    SubShader
    {
        Tags
        {
            "RenderType" = "Opaque"
            "RenderPipeline" = "UniversalRenderPipeline"
        }
        
        HLSLINCLUDE
        
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

        CBUFFER_START(UnityPerMaterial)
        half4 _AlbedoColorTint;
        half4 _SpecularColor;
        half _SpecularRange;
        float _NormalScale;
        float4 _TexNormal_ST;
        float4 _TexAlbedo_ST;
        CBUFFER_END

        TEXTURE2D(_TexAlbedo);
        TEXTURE2D(_TexNormal);
        SAMPLER(sampler_TexAlbedo);
        SAMPLER(sampler_TexNormal);

        struct VSInput
        {
            float4 positionL : position;
            float4 tangentL : TANGENT;
            float3 normalL : NORMAL;
            float4 uv : TEXCOORD0;
        };

        struct PSInput
        {
            float4 positionH : SV_POSITION;
            float4 uv : TEXCOORD0;
            float3 lightDirT : TEXCOORD1;
            float3 viewDirT : TEXCOORD2;
        };
        
        ENDHLSL
// ------------------------------------------------------------------------------------------- pass 1 ------------------------------------------------------------------------------
        Pass
        {
            NAME"Main Pass"
            
            Tags
            {
                "LightMode" = "UniversalForward"
            }
            
            HLSLPROGRAM
            #pragma vertex VS
            #pragma fragment PS

            PSInput VS(VSInput vsInput)
            {
                PSInput psInput;

                psInput.positionH = TransformObjectToHClip(vsInput.positionL);

                psInput.uv.xy = TRANSFORM_TEX(vsInput.uv, _TexAlbedo);
                psInput.uv.zw = TRANSFORM_TEX(vsInput.uv, _TexNormal);

                // 因为叉乘后的垂直方向有两个,vsInput.tangentL.w用于确定垂直方向
                // unity_WorldTransformParams.w该值与scale有关,可能造成镜像,若scale有奇数个为负,则该值为-1;否则为1
                float3 bitTangent = cross(normalize(vsInput.normalL), normalize(vsInput.tangentL.xyz)) * vsInput.tangentL.w * unity_WorldTransformParams.w;
                float3x3 rotation = float3x3(vsInput.tangentL.xyz, bitTangent.xyz, vsInput.normalL);

                psInput.viewDirT = mul(rotation,  TransformWorldToObject(_WorldSpaceCameraPos - TransformObjectToWorld(vsInput.positionL)));
                psInput.lightDirT = mul(rotation, TransformWorldToObject(GetMainLight().direction));

                return psInput;
            }

            half4 PS(PSInput psInput) : SV_TARGET
            {
                const float3 viewDirT = normalize(psInput.viewDirT);
                const float3 lightDirT = normalize(psInput.lightDirT);
                
                const half4 texAlbedo = SAMPLE_TEXTURE2D(_TexAlbedo, sampler_TexAlbedo, psInput.uv.xy);
                half4 packedNormal = SAMPLE_TEXTURE2D(_TexNormal, sampler_TexNormal, psInput.uv.zw);
                half3 tangentNormal;
                tangentNormal.xy = ((packedNormal * 2) - 1) * _NormalScale;
                tangentNormal.z = sqrt(1 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));

                const half3 halfVector = normalize(viewDirT + lightDirT);
                const half NoL = dot(tangentNormal, lightDirT) * 0.5 + 0.5;
                const half NoH = saturate(dot(tangentNormal, halfVector));

                const half3 diffuse = texAlbedo * _AlbedoColorTint * GetMainLight().color * NoL;
                const half3 specular = GetMainLight().color * _SpecularColor * pow(NoH, _SpecularRange);
                half3 ambient = half3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w);

                return half4(diffuse + specular + ambient, texAlbedo.a);
            }
            
            ENDHLSL
        }
    }
}

在世界空间计算光照

{
    // ------------------------------------------------------------------------------------------- Properties ---------------------------------------------------------------------
    Properties
    {
        _AlbedoColorTint("Albedo Color Tint", Color) = (1, 1, 1, 1)
        _TexAlbedo("Albedo Tex", 2D) = "white"{}
        
        _SpecularColor("Specular Color", Color) = (1, 1, 1, 1)
        _SpecularRange("Specular Range", Range(1, 200)) = 10
        
        // // "bump" 是untiy内置的normal map,当没有提供Normal map时,untiy会内置的Normal map
        _TexNormal("Normal Tex", 2D) = "bump"{}
        _NormalScale("Normal Scale", float) = 1
    }
    
    // ------------------------------------------------------------------------------------------- SubShader ----------------------------------------------------------------------
    SubShader
    {
        Tags
        {
            "RenderType" = "Opaque"
            "RenderPipeline" = "UniversalRenderPipeline"
        }
        
        HLSLINCLUDE
        
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

        CBUFFER_START(UnityPerMaterial)
        half4 _AlbedoColorTint;
        half4 _SpecularColor;
        half _SpecularRange;
        float _NormalScale;
        float4 _TexNormal_ST;
        float4 _TexAlbedo_ST;
        CBUFFER_END

        TEXTURE2D(_TexAlbedo);
        TEXTURE2D(_TexNormal);
        SAMPLER(sampler_TexAlbedo);
        SAMPLER(sampler_TexNormal);

        struct VSInput
        {
            float4 positionL : POSITION;
            float4 uv : TEXCOORD0;
            float4 tangentL : TANGENT;
            float3 normalL : NORMAL;
        };

        struct PSInput
        {
            float4 positionH : SV_POSITION;
            float4 uv : TEXCOORD0;
            float4 T2W0: TEXCOORD1;
            float4 T2W1 : TEXCOORD2;
            float4 T2W2 : TEXCOORD3;
        };
        ENDHLSL
// ------------------------------------------------------------------------------------------- pass 1 ------------------------------------------------------------------------------
        Pass
        {
            NAME"Main Pass"
            
            Tags
            {
                "LightMode" = "UniversalForward"
            }
            
            HLSLPROGRAM
            #pragma vertex VS
            #pragma fragment PS

            PSInput VS(VSInput vsInput)
            {
                PSInput psInput;

                psInput.positionH = TransformObjectToHClip(vsInput.positionL);

                psInput.uv.xy = TRANSFORM_TEX(vsInput.uv, _TexAlbedo);
                psInput.uv.zw = TRANSFORM_TEX(vsInput.uv, _TexNormal);

                float3 positionW = TransformObjectToWorld(vsInput.positionL).xyz;
                float3 tangentW = TransformObjectToWorld(vsInput.tangentL).xyz;
                float3 normalW = TransformObjectToWorld(vsInput.normalL);
                // 因为叉乘后的垂直方向有两个,vsInput.tangentL.w用于确定垂直方向
                // unity_WorldTransformParams.w该值与scale有关,可能造成镜像,若scale有奇数个为负,则该值为-1;否则为1
                float3 bitTangentW = cross(tangentW, normalW) * vsInput.tangentL.w * unity_WorldTransformParams.w;

                psInput.T2W0 = float4(tangentW.x, bitTangentW.x, normalW.x, positionW.x);
                psInput.T2W1 = float4(tangentW.y, bitTangentW.y, normalW.y, positionW.y);
                psInput.T2W2 = float4(tangentW.z, bitTangentW.z, normalW.z, positionW.z);

                return psInput;
            }

            half4 PS(PSInput psInput) : SV_TARGET
            {
                float3 positionW = float3(psInput.T2W0.w, psInput.T2W1.w, psInput.T2W2.w);
                float3 lightDirW = normalize(GetMainLight().direction);
                float3 viewDirW = normalize(_WorldSpaceCameraPos - positionW);

                half3 packedNormal = SAMPLE_TEXTURE2D(_TexNormal, sampler_TexNormal, psInput.uv.zw);
                packedNormal.xy = (packedNormal * 2 - 1) * _NormalScale;
                packedNormal.z = sqrt(1 - saturate(dot(packedNormal.xy, packedNormal.xy)));
                half3 packedNormalW = normalize(half3(dot(psInput.T2W0.xyz, packedNormal), dot(psInput.T2W1.xyz, packedNormal), dot(psInput.T2W2.xyz, packedNormal)));

                half3 halfVector = normalize(viewDirW + lightDirW);
                half NoL = dot(packedNormalW, lightDirW) * 0.5 + 0.5;
                half NoH = saturate(dot(packedNormalW, halfVector));

                half4 texAlbedo = SAMPLE_TEXTURE2D(_TexAlbedo, sampler_TexAlbedo, psInput.uv.xy);
                half3 diffuse = texAlbedo * _AlbedoColorTint * GetMainLight().color * NoL;
                half3 specular = _SpecularColor * GetMainLight().color * pow(NoH, _SpecularRange);
                half3 ambient = half3(unity_SHAr.w, unity_SHAg.w, unity_SHAb.w);

                return half4(diffuse + specular + ambient, texAlbedo.a);
            }
            ENDHLSL
        }
    }
}

Ramp Texture

  • 思路:以Texture存储渐变值,再以half lambert作为ramp texture的uv点

  • 需要将warp mode设为clamp,防止采样浮点数精度造成的问题

  • 实现

    {
    // ------------------------------------------------------------------------------------------- Properties ---------------------------------------------------------------------
        Properties
        {
            _ColorTint("Albedo Color Tint", Color) = (1, 1, 1, 1)
            _TexRamp("Albedo Tex", 2D) = "white"{}
        }
        
    // ------------------------------------------------------------------------------------------- SubShader ----------------------------------------------------------------------
        SubShader
        {
                Tags
                {
                    "RenderType" = "Opaque"
                    "RenderPipeline" = "UniversalRenderPipeline"
                }
                
                HLSLINCLUDE
            
                #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
                #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
    
                CBUFFER_START(UnityPerMaterial)
                half4 _ColorTint;
                float4 _TexRamp_ST;
                CBUFFER_END
                
                TEXTURE2D(_TexRamp);
                SAMPLER(sampler_TexRamp);
    
                struct VSInput
                {
                    float4 positionL : POSITION;
                    float3 normalL : NORMAL;
                };
    
                struct PSInput
                {
                    float4 positionH : SV_POSITION;
                    float3 normalW : NORMAL;
                    float3 positionW : TEXCOORD1;
                };
                ENDHLSL
    
    // ------------------------------------------------------------------------------------------- Pass 1 ----------------------------------------------------------------------
    
                Pass
                {
                    NAME"Main Pass"
                    
                    Tags
                    {
                        "LightMode" = "UniversalForward"
                    }
                    
                    HLSLPROGRAM
    
                    #pragma vertex VS
                    #pragma fragment PS
    
                    PSInput VS(VSInput vsInput)
                    {
                        PSInput psInput;
    
                        psInput.positionH = TransformObjectToHClip(vsInput.positionL);
                        psInput.positionW = TransformObjectToWorld(vsInput.positionL);
    
                        psInput.normalW = TransformObjectToWorldNormal(vsInput.normalL);
    
                        return psInput;
                    }
    
                    float4 PS(PSInput psInput) : SV_TARGET
                    {
                        Light mainLight = GetMainLight();
                        half3 lightDirW = normalize(mainLight.direction);
    
                        half3 normalW = normalize(psInput.normalW);
                    
                        half NoL = saturate(dot(normalW, lightDirW) * 0.5 + 0.5);
    
                        half4 texDiffuse = SAMPLE_TEXTURE2D(_TexRamp, sampler_TexRamp, half2(NoL, NoL));
    
                        return texDiffuse * _ColorTint;
                    }
                    ENDHLSL
                }
        }
    }
    

Alpha Blend

  • blend公式:shader color * alpha + buffer color * (1 - alpha)
    image-20231102150059124

    image-20231102151113987

  • 实现

    {
    // ------------------------------------------------------------------------------------------- Properties ---------------------------------------------------------------------
        Properties
        {
            _TexAlbedo("Albedo Tex", 2D) = "white" {}
            _ColorTint("Albedo Color Tint", Color) =  (1, 1, 1, 1)
            _TexAlpha("Alpha Tex", 2D) = "white" {}
        }
    
    // ------------------------------------------------------------------------------------------- SubShader ---------------------------------------------------------------------
        SubShader
        {
            Tags
            {
                "RenderPipeline" = "UniversalRenderPipeline"
                "RenderType" = "Transparent"
                "Queue" = "Transparent"
                "IgnoreProjector" = "True"  //取消贴花的影响
            }
            
            HLSLINCLUDE
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
    
            CBUFFER_START(UnityPerMaterial)
            float4 _ColorTint;
            float4 _TexAlbedo_ST;
            float4 _TexAlpha_ST;
            CBUFFER_END
    
            TEXTURE2D(_TexAlbedo);
            TEXTURE2D(_TexAlpha);
            SAMPLER(sampler_TexAlbedo);
            SAMPLER(sampler_TexAlpha);
    
            struct VSInput
            {
                float4 positionL : position;
                float4 uv : TEXCOORD0;
            };
    
            struct PSInput
            {
                float4 positionH : SV_POSITION;
                float4 uv : TEXCOORD0;
            };
            ENDHLSL
    
    // ------------------------------------------------------------------------------------------- Main Pass ---------------------------------------------------------------------
    
            Pass
            {
                Tags
                {
                    "LightMode" = "UniversalForward"
                }
                
                // shader color * alpha + buffer color * (1 - alpha)
                Blend SrcAlpha OneMinusSrcAlpha
                Zwrite Off
                
                HLSLPROGRAM
                #pragma vertex VS
                #pragma fragment PS
    
                PSInput VS(VSInput vsInput)
                {
                    PSInput psInput;
    
                    psInput.positionH = TransformObjectToHClip(vsInput.positionL);
    
                    psInput.uv.xy = TRANSFORM_TEX(vsInput.uv, _TexAlbedo);
                    psInput.uv.zw = TRANSFORM_TEX(vsInput.uv, _TexAlpha);
    
                    return psInput;
                }
    
                float4 PS(PSInput psInput) : SV_TARGET
                {
                    half4 texAlbedo = SAMPLE_TEXTURE2D(_TexAlbedo, sampler_TexAlbedo, psInput.uv.xy) * _ColorTint;
                    half texAlpha = SAMPLE_TEXTURE2D(_TexAlpha, sampler_TexAlpha, psInput.uv.zw).r;
    
                    return float4(texAlbedo.rgb, texAlpha);
                }
                ENDHLSL
            }
        }
    }
    
  • 效果

    image-20231102155011822

Alpha Test

  • 必要设置

    image-20231102181409746

  • 实现

    {
    // ------------------------------------------------------------------------------------------- Properties ---------------------------------------------------------------------
        Properties
        {
            _TexAlbedo("Albedo Tex", 2D) = "white" {}
            _AlbedoColorTint("Albedo Color Tint", Color) = (1, 1, 1, 1)
            // "_Cutoff"名字固定
            _Cutoff("Cut off", Range(0,1)) = 0
        }
     
    // ------------------------------------------------------------------------------------------- Properties ---------------------------------------------------------------------
        SubShader
        {
            Tags
            {
                "RenderPipeline" = "UniversalRenderPipeline"
                "RenderType" = "TransparentCutout"
                "Queue" = "AlphaTest"
            }
            
            HLSLINCLUDE
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
    
            CBUFFER_START(UnityPerMaterial)
            float4 _TexAlbedo_ST;
            float4 _AlbedoColorTint;
            float _Cutoff;
            CBUFFER_END
    
            TEXTURE2D(_TexAlbedo);
            SAMPLER(sampler_TexAlbedo);
    
            struct VSInput
            {
                float4 positionL : position;
                float2 uv : TEXCOORD0;
                float3 normalL : NORMAL;
            };
    
            struct PSInput
            {
                float4 positionH : SV_POSITION;
                float3 normalW : NORMAL;
                float3 positionW : TEXCOORD1;
                float2 uv : TEXCOORD0;
            };
            ENDHLSL
    
    // ------------------------------------------------------------------------------------------- Main Pass ---------------------------------------------------------------------
            Pass
            {
                Tags
                {
                    "LightMode" = "UniversalForward"
                }
                
                HLSLPROGRAM
                #pragma vertex VS
                #pragma fragment PS
    
                PSInput VS(VSInput vsInput)
                {
                    PSInput VSOutput;
    
                    VSOutput.positionH = TransformObjectToHClip(vsInput.positionL);
                    VSOutput.uv = TRANSFORM_TEX(vsInput.uv, _TexAlbedo);
                    VSOutput.normalW = TransformObjectToWorldNormal(vsInput.normalL);
                    VSOutput.positionW = TransformObjectToWorld(vsInput.positionL);
    
                    return VSOutput;
                }
    
                float4 PS(PSInput psInput) : SV_TARGET
                {
                    float4 texAlbedo = SAMPLE_TEXTURE2D(_TexAlbedo, sampler_TexAlbedo, psInput.uv) * _AlbedoColorTint;
    
                    half3 normalW = normalize(psInput.normalW);
                    
                    Light mainLight = GetMainLight();
                    half3 lightColor = mainLight.color;
                    half3 lightDirW = mainLight.direction;
    
                    float NoL = dot(normalW, lightDirW) * 0.5 + 0.5;
    
                    // 利用clip()进行alpha test
                    clip(texAlbedo.a - _Cutoff - 0.01);
    
                    float3 diffuse = texAlbedo.rgb * _AlbedoColorTint * lightColor * NoL;
    
                    return float4(diffuse, texAlbedo.a);
                }
                ENDHLSL
            }
        }
    }
    

多光源

  • URP中的多光源处理与Buildin里的不同。在Buildin中,在其中一个pass处理主光源,在其他pass中处理其他光源;而在URP中,只在一个pass中循环处理光源——收集所有光照信息,再传输给Pass,由开发者在Pass中决定使用指定光源进行光照,最后叠加光源计算的结果

  • 优点:降低draw call

  • 缺点:打破合批

  • 思路:先计算其余光源的个数,再用其遍历全部灯光(通过GetAdditionalLight(i,WS_Pos)遍历每个光源的struct Light)得到光源方向、颜色、距离衰减、阴影衰减,最后累加

  • 关键函数

    • GetAdditionalLightsCount():返回额外灯光的个数
    • Light GetAdditionalLight(uint i, float3 positionWS):返回struct Light
      • i:额外灯光的id
      • positionWS:世界空间下的顶点坐标
  • 实现

    {
    // ------------------------------------------------------------------------------------------- Properties ---------------------------------------------------------------------
        Properties
        {
            _TexAlbedo("Albedo Tex", 2D) = "white" {}
            _AlbedoColorTint("Albedo Color Tint", Color) = (1, 1, 1, 1)
            _SpecularColor("Specular Color", Color) = (1, 1, 1, 1)
            _Gloss("Gloss", Range(1, 200)) = 20
            [KeywordEnum(OFF, ON)] _MULTIPLE_LIGHT("multiple light", float) = 1
        }
    // ------------------------------------------------------------------------------------------- SubShader ---------------------------------------------------------------------
        SubShader
        {
            Tags
            {
                "RenderType" = "Opaque"
                "RenderPipeline" = "UniversalRenderPipeline"
            }
            LOD 100
            
            HLSLINCLUDE
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
    
            CBUFFER_START(UnityPerMaterial)
            half4 _AlbedoColorTint;
            float4 _TexAlbedo_ST;
            float _Gloss;
            float4 _SpecularColor;
            CBUFFER_END
    
            TEXTURE2D(_TexAlbedo);
            SAMPLER(sampler_TexAlbedo);
    
            struct VSInput
            {
                float4 positionL : POSITION;
                float3 normalL : NORMAL;
                float2 uv : TEXCOORD0;
            };
    
            struct PSInput
            {
                float4 positionH : SV_POSITION;
                float3 positionW : TEXCOORD1;
                float3 normalW : NORMAL;
                float3 viewDirW : TEXCOORD2;
                float2 uv : TEXCOORD0;
            };
            ENDHLSL
    
    // -------------------------------------------------------------------------------------------Main Pass ---------------------------------------------------------------------
            Pass
            {
                Tags
                {
                    "LightMode" = "UniversalForward"
                }
                
                HLSLPROGRAM
                #pragma vertex VS
                #pragma fragment PS
                #pragma shader_feature _MULTIPLE_LIGHT_ON _MULTIPLE_LIGHT_OFF
                #pragma enable_d3d11_debug_symbols
    
                PSInput VS(VSInput vsInput)
                {
                    PSInput VSOutput;
    
                    VSOutput.positionH = TransformObjectToHClip(vsInput.positionL);
                    VSOutput.positionW = TransformObjectToWorld(vsInput.positionL.xyz);
    
                    VSOutput.normalW = TransformObjectToWorldNormal(vsInput.normalL);
                    VSOutput.viewDirW = GetCameraPositionWS() - VSOutput.positionW;
    
                    VSOutput.uv = TRANSFORM_TEX(vsInput.uv, _TexAlbedo);
    
                    return VSOutput;
                }
    
                float4 PS(PSInput psInput) : SV_TARGET
                {
                    half4 texAlbedo = SAMPLE_TEXTURE2D(_TexAlbedo, sampler_TexAlbedo, psInput.uv) * _AlbedoColorTint;
    
                    Light mainLight = GetMainLight();
                    half3 mainLightColor = mainLight.color;
                    half3 mainLightDir = normalize(mainLight.direction);
                    
                    float3 positionW = psInput.positionW;
                    half3 normalW = normalize(psInput.normalW);
                    half3 viewDirW = normalize(psInput.viewDirW);
                    half3 halfVector = normalize(viewDirW + mainLightDir);
    
                    half NoL = saturate(dot(normalW, mainLightDir) * 0.5 + 0.5);
                    half NoH = saturate(dot(normalW, halfVector));
    
                    float3 diffuse = texAlbedo.rgb * mainLightColor * NoL;
                    float3 specular = _SpecularColor * pow(NoH, _Gloss);
                    
                    // calc add lights
                    float3 addColor = float3(0.f, 0.f, 0.f);
                
                    #ifdef _MULTIPLE_LIGHT_ON
                        // 求得其余灯光数量
                        int addLightCounts = GetAdditionalLightsCount();
                        for(int i = 0; i < addLightCounts; ++i)
                        {
                            // 求得某一个灯光数据
                            Light addLight = GetAdditionalLight(i, positionW);
                            half3 addLightDirW = normalize(addLight.direction);
                            half3 addLightHalfVector = normalize(viewDirW + addLightDirW);
                            half addLightNoL = saturate(dot(normalW, addLightDirW) * 0.5 + 0.5);
                            half addLightNoH = saturate(dot(normalW, addLightHalfVector));
    
                            float3 addLightDiffuse = texAlbedo.rgb * addLight.color * addLightNoL * addLight.distanceAttenuation * addLight.shadowAttenuation;
                            float3 addLightSpecular = _SpecularColor * pow(addLightNoH, _Gloss);
                            addColor += addLightDiffuse + addLightSpecular;
                        }
                    #else
                        addColor = (0.f, 0.f, 0.f);
                    #endif
    
                    return float4(diffuse + specular + addColor, 1.f);
                }
                ENDHLSL
            }
        }
    }
    
  • 效果
    image-20231102235527609

主光源阴影

  • 重要函数

    • Light GetMainLight(float4 shadowCoord):计算阴影衰减

      image-20231103212332812

    • TransformWorldToShadowCoord(float3 positionWS):计算阴影坐标image-20231103212539098image-20231103212404883

      image-20231105220148839

  • 实现

    {
    // ------------------------------------------------------------------------------------------- Properties ---------------------------------------------------------------------
        Properties
        {
            _TexAlbedo("Albedo Tex", 2D) = "white" {}
            _AlbedoColorTint("Albedo Color Tint", Color) = (1, 1, 1, 1)
            _SpecularColor("Specular Color", Color) = (1, 1, 1, 1)
            _Gloss("Gloss", Range(10, 300)) = 20
        }
    
    // ------------------------------------------------------------------------------------------- SubShader ---------------------------------------------------------------------
        SubShader
        {
            Tags
            {
                "RenderType" = "Opaque"
                "RenderPipeline" = "UniversalPipeline"
            }
            LOD 100
            
            HLSLINCLUDE
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
    
            CBUFFER_START(UnityPerMaterial)
            half4 _AlbedoColorTint;
            float4 _TexAlbedo_ST;
            float _Gloss;
            float4 _SpecularColor;
            CBUFFER_END
    
            TEXTURE2D(_TexAlbedo);
            SAMPLER(sampler_TexAlbedo);
    
            struct VSInput
            {
                float4 positionL : POSITION;
                float3 normalL : NORMAL;
                float2 uv : TEXCOORD0;
            };
    
            struct PSInput
            {
                float4 positionH : SV_POSITION;
                float3 positionW : TEXCOORD1;
                float3 normalW : NORMAL;
                float3 viewDirW : TEXCOORD2;
                float2 uv : TEXCOORD0;
            };        
            ENDHLSL
    
    // ------------------------------------------------------------------------------------------- Main Pass ---------------------------------------------------------------------            
            Pass
            {
                Tags
                {
                    "LightMode" = "UniversalForward"
                }
                
                HLSLPROGRAM
                #pragma vertex VS
                #pragma fragment PS
                #pragma multi_compile _ _MAIN_LIGHT_SHADOWS    // 计算阴影衰减
                #pragma multi_compile _ _MAIN_LIGHT_SHADOWS_CASCADE    // 得到正确的阴影坐标
                #pragma multi_compile _ _SHADOWS_SOFT  //计算软阴影
    
                PSInput VS(VSInput vsInput)
                {
                    PSInput VSOuput;
    
                    VSOuput.positionH = TransformObjectToHClip(vsInput.positionL);
                    VSOuput.positionW = TransformObjectToWorld(vsInput.positionL);
    
                    VSOuput.normalW = TransformObjectToWorldNormal(vsInput.normalL);
                    VSOuput.viewDirW = GetCameraPositionWS() - VSOuput.positionW;
    
                    VSOuput.uv = TRANSFORM_TEX(vsInput.uv, _TexAlbedo);
    
                    return VSOuput;
                }
    
                float4 PS(PSInput psInput) : SV_TARGET
                {
                    half4 texAlbedo = SAMPLE_TEXTURE2D(_TexAlbedo, sampler_TexAlbedo, psInput.uv) * _AlbedoColorTint;
                    
                    Light mainLight = GetMainLight(TransformWorldToShadowCoord(psInput.positionW));  // 得到阴影坐标并计算阴影衰减
                    half3 lightColor = mainLight.color;
                    half3 lightDirW = normalize(mainLight.direction);
                    
                    half3 normalW = normalize(psInput.normalW);
                    half3 viewDirW = normalize(psInput.viewDirW);
                    half3 halfVector = normalize(viewDirW + lightDirW);
                    
                    float NoL = saturate(dot(normalW, lightDirW) * 0.5 + 0.5);
                    float NoH = saturate(dot(normalW, halfVector));
    
                    // // 需要乘以shadowAttenuation计算阴影衰减
                    half3 diffuse = texAlbedo * lightColor * mainLight.shadowAttenuation * NoL;
                    half3 specular = _SpecularColor * pow(NoH, _Gloss) * mainLight.shadowAttenuation;
    
                    return float4(diffuse + specular, 1.f);
                }
                ENDHLSL
            }
            // 阴影接收
            UsePass "Universal Render Pipeline/Lit/ShadowCaster" 
        }
    }
    
  • 效果
    image-20231103194716557

多光源阴影

  • 重要函数

    • Light GetAdditionalLight(uint i, float3 positionWS, half4 shadowMask)
      image-20231105220224261

      image-20231105220243149

      image-20231105220303371

      image-20231105220310126

      可以看到想要计算额外光的阴影衰减就需要定义关键字"ADDITIONAL_LIGHT_CALCULATE_SHADOWS"

  • 实现

    {
    // ------------------------------------------------------------------------------------------- Properties ---------------------------------------------------------------------
        Properties
        {
            _TexAlbedo("Albedo Tex", 2D) = "white" {}
            _AlbedoColorTint("Albedo Color Tint", Color) = (1, 1, 1, 1)
            _SpecularColor("Specular Color", Color) = (1, 1, 1, 1)
            _Gloss("Gloss", Range(10, 300)) = 20
            [KeywordEnum(OFF, ON)] _MULTIPLE_LIGHT("Muliptle Light", float) = 0
        }
        
    // ------------------------------------------------------------------------------------------- SubShader ---------------------------------------------------------------------
        SubShader
        {
            Tags
            {
                "RenderType" = "Opaque"
                "RenderPipeline" = "UniversalPipeline"
            }
            LOD 100
            
            HLSLINCLUDE
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
    
            CBUFFER_START(UnityPerMaterial)
            half4 _AlbedoColorTint;
            float4 _TexAlbedo_ST;
            float _Gloss;
            float4 _SpecularColor;
            CBUFFER_END
    
            TEXTURE2D(_TexAlbedo);
            SAMPLER(sampler_TexAlbedo);
    
            struct VSInput
            {
                float4 positionL : POSITION;
                float3 normalL : NORMAL;
                float2 uv : TEXCOORD0;
            };
    
            struct PSInput
            {
                float4 positionH : SV_POSITION;
                float3 positionW : TEXCOORD1;
                float3 normalW : NORMAL;
                float3 viewDirW : TEXCOORD2;
                float2 uv : TEXCOORD0;
            };        
            ENDHLSL
    
    // ------------------------------------------------------------------------------------------- Main Pass ---------------------------------------------------------------------
            Pass
            {
                Tags
                {
                    "LightMode" = "UniversalForward"
                }
                
                HLSLPROGRAM
                #pragma vertex VS
                #pragma fragment PS
                #pragma shader_feature _MULTIPLE_LIGHT_ON _MULTIPLE_LIGHT_OFF
                
                #pragma multi_compile __ _MAIN_LIGHT_SHADOWS    // 计算阴影衰减 
                #pragma multi_compile __ _MAIN_LIGHT_SHADOWS_CASCADE    // 得到正确的阴影坐标
                #pragma multi_compile __ _SHADOWS_SOFT  //计算软阴影
                #pragma multi_compile __ ADDITIONAL_LIGHT_CALCULATE_SHADOWS // 计算额外光的阴影衰减和距离衰减
    
                PSInput VS(VSInput vsInput)
                {
                    PSInput VSOuput;
    
                    VSOuput.positionH = TransformObjectToHClip(vsInput.positionL);
                    VSOuput.positionW = TransformObjectToWorld(vsInput.positionL);
    
                    VSOuput.normalW = TransformObjectToWorldNormal(vsInput.normalL);
                    VSOuput.viewDirW = GetCameraPositionWS() - VSOuput.positionW;
    
                    VSOuput.uv = TRANSFORM_TEX(vsInput.uv, _TexAlbedo);
    
                    return VSOuput;
                }
    
                float4 PS(PSInput psInput) : SV_TARGET
                {
                    half4 texAlbedo = SAMPLE_TEXTURE2D(_TexAlbedo, sampler_TexAlbedo, psInput.uv) * _AlbedoColorTint;
                    
                    half4 shadowUV = TransformWorldToShadowCoord(psInput.positionW);
                    Light mainLight = GetMainLight(shadowUV);  // 得到阴影坐标并计算阴影衰减
                    half3 lightColor = mainLight.color;
                    half3 lightDirW = normalize(mainLight.direction);
    
                    float3 positionW = psInput.positionW;
                    half3 normalW = normalize(psInput.normalW);
                    half3 viewDirW = normalize(psInput.viewDirW);
                    half3 halfVector = normalize(viewDirW + lightDirW);
                    
                    float NoL = saturate(dot(normalW, lightDirW) * 0.5 + 0.5);
                    float NoH = saturate(dot(normalW, halfVector));
    
                    // // 需要乘以shadowAttenuation计算阴影衰减
                    half3 diffuse = texAlbedo * lightColor * mainLight.shadowAttenuation * NoL;
                    half3 specular = _SpecularColor * pow(NoH, _Gloss) * mainLight.shadowAttenuation;
    
                    float3 resultColor = diffuse + specular;
                    #if defined _MULTIPLE_LIGHT_ON
                    int addLightCounts = GetAdditionalLightsCount();
                    for(uint i = 0; i < addLightCounts; ++i)
                    {
                        Light addLight = GetAdditionalLight(i, positionW, half4(1.f, 1.f, 1.f, 1.f));
                        half3 addLightColor = addLight.color;
                        half3 addLightDirW = normalize(addLight.direction);
                        half3 addLightHalfVector = normalize(viewDirW + addLightDirW);
    
                        half addLightNoL = saturate(dot(normalW, addLightDirW) * 0.5 + 0.5);
                        half addLightNoH = saturate(dot(normalW, addLightHalfVector));
    
                        half3 addLightDiffuse = texAlbedo * addLightNoL * addLightColor * addLight.shadowAttenuation * addLight.distanceAttenuation;
                        half3 addLightSpecular = _SpecularColor * pow(addLightNoH, _Gloss) * addLight.shadowAttenuation * addLight.distanceAttenuation;
    
                        resultColor += addLightDiffuse + addLightSpecular;
                    }
                    #endif
                    
    
                    return float4(resultColor, 1.f);
                }
                ENDHLSL
            }
            // 阴影接收
            UsePass "Universal Render Pipeline/Lit/ShadowCaster" 
        }
    }
    
  • 解决SRP Batcher不兼容
    可以看到使用多光源投影会使得SRP Batcher无法工作

    image-20231105220406990

    • 关键点

      • float3 ApplyShadowBias(float3 positionWS, float3 normalWS, float3 lightDirection):定义于"Shadow.hlsl"。用于得到阴影投射的裁剪空间坐标
        image-20231105220456460
      • UNITY_REVERSED_Z:因为dx11/12的深度缓冲,1.0为近平面,0.0为远平面;而dx9/opengl的深度缓存,0.0为近平面,1.0远平面,所以这里需要翻转深度值
        • 原因:提升深度缓冲的精度,减轻z值冲突和提高阴影质量
      • _LightDirection:获取主光源和其余光源的方向
        image-20231105220505791
    • 实现

      {
      // ------------------------------------------------------------------ Properties ------------------------------------------------------------------
          Properties
          {
              _TexAlbedo("Albedo Tex", 2D) = "white" {}
              _AlbedoColorTint("Albedo Color Tint", Color) = (1, 1, 1, 1)
              _SpecularColor("Specular Color", Color) = (1, 1, 1, 1)
              _Gloss("Gloss", Range(10, 300)) = 20
              _Cutoff("Cutoff", Range(0,1)) = 1
              
              [KeywordEnum(OFF, ON)] _MULTIPLE_LIGHT("Muliptle Light", float) = 0
              [KeywordEnum(OFF, ON)] _CUT("CUT", float) = 0
          }
       
      // ------------------------------------------------------------------ SubShader ------------------------------------------------------------------
          SubShader
          {
              Tags
              {
                  "RenderPipeline" = "UniversalPipeline"
              }
              LOD 100
              
              HLSLINCLUDE
              #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
              #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
              #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Shadows.hlsl"
      
              #pragma shader_feature_local _MULTIPLE_LIGHT_ON
              #pragma shader_feature_local _CUT_ON
      
              CBUFFER_START(UnityPerMaterial)
              half4 _AlbedoColorTint;
              float4 _TexAlbedo_ST;
              float _Gloss;
              float4 _SpecularColor;
              float _Cutoff;
              CBUFFER_END
      
              TEXTURE2D(_TexAlbedo);
              SAMPLER(sampler_TexAlbedo);
      
              struct VSInput
              {
                  float4 positionL : POSITION;
                  float3 normalL : NORMAL;
                  float2 uv : TEXCOORD0;
              };
      
              struct PSInput
              {
                  float4 positionH : SV_POSITION;
                  float3 positionW : TEXCOORD1;
                  float3 normalW : NORMAL;
                  float3 viewDirW : TEXCOORD2;
                  float2 uv : TEXCOORD0;
                  half4 shadowUV : TEXCOORD3;
              };        
              ENDHLSL
      
      // ------------------------------------------------------------------ Main Pass ------------------------------------------------------------------
              Pass
              {
                  Tags
                  {
                      "LightMode" = "UniversalForward"
                      "RenderType" = "TransparentCutout"
                      "Queue" = "AlphaTest"
                  }
                  Cull off
                  
                  HLSLPROGRAM
                  #pragma vertex VS
                  #pragma fragment PS
                  
                  #pragma multi_compile __ _MAIN_LIGHT_SHADOWS    // 计算阴影衰减 
                  #pragma multi_compile __ _MAIN_LIGHT_SHADOWS_CASCADE    // 得到正确的阴影坐标
                  #pragma multi_compile __ _SHADOWS_SOFT  //计算软阴影
                  #pragma multi_compile __ ADDITIONAL_LIGHT_CALCULATE_SHADOWS // 计算额外光的阴影衰减和距离衰减
                  #pragma multi_compile __ _ADDITIONAL_LIGHTS_VERTEX _ADDITIONAL_LIGHTS   //计算阴影投射
      
                  PSInput VS(VSInput vsInput)
                  {
                      PSInput VSOutput;
      
                      VSOutput.positionH = TransformObjectToHClip(vsInput.positionL);
                      VSOutput.positionW = TransformObjectToWorld(vsInput.positionL);
      
                      VSOutput.normalW = TransformObjectToWorldNormal(vsInput.normalL);
                      VSOutput.viewDirW = GetCameraPositionWS() - VSOutput.positionW;
                  
                      VSOutput.shadowUV = TransformWorldToShadowCoord(VSOutput.positionW);
      
                      VSOutput.uv = TRANSFORM_TEX(vsInput.uv, _TexAlbedo);
      
                      return VSOutput;
                  }
      
                  float4 PS(PSInput psInput) : SV_TARGET
                  {
                      half4 texAlbedo = SAMPLE_TEXTURE2D(_TexAlbedo, sampler_TexAlbedo, psInput.uv) * _AlbedoColorTint;
                      #ifdef _CUT_ON
                      clip(texAlbedo.a - _Cutoff);
                      #endif
                      
                      Light mainLight = GetMainLight(psInput.shadowUV);  // 得到阴影坐标并计算阴影衰减
                      half3 lightColor = mainLight.color;
                      half3 lightDirW = normalize(mainLight.direction);
      
                      float3 positionW = psInput.positionW;
                      half3 normalW = normalize(psInput.normalW);
                      half3 viewDirW = normalize(psInput.viewDirW);
                      half3 halfVector = normalize(viewDirW + lightDirW);
                      
                      float NoL = saturate(dot(normalW, lightDirW) * 0.5 + 0.5);
                      float NoH = saturate(dot(normalW, halfVector));
      
                      // // 需要乘以shadowAttenuation计算阴影衰减
                      half3 diffuse = texAlbedo * lightColor * mainLight.shadowAttenuation * NoL;
                      half3 specular = _SpecularColor * pow(NoH, _Gloss) * mainLight.shadowAttenuation;
                      float3 resultColor = diffuse + specular;
      
                      // calc add light
                      #if defined _MULTIPLE_LIGHT_ON
                      int addLightCounts = GetAdditionalLightsCount();
                      for(uint i = 0; i < addLightCounts; ++i)
                      {
                          Light addLight = GetAdditionalLight(i, positionW, half4(1.f, 1.f, 1.f, 1.f));
                          half3 addLightColor = addLight.color;
                          half3 addLightDirW = normalize(addLight.direction);
                          half3 addLightHalfVector = normalize(viewDirW + addLightDirW);
      
                          half addLightNoL = saturate(dot(normalW, addLightDirW) * 0.5 + 0.5);
                          half addLightNoH = saturate(dot(normalW, addLightHalfVector));
      
                          half3 addLightDiffuse = texAlbedo * addLightNoL * addLightColor * addLight.shadowAttenuation * addLight.distanceAttenuation;
                          half3 addLightSpecular = _SpecularColor * pow(addLightNoH, _Gloss) * addLight.shadowAttenuation * addLight.distanceAttenuation;
      
                          resultColor += addLightDiffuse + addLightSpecular;
                      }
                      #endif
                      
      
                      return float4(resultColor, 1.f);
                  }
                  ENDHLSL
              }
              //UsePass "Universal Render Pipeline/Lit/ShadowCaster" 
          
      // ------------------------------------------------------------------ ShadowCaster Pass ------------------------------------------------------------------
              Pass
              {
                  Tags
                  {
                      "LightMode" = "ShadowCaster"
                  }
                  
                  HLSLPROGRAM
                  #pragma vertex VSShadow
                  #pragma fragment PSShadow
      
                  // 获取主光源和其余光源的方向(Unity自动完成赋值)
                  half3 _LightDirection;
      
                  PSInput VSShadow(VSInput vsInput)
                  {
                      PSInput VSOutput;
      
                      VSOutput.uv = TRANSFORM_TEX(vsInput.uv, _TexAlbedo);
      
                      VSOutput.normalW = TransformObjectToWorldNormal(vsInput.normalL);
                      VSOutput.positionW = TransformObjectToWorld(vsInput.positionL);
                      Light mainLight = GetMainLight();
                      
                      VSOutput.positionH = TransformWorldToHClip(ApplyShadowBias(VSOutput.positionW, VSOutput.normalW, _LightDirection));
                      
                      #if UNITY_REVERSED_Z
                      VSOutput.positionH.z = min(VSOutput.positionH.z, VSOutput.positionH.w * UNITY_NEAR_CLIP_VALUE);
                      #else
                      VSOutput.positionH.z = max(VSOutput.positionH.z, VSOutput.positionH.w * UNITY_NEAR_CLIP_VALUE);
                      #endif
      
                      return VSOutput;
                  }
      
                  half4 PSShadow(PSInput psInput) : SV_TARGET
                  {
                      #ifdef _CUT_ON
                      float alpha = SAMPLE_TEXTURE2D(_TexAlbedo, sampler_TexAlbedo, psInput.uv).a;
                      clip(alpha - _Cutoff);
                      #endif
      
                      return 0;
                  }
                  ENDHLSL
              }
          }
      }
      

      image-20231105220515687

FlipbBook

  • 思路

    • frac(floor(_Time.y) / _flipbookCols):对time向下取整得到整数,并处以列数,这样就可以得到每列每个图像出现的时间点(如8列,即0,0.125,0.25,0.375,0.5,0.625,0.75,0.875),再加上第一列的即可psInput.uv.x / _flipbookCols

    • frac(floor(_Time.y / _flipbookCols) / _flipbookRows):因为这里需要考虑上一行是否走完,所以需要_Time.y / _flipbookCols,再除以总行数即可得到当前的行数

    • 动画方向

      因为unity的uv是从下到上,而目前实现的filpbook是从上到下,若不调整会造成动画是反向的,所以需要对flipbook的y进行oneminus(1 - y)

  • 实现

    {
    // ------------------------------------------------------------------ Properties ------------------------------------------------------------------
        Properties
        {
            _TexAlbedo("Alebdo Tex", 2D) = "white" {}
            _AlbedoTint("Albedo Color Tint", Color) = (1, 1, 1, 1)
            // filpbook的行列数
            _flipbookCols("flipbook columns", Range(0,8)) = 8
            _flipbookRows("flipbook rows", Range(0, 8)) = 8
            // 帧率
            _FrameRate("Frame Rate", Range(0, 144)) = 60
        }
     
    // ------------------------------------------------------------------ SubShader ------------------------------------------------------------------   
        SubShader
        {
            Tags
            {
                "RenderPipeline" = "UniversalPipeline"
                // flipbook通常为透明纹理
                "Queue" = "Transparent"
                "RenderType" = "Transparent"
            }
            
            HLSLINCLUDE
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
    
            CBUFFER_START(UnityPerMaterial)
            half4 _AlbedoTint;
            float4 _TexAlbedo_ST;
            half _flipbookCols;
            half _flipbookRows;
            half _FrameRate;
            CBUFFER_END
    
            TEXTURE2D(_TexAlbedo);
            SAMPLER(sampler_TexAlbedo);
            
            ENDHLSL
    
    // ------------------------------------------------------------------ Main Pass ------------------------------------------------------------------ 
            Pass
            {
                Tags
                {
                     "LightMode" = "UniversalForward"
                }
                ZWrite off
                Blend SrcAlpha OneMinusSrcAlpha
                
                HLSLPROGRAM
                #pragma vertex VS
                #pragma fragment PS
    
                struct VSInput
                {
                    float4 positionL : position;
                    float2 uv : TEXCOORD0;
                };
    
                struct PSInput
                {
                    float4 positionH : SV_POSITION;
                    float2 uv : TEXCOORD0;
                };
    
                PSInput VS(VSInput vsInput)
                {
                    PSInput VSOutput;
    
                    VSOutput.positionH = TransformObjectToHClip(vsInput.positionL);
                    VSOutput.uv = TRANSFORM_TEX(vsInput.uv, _TexAlbedo);
    
                    return VSOutput;
                }
    
                float4 PS(PSInput psInput) : SV_TARGET
                {
                    float2 flipbookUV;
    
                    flipbookUV.x = psInput.uv.x / _flipbookCols + frac(floor(_Time.y * _FrameRate) / _flipbookCols);
                    flipbookUV.y = psInput.uv.y / _flipbookRows + 1 - frac(floor(_Time.y * _FrameRate / _flipbookCols) / _flipbookRows);
    
                    return SAMPLE_TEXTURE2D(_TexAlbedo, sampler_TexAlbedo, flipbookUV);
                }
                ENDHLSL
            }
        }
    }
    

    注意:flipbook的纹理类型需要设为"Alpha is Transparency"
    image-20231105220524481

Billboard

  • 思路:主要是确定新的坐标系。推荐一篇简单清晰的https://www.bilibili.com/read/cv6483887/?spm_id_from=333.999.0.0

  • 实现

    {
    // ------------------------------------------------------------------ Properties ------------------------------------------------------------------
        Properties
        {
            _TexAlbedo("Albedo Tex", 2D) = "white" {}
            _AlbedoTint("Albedo Color Tint", Color) = (1, 1, 1, 1)
            _FlipbookCols("Flipbook Columns", Range(0, 8)) = 8
            _FlipbookRows("Flipbook Rows", Range(0, 8)) = 8
            _FrameRate("Frame Rate", Range(0, 144)) = 60
            
            // 是否固定z轴
            [KeywordEnum(LOCK_Z, FREE_Z)] _Z_STAGE("Z_Stage", float) = 1
        }
        
    // ------------------------------------------------------------------ SubShader ------------------------------------------------------------------
        SubShader
        {
            Tags
            {
                "RenderPipeline" = "UniversalRenderPipeline"
                "RenderType" = "Transparent"
                "Queue" = "Transparent"
            }
            
            HLSLINCLUDE
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
    
            CBUFFER_START(UnityPerMaterial)
            half4 _AlbedoTint;
            float4 _TexAlbedo_ST;
            half _FlipbookCols;
            half _FlipbookRows;
            half _FrameRate;
            CBUFFER_END
    
            TEXTURE2D(_TexAlbedo);
            SAMPLER(sampler_TexAlbedo);
            
            ENDHLSL
    
            Pass
            {
                Tags
                {
                    "LightMode" = "UniversalForward"
                }
                ZWrite off
                Blend SrcAlpha OneMinusSrcAlpha
                
                HLSLPROGRAM
                #pragma vertex VS
                #pragma fragment PS
    
                #pragma shader_feature_local _Z_STAGE_LOCK_Z
    
                struct VSInput
                {
                    float4 positionL : position;
                    float2 uv : TEXCOORD0;
                };
    
                struct PSInput
                {
                    float4 positionH : SV_POSITION;
                    float2 uv : TEXCOORD0;
                };
    
                PSInput VS(VSInput vsInput)
                {
                    PSInput VSOutput;
    
                    // 构建新的坐标系
    
                    // newZ
                    // newZ:world space下(0,0,0)到相机坐标的朝向转换至模型空间
                    float3 newZ = TransformWorldToObject(GetCameraPositionWS());
                    // 是否锁定z轴
                    // 不锁定:始终朝向相机
                    // 锁定:z轴固定,无法移动
                    #ifdef _Z_STAGE_LOCK_Z
                    // 以相机投影在水平面的的分量作为newZ
                    newZ.y = 0;
                    #endif
                    newZ = normalize(newZ);
    
                    // newX
                    // 当newZ与oldY(0, 1, 0)不重叠:newX = cross(newZ, oldY)
                    // 当newZ与oldY(0, 1, 0)完全重叠:newX = cross(newZ, oldZ(0, 0, 1))。因为完全重叠时,newX一定与oldZ垂直
                    float3 newX = abs(newZ.y) < 0.99 ? cross(newZ, half3(0.f, 1.f ,0.f)) : cross(newZ, half3(0.f, 0.f, 1.f));
                    newX = normalize(newX);
    
                    // newY
                    float3 newY = normalize(cross(newZ, newX));
    
                    float3x3 billboardMatrix = {newX, newY, newZ};
                    // 左乘相当于乘以矩阵的转置
                    float3 newPosition = mul(vsInput.positionL, billboardMatrix);
    
                    VSOutput.positionH = TransformObjectToHClip(newPosition);
                    VSOutput.uv = TRANSFORM_TEX(vsInput.uv, _TexAlbedo);
    
                    return VSOutput;
                }
    
                float4 PS(PSInput psInput) : SV_TARGET
                {
                    float2 flipbookUV;
    
                    flipbookUV.x = psInput.uv.x / _FlipbookCols + frac(floor(_Time.y * _FrameRate) / _FlipbookCols);
                    flipbookUV.y = psInput.uv.y / _FlipbookRows + 1 - frac(floor(_Time.y * _FrameRate / _FlipbookCols) / _FlipbookRows);
    
                    return SAMPLE_TEXTURE2D(_TexAlbedo, sampler_TexAlbedo, flipbookUV);
                }
                ENDHLSL
            }
        }
    }
    

viewport Color

  • 在build in中,通过grad pass 获取屏幕图像;但在URP中则不同,需要通过URP提供的copy color得到一张名叫_CameraColorTexture的屏幕图像,随后使用"SAMPER()"采样图像

  • 想要使用_CameraColorTexture,需要启用"Opaque Texture"
    image-20231105220532888

  • 获取viewport uv

    在以往Unity以opengl(入门精要)为准,齐次裁剪空间的近平面深度为near,远平面深度为far,深度差为fra - near;但目前untiy以DX11为准,齐次裁剪空间的近平面的深度为near,远平面深度为0,深度差为near。可以看出在DX11下,对Z轴进行反向(提高近远平面的精度)

    vertex shader中获得viewport uv:不能进行透视除法,因此viewport uv为:w * (xy / w * 0.5 + 0.5)。当然对于dx11/12来说,v的方向是相反的,因此需要进行oneminus

    pixel shader中获得viewport uv :psInput.positionH(SV_POSITION).xy / _ScreenParams.xy

  • 实现

    {
    // ------------------------------------------------------------------ Properties ------------------------------------------------------------------
        Properties
        {
            _TexNormal("Normal Tex", 2D) = "bump" {}
            _NormalScale("Normal Scale", Range(0, 5)) = 1
            _Intensity("viewport UV Offset", Range(0, 1000)) = 100
            
            // 在world space 还是tangent space计算normal
            [KeywordEnum(WORLD, TANGENT)] _NORMAL_STAGE("Normal Stage", float) = 1
        }
        
    // ------------------------------------------------------------------ SubShader ------------------------------------------------------------------
        SubShader
        {
            Tags
            {
                "RenderPipeline" = "UniversalPipeline"
                "Queue" = "Transparent"
                "RenderType" = "Transparent"
            }
            
            HLSLINCLUDE
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
    
            CBUFFER_START(UnityPerMaterial)
            float4 _TexNormal_ST;
            half _NormalScale;
            half _Intensity;
            CBUFFER_END
    
            // 非本shader独有,因此不可以放于常量缓冲区
            float4 _CameraColorTexture_TexelSize;
            TEXTURE2D(_TexNormal);
            SAMPLER(sampler_TexNormal);
            SAMPLER(_CameraColorTexture);
            
            ENDHLSL
    
    // ------------------------------------------------------------------ Main Pass ------------------------------------------------------------------
            Pass
            {
                Tags
                {
                    "LightMode" = "UniversalForward"
                }
                
                HLSLPROGRAM
                #pragma vertex VS
                #pragma fragment PS
    
                #pragma shader_feature_local _NORMAL_STAGE_WORLD
    
                struct VSInput
                {
                    float4 positionL : position;
                    float4 tangentL : TANGENT;
                    float3 normalL : NORMAL;
                    float2 uv : TEXCOORD0;
                };
    
                struct PSInput
                {
                    float4 positionH : SV_POSITION;
                    float4 tangentW : TANGENT;
                    float4 bitTangentW : TEXCOORD1;
                    float4 normalW : NORMAL;
                    float2 uv : TEXCOORD0;
                };
    
                PSInput VS(VSInput vsInput)
                {
                    PSInput vsOutput;
    
                    vsOutput.positionH = TransformObjectToHClip(vsInput.positionL);
                    vsOutput.normalW.xyz = TransformObjectToWorldNormal(vsInput.normalL);
                    vsOutput.tangentW.xyz = TransformObjectToWorldDir(vsInput.tangentL);
                    vsOutput.bitTangentW.xyz = cross(vsOutput.normalW, vsOutput.tangentW.xyz) * vsInput.tangentL.w * unity_WorldTransformParams.w;
    
                    float3 positionW = TransformObjectToWorld(vsInput.positionL);
                    vsOutput.tangentW.w = positionW.x;
                    vsOutput.bitTangentW.w = positionW.y;
                    vsOutput.normalW.w = positionW.z;
    
                    vsOutput.uv = TRANSFORM_TEX(vsInput.uv, _TexNormal);
    
                    return vsOutput;
                }
    
                float4 PS(PSInput psInput) : SV_TARGET
                {
                    half4 texNormal = SAMPLE_TEXTURE2D(_TexNormal, sampler_TexNormal, psInput.uv);
    
                    float3 normalT = UnpackNormalScale(texNormal, _NormalScale);
    
                    // 求得viewport的 uv
                    float2 viewportUV = psInput.positionH.xy / _ScreenParams.xy;
    
                    #ifdef _NORMAL_STAGE_WORLD  // 在world space计算normal
                    float3x3 matrixT2W = {psInput.tangentW.xyz, psInput.bitTangentW.xyz, psInput.normalW.xyz};
                    float3 normalW = mul(normalT, matrixT2W);
    
                    // 对viewport uv进行offset
                    float2 viewportBias = normalW.xy * _Intensity * _CameraColorTexture_TexelSize;
                    #else
                    float2 viewportBias = normalT.xy * _Intensity * _CameraColorTexture_TexelSize;
                    #endif
    
                    float4 glassColor = tex2D(_CameraColorTexture, viewportUV + viewportBias);
    
                    return float4(glassColor.xyz, 1.f);
                }
                ENDHLSL
            }
        }
    }
    

    此时大概率会发现在Scene窗口下渲染出的物体是纯黑色的,而在Game窗口下是正常的,个人猜测是因为在获取"_CameraColorTexture"时,半透明物体还没渲染成型,所以是纯黑的,也就是说"_CameraColorTexture"应该是在透明物体渲染后再采样场景

    image-20231105220545058

Viewport Depth

  • 为了获取当前viewport的depth,需要获得一张名为_CameraDepthTexture的深度图并进行采样,这就需要启用"Depth Texture"
    image-20231105220551417

  • 采样深度图后,使用float Linear01Depth(float depth, float4 zBufferParam)将depth转换至linear space
    image-20231105220557711

  • 需要注意_CameraDepthTexture深度图,从近到远,值是从1到0(这是最原始的深度图,因为dx平台下进行了Reversed Z,所以深度是相反);但当转换为线性的01深度,从近到远,值是从01

  • 思路

    • 因为该护盾效果是根据viewport depth实现的,所以需要先获取viewport uv

      // vs 
      vsOutput.positionVP.xy = vsOutput.positionH.w * (vsOutput.positionH.xy / vsOutput.positionH.w * 0.5 + 0.5);
      vsOutput.positionVP.zw = vsOutput.positionH.zw;
      
      // ps
      psInput.positionVP.xy /= psInput.positionVP.w; // 透视除法
      #ifdef UNITY_UV_STARTS_AT_TOP   // 判断opengl,还是dx
      psInput.positionVP.y = 1 - psInput.positionVP.y;
      #endif
      
    • 获取viewport depth(_CameraDepthTexture类似于UE的scene depth,采样时忽略半透明物体 )

      SAMPLER(_CameraDepthTexture);
      
      float4 depthColor = tex2D(_CameraDepthTexture, psInput.positionVP.xy);
      float depthBuffer = Linear01Depth(depthColor, _ZBufferParams);  // 转换为线性depth
      
    • 为了实现互盾在模型上方(不被模型覆盖)的效果,且护盾与模型的交界处显得非常明显还需要获取model depth(此处modelDepth类似于pixel depth,采样时并没有忽略半透明物体)

      float modelDepth = psInput.positionH.z;
      modelDepth = Linear01Depth(modelDepth, _ZBufferParams); // 转换为线性depth
      // 使用模型深度 - viewport depth后,再+一个控制深度大小的值。这样可以控制护盾的厚度
      float edge = saturate(modelDepth - depthBuffer + _DepthSize) * 100 * _DepthOffset;
      

      这里edge的思路就是用pixel depth - scene depth,得到的值肯定是负值,再加上_DepthSize使得部分较大的负值大于0,即可实现明显的交界效果
      image-20231105220609659

    • 边缘光
      edge有个缺点,只在和物体接触时,交界轮廓很明显,因此需要加上fresnel效果

      float fresnel = Fresnel(normalW, viewDirW, _FresnelExp) * _DepthOffset;
      

      image-20231105220616607

    • 将edge 和 fresnel相加,作为最终结果的alpha

      return float4(texAlbedo.xyz, edge + fresnel);
      

      image-20231105220626348

    • 添加流光动画效果

      float flow = saturate(pow(1 - abs(frac(psInput.positionW.y * 0.3 - _Time.y * 0.2) - 0.5), 10) * 0.3);
      float4 flowColor = flow * _Emission;
      


      此时实现的重点在于frac(),可以用shader graph看看效果

  • 实现

    {
    // ------------------------------------------------------------------ Properties ------------------------------------------------------------------
        Properties
        {
            _TexAlbedo("Albedo Tex", 2D) = "white" {}
            [HDR]_AlbedoTint("Albedo Tint", Color) = (1, 1, 1, 1)
            _DepthOffset("Depth Offset", Range(0,100)) = 50
            _DepthSize("Depth Size", Range(0, 0.001)) = 0.0005
            _FresnelExp("Fresnel Exp", Range(0, 10)) = 5
            [HDR]_FlowColor("Flow Color", Color) = (1, 1, 1, 1)
            _FlowSpeed("Flow Speed", Range(0, 1)) = 0.5
            _FlowEdge("Flow Edge", Range(0, 1)) = 0.5
        }
        
    // ------------------------------------------------------------------ Properties ------------------------------------------------------------------
        SubShader
        {
            Tags
            {
                "RenderPipeline" = "UniversalRenderPipeline"
                "Queue" = "Transparent"
                "RenderType" = "Transparent"
                
            }
            
            HLSLINCLUDE
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
            #include "Assets/Shader/MyUtil/MyUtil.hlsl" //个人定义的工具函数
    
            CBUFFER_START(UnityPerMaterial)
            float4 _AlbedoTint;
            float4 _TexAlbedo_ST;
            half _DepthOffset;
            half _DepthSize;
            half _FlowEdge;
            half _FlowSpeed;
            float4 _FlowColor;
            half _FresnelExp;
            CBUFFER_END
    
            TEXTURE2D(_TexAlbedo);
            SAMPLER(sampler_TexAlbedo);
            // 采样viewport的深度图
            SAMPLER(_CameraDepthTexture);
            ENDHLSL
    
            Pass
            {
                Tags
                {
                    "LightMode" = "UniversalForward"
                }
                Blend SrcAlpha OneMinusSrcAlpha
                
                HLSLPROGRAM
                #pragma vertex VS
                #pragma fragment PS
    
                struct VSInput
                {
                    float4 positionL : position;
                    float4 normalL : NORMAL;
                    float2 uv : TEXCOORD0;
                };
    
                struct PSInput
                {
                    float4 positionH : SV_POSITION;
                    float4 positionVP : TEXCOORD2;
                    float3 normalW : NORMAL;
                    float2 uv : TEXCOORD0;
                    float3 positionW : TEXCOORD1;
                };
    
                PSInput VS(VSInput vsInput)
                {
                    PSInput vsOutput;
    
                    vsOutput.uv = TRANSFORM_TEX(vsInput.uv, _TexAlbedo);
    
                    vsOutput.positionH = TransformObjectToHClip(vsInput.positionL);
                    vsOutput.positionW = TransformObjectToWorld(vsInput.positionL).xyz;
                    vsOutput.normalW = TransformObjectToWorldNormal(vsInput.normalL).xyz;
                    // calc viewport position,但xy不能进行透视除法, 因为viewport只关心xy,所以zw不变
                    vsOutput.positionVP.xy = vsOutput.positionH.xy * 0.5 + 0.5 * float2(vsOutput.positionH.w, vsOutput.positionH.w);
                    vsOutput.positionVP.zw = vsOutput.positionH.zw;
    
                    return vsOutput;
                }
    
                float4 PS(PSInput psInput) : SV_TARGET
                {
                    half3 viewDirW = normalize(GetCameraPositionWS() - psInput.positionW);
                    half3 normalW = normalize(psInput.normalW);
                    
                    float2 uv = psInput.uv;
                    half4 texAlbedo = SAMPLE_TEXTURE2D(_TexAlbedo, sampler_TexAlbedo, uv) * _AlbedoTint;
    
                    // calc viewport uv
                    psInput.positionVP.xy /= psInput.positionVP.w; // 透视除法
                    #ifdef UNITY_UV_STARTS_AT_TOP   // 判断opengl,还是dx
                    psInput.positionVP.y = 1 - psInput.positionVP.y;
                    #endif
    
                    // Fresnel
                    float fresnel = Fresnel(normalW, viewDirW, _FresnelExp) * _DepthOffset;
                    
                    // calc buffer depth
                    float4 depthColor = tex2D(_CameraDepthTexture, psInput.positionVP.xy);
                    float depthBuffer = Linear01Depth(depthColor, _ZBufferParams);  // 转换为线性depth
    
                    // calc 目标模型 depth
                    float modelDepth = psInput.positionH.z;
                    modelDepth = Linear01Depth(modelDepth, _ZBufferParams); // 转换为线性depth
                    float edge = saturate(modelDepth - depthBuffer + _DepthSize) * 100 * _DepthOffset;
                    
                    // 扫光
                    float flow = saturate(pow(1 - abs(frac(psInput.positionW.y * 0.3 - _Time.y * _FlowSpeed) - _FlowEdge), 10) * 0.3);
                    float4 flowColor = flow * _FlowColor;
                    
                    return float4(flowColor.rgb + texAlbedo.rgb, edge + fresnel + flowColor.a);
                }
                ENDHLSL
            }
        }
        
    }
    
  • 效果

场景反射

  • 在unity中常用的反射有三种:天空盒(sky box)、反射探针(light probe)、屏幕空间反射。在这里介绍前面两种的shader实现方式(实现方式是相同的),屏幕空间反射会放到新的一篇进行详细介绍

  • 天空盒和反射探针的实现原理类似,天空盒是采样一张Texture;而反射探针是使用球谐函数对一张Texture进行编码(以球谐函数的方式存储,因为纹理开销较大)

  • 天空盒
    准备一个sky box的material,Shader选择"Cubemap",Cubemap Texture一定要使用HDRI格式的Texture,否则效果不理想

    image-20231111234107450

    依次点击"Window"->"Rendering"->"Lighting"->"Environment"->"Sky box Material",选择刚刚准备好的sky box material
    image-20231111234301884

  • 反射探针

    • 为什么需要反射探针?

      当场景愈来愈复杂,天空盒给与的间接光效果是不理想的,有一种好用且开销很低的方式就是反射探针,如下图所示,每个黄色小点表示的就是反射探针,它存储了该点的环境光信息
      An extremely simple scene showing light probes placed around two cubes

    • 什么是反射探针

      小伙伴可能会认为反射探针其实就是一个小型的skybox,这个想法对但不完全对。因为为了达到理想的间接光效果,很有可能需要用到许许多多的反射探针,每个反射探针都存储一张Texture,那开销就太大了!所以反射探针对此进行了优化,也就是球谐函数(这个很复杂,可以看看这https://mp.weixin.qq.com/s/dW6Kz_jyS503QTtLnyK6og)

      简单来说,球谐函数可以对环境光Texture进行编码,记录函数的参数,Bake时使用傅里叶展开还原(因为没有什么时傅里叶展开画不出来的,正余弦越多图像越精确)这一Texture。采用球谐函数的另一个理由是,环境光的精度要求不是那么高,无需保存很多参数,例如在Unity中,球谐函数只采用了九个球面正余弦函数,就可以满足低频环境光照的需求
      image-20231112001000411

    • 创建反射探针
      image-20231112001222265

    • 反射探针有一定的范围限制,只有在这个范围内的物体才会反射

      image-20231112001323229

    • 在这里反射探针类型以"Baked"为例

      灯光的"Mode"设为"Baked"
      image-20231112004157406

      将需要烘培的物体设为"Static"(因为反射探针只反射静态物体)
      image-20231112004242725

      对于需要烘培的物体,设置其"Reflection Probes"为"Blend Probe"

      image-20231112004349785

  • 重要函数

    • SAMPLE_TEXTURECUBE_LOD:采样环境光

      // 通过unity_SpecCube0 获取相应的cube map
      // URP 定义
      TEXTURECUBE(unity_SpecCube0);
      SAMPLER(samplerunity_SpecCube0);
      
      // 获取cube map后对其进行采样
      // lod:mipmap级别
      // URP 定义
      #define SAMPLE_TEXTURECUBE_LOD(textureName, samplerName, coord3, lod)                    PLATFORM_SAMPLE_TEXTURECUBE_LOD(textureName, samplerName, coord3, lod)
      
  • 实现

    Shader "Custom/Reflect"
    {
        Properties
        {
            // // 采样的Texture Mipmap Level(Unity中Mipmap有10个Level)
            [IntRange]_MipMapLevel("Skybox MipMap Level", Range(0, 9)) = 0
        }
        
        SubShader
        {
            Tags
            {
                "Pipeline" = "UniversalPipeline"
                "RenderType" = "Opaque"
            }
            LOD 100
            
            HLSLINCLUDE
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
    
            CBUFFER_START(UnityPerMaterial)
            int _MipMapLevel;
            CBUFFER_END
    
            struct VSInput
            {
                float4 positionL : POSITION;
                float4 normalL : NORMAL;
            };
    
            struct PSInput
            {
                float4 positionH : SV_POSITION;
                float3 positionW : TEXCOORD0;
                float3 normalW : NORMAL;
            };
            ENDHLSL
    
            Pass
            {
                Tags
                {
                    "LightMode" = "UniversalForward"
                }
                
                HLSLPROGRAM
                #pragma vertex VS
                #pragma fragment PS
    
                PSInput VS(VSInput vsInput)
                {
                    PSInput vsOutput;
    
                    vsOutput.positionH = TransformObjectToHClip(vsInput.positionL.xyz);
                    vsOutput.normalW = normalize(TransformObjectToWorldNormal(vsInput.normalL.xyz));
                    vsOutput.positionW = TransformObjectToWorld(vsInput.positionL.xyz);
    
                    return vsOutput;
                }
    
                half4 PS(PSInput psInput) : SV_TARGET
                {
                    half4 outputColor = 0.f;
    
                    float3 viewDirW = normalize(GetCameraPositionWS() - psInput.positionW);
                    float3 normalW = psInput.normalW;
                    float3 reflectDirW = reflect(-viewDirW, normalW);
    
                    half4 SkyBoxColor = SAMPLE_TEXTURECUBE_LOD(unity_SpecCube0, samplerunity_SpecCube0, reflectDirW, _MipMapLevel);
                    
                    outputColor += SkyBoxColor;
                    
                    return outputColor;
                }
                ENDHLSL
            }
        }
    }
    
    

    image-20231111234836738

reference

https://www.bilibili.com/read/cv14791374/

https://space.bilibili.com/5863867

https://juejin.cn/post/7105038680091263007

https://mp.weixin.qq.com/s/dW6Kz_jyS503QTtLnyK6og

https://docs.unity3d.com/Manual/LightProbes.html

https://www.jianshu.com/p/c7d727b5f38a

posted @ 2023-11-05 22:08  爱莉希雅  阅读(50)  评论(0编辑  收藏  举报