Unity Shader - URP Fog - URP 管线下的雾效

文章目录

  • 参考 LitForwardPass.hlsl
  • 临摹使用 Test/URPFog
  • 只要 Fog_Linear 变体的
  • 效果
  • 问题
    • 修复
  • References


管线:URP
URP:7.7.1
unity:2019.4.30f1


参考 LitForwardPass.hlsl

LitForwardPass.hlsl 在:#include Packages/com.unity.render-pipelines.universal/Shaders/LitForwardPass.hlsl

我将此文件都全放到这吧,在应用的地方,我都会对应的行的尾部加上 : // jave.lin fog 的标记,便于阅读

#ifndef UNIVERSAL_FORWARD_LIT_PASS_INCLUDED
#define UNIVERSAL_FORWARD_LIT_PASS_INCLUDED

#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

struct Attributes
{
    float4 positionOS   : POSITION;
    float3 normalOS     : NORMAL;
    float4 tangentOS    : TANGENT;
    float2 texcoord     : TEXCOORD0;
    float2 lightmapUV   : TEXCOORD1;
    UNITY_VERTEX_INPUT_INSTANCE_ID
};

struct Varyings
{
    float2 uv                       : TEXCOORD0;
    DECLARE_LIGHTMAP_OR_SH(lightmapUV, vertexSH, 1);

#if defined(REQUIRES_WORLD_SPACE_POS_INTERPOLATOR)
    float3 positionWS               : TEXCOORD2;
#endif

    float3 normalWS                 : TEXCOORD3;
#ifdef _NORMALMAP
    float4 tangentWS                : TEXCOORD4;    // xyz: tangent, w: sign
#endif
    float3 viewDirWS                : TEXCOORD5;

    half4 fogFactorAndVertexLight   : TEXCOORD6; // x: fogFactor, yzw: vertex light // jave.lin fog 在 x 分量存的就是 fog 的因子,在 Varying 数据结构中是未了传给 fragment 阶段使用的数据

#if defined(REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR)
    float4 shadowCoord              : TEXCOORD7;
#endif

    float4 positionCS               : SV_POSITION;
    UNITY_VERTEX_INPUT_INSTANCE_ID
    UNITY_VERTEX_OUTPUT_STEREO
};

void InitializeInputData(Varyings input, half3 normalTS, out InputData inputData)
{
    inputData = (InputData)0;

#if defined(REQUIRES_WORLD_SPACE_POS_INTERPOLATOR)
    inputData.positionWS = input.positionWS;
#endif

    half3 viewDirWS = SafeNormalize(input.viewDirWS);
#ifdef _NORMALMAP
    float sgn = input.tangentWS.w;      // should be either +1 or -1
    float3 bitangent = sgn * cross(input.normalWS.xyz, input.tangentWS.xyz);
    inputData.normalWS = TransformTangentToWorld(normalTS, half3x3(input.tangentWS.xyz, bitangent.xyz, input.normalWS.xyz));
#else
    inputData.normalWS = input.normalWS;
#endif

    inputData.normalWS = NormalizeNormalPerPixel(inputData.normalWS);
    inputData.viewDirectionWS = viewDirWS;

#if defined(REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR)
    inputData.shadowCoord = input.shadowCoord;
#elif defined(MAIN_LIGHT_CALCULATE_SHADOWS)
    inputData.shadowCoord = TransformWorldToShadowCoord(inputData.positionWS);
#else
    inputData.shadowCoord = float4(0, 0, 0, 0);
#endif

    inputData.fogCoord = input.fogFactorAndVertexLight.x; // jave.lin fog 将 fogCoord 取出
    inputData.vertexLighting = input.fogFactorAndVertexLight.yzw;
    inputData.bakedGI = SAMPLE_GI(input.lightmapUV, input.vertexSH, inputData.normalWS);
}

///
//                  Vertex and Fragment functions                            //
///

// Used in Standard (Physically Based) shader
Varyings LitPassVertex(Attributes input)
{
    Varyings output = (Varyings)0;

    UNITY_SETUP_INSTANCE_ID(input);
    UNITY_TRANSFER_INSTANCE_ID(input, output);
    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);

    VertexPositionInputs vertexInput = GetVertexPositionInputs(input.positionOS.xyz);

    // normalWS and tangentWS already normalize.
    // this is required to avoid skewing the direction during interpolation
    // also required for per-vertex lighting and SH evaluation
    VertexNormalInputs normalInput = GetVertexNormalInputs(input.normalOS, input.tangentOS);
    float3 viewDirWS = GetCameraPositionWS() - vertexInput.positionWS;
    half3 vertexLight = VertexLighting(vertexInput.positionWS, normalInput.normalWS);
    half fogFactor = ComputeFogFactor(vertexInput.positionCS.z); // jave.lin fog 根据 clip space z 来计算 fog 因子,ComputeFogFactor 函数在:#include "Packages/com.unity.render-pipeline.universal/ShaderLibrary/Core.hlsl" 中

    output.uv = TRANSFORM_TEX(input.texcoord, _BaseMap);

    // already normalized from normal transform to WS.
    output.normalWS = normalInput.normalWS;
    output.viewDirWS = viewDirWS;
#ifdef _NORMALMAP
    real sign = input.tangentOS.w * GetOddNegativeScale();
    output.tangentWS = half4(normalInput.tangentWS.xyz, sign);
#endif

    OUTPUT_LIGHTMAP_UV(input.lightmapUV, unity_LightmapST, output.lightmapUV);
    OUTPUT_SH(output.normalWS.xyz, output.vertexSH);

    output.fogFactorAndVertexLight = half4(fogFactor, vertexLight); // jave.lin fog 打包放入 pack4 的 x 分量

#if defined(REQUIRES_WORLD_SPACE_POS_INTERPOLATOR)
    output.positionWS = vertexInput.positionWS;
#endif

#if defined(REQUIRES_VERTEX_SHADOW_COORD_INTERPOLATOR)
    output.shadowCoord = GetShadowCoord(vertexInput);
#endif

    output.positionCS = vertexInput.positionCS;

    return output;
}

// Used in Standard (Physically Based) shader
half4 LitPassFragment(Varyings input) : SV_Target
{
    UNITY_SETUP_INSTANCE_ID(input);
    UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);

    SurfaceData surfaceData;
    InitializeStandardLitSurfaceData(input.uv, surfaceData);

    InputData inputData;
    InitializeInputData(input, surfaceData.normalTS, inputData);

    half4 color = UniversalFragmentPBR(inputData, surfaceData.albedo, surfaceData.metallic, surfaceData.specular, surfaceData.smoothness, surfaceData.occlusion, surfaceData.emission, surfaceData.alpha);

    color.rgb = MixFog(color.rgb, inputData.fogCoord); // jave.lin fog 最后再 fragment 阶段应用 fog 因子,至于 为何叫:fogCoord ,估计是受 Built-in RP 的命名阴影,MixFog 函数同样在:#include "Packages/com.unity.render-pipeline.universal/ShaderLibrary/Core.hlsl"
    color.a = OutputAlpha(color.a, _Surface);

    return color;
}

#endif


临摹使用 Test/URPFog

仅看注释中带有 // jave.lin fog 的内容

// jave.lin 2021/10/16
// 测试 URP 自带的 Fog

Shader "Test/URPFog"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100
        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_fog // jave.lin fog 变体 keyword 需要
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" // jave.lin fog ComputFogFactor, MixFog 等,雾效相关的 API 在里面有
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };
            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                real fogFactor : TEXCOORD1; // jave.lin fog 雾因子,或是强度
            };
            CBUFFER_START(UnityPerMaterial)
                float4 _MainTex_ST;
            CBUFFER_END
            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = TransformObjectToHClip(v.vertex.xyz);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                o.fogFactor = ComputeFogFactor(o.vertex.z); // jave.lin fog 根据 clip space z 来计算 fog factor
                return o;
            }
            half4 frag (v2f i) : SV_Target
            {
                half4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv);
                col.rgb = MixFog(col.rgb, i.fogFactor); // jave.lin fog 根据 fogFactor lerp 混合 src color & fog color
                return col;
            }
            ENDHLSL
        }
    }
}


只要 Fog_Linear 变体的

因为我们项目特殊性,并不需要其他的 FOG_EXP, FOG_EXP2, 只要 FOG_LINEAR ,一切都是为了减少变体,如果依靠:Editor/Project Settings…/Graphics 中的 Instancing Variants 设置为:Strip Unused 的方式,还是不够狠,还是会有不必要的变体,如下:

Unity Shader - URP Fog - URP 管线下的雾效_第1张图片

变体数量最好的控制方式就是尽量不使用 内置的 keyword 定义方式,如:#pragma multi_compile_fog,这个内置的编译指令会被编译成 #pragma multi_compile _ FOG_LINEAR FOG_EXP FOG_EXP2 ,包含了三个变体,要是和其他变体组合一起,变体数量就会很多

下面的 GIF 是演示使用 #pragma multi_compile_fog 的方式,变体 keyword 有 3 个,而且可以看到变体虽然 strip 了,但还是有 2 个变体
Unity Shader - URP Fog - URP 管线下的雾效_第2张图片

但是如果我们 自己用 #define 的方式,variants 就可以删减了:

            #define FOG_LINEAR // 我们可以使用明文声明的方式来确定变体
            //#pragma multi_compile_fog // jave.lin fog 变体 keyword 需要,这里注释掉,不用这种方式

Unity Shader - URP Fog - URP 管线下的雾效_第3张图片


效果

Unity Shader - URP Fog - URP 管线下的雾效_第4张图片


问题

但是从上面的效果图中,可以看到,地表的 frag 的 fog 效果不对,甚至部分地表面的 frag 比 圆柱体 frag 离屏幕更近,但是 fog 却更浓,所以此算法是有 BUG的

如下:
Unity Shader - URP Fog - URP 管线下的雾效_第5张图片

Unity Shader - URP Fog - URP 管线下的雾效_第6张图片


修复

将 factor 放到 frag 阶段去计算即可,shader 如下:

// jave.lin 2021/10/16
// 测试 URP 自定义变体的 Fog

Shader "Test/CustomFog_InterpolateFactorInFrag"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100
        Pass
        {
            HLSLPROGRAM
            #define FOG_LINEAR // 我们可以使用明文声明的方式来确定变体
            //#pragma multi_compile_fog // jave.lin fog 变体 keyword 需要,这里注释掉,不用这种方式
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" // jave.lin fog ComputFogFactor, MixFog 等,雾效相关的 API 在里面有
            CBUFFER_START(UnityPerMaterial)
                float4 _MainTex_ST;
            CBUFFER_END
            #pragma vertex vert
            #pragma fragment frag
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };
            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };
            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = TransformObjectToHClip(v.vertex.xyz);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                return o;
            }
            half4 frag(v2f i) : SV_Target
            {
                float csz = i.vertex.z * i.vertex.w; // jave.lin fog 因为 vs 阶段后 还有一个 vs post 会处理 vertex data 的 perspective dive 的透视除法,所以这里需要将除去的值重新还原到 clip space z
                real fogFactor = ComputeFogFactor(csz); // jave.lin fog 根据 clip space z 来计算 fog factor
                half4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv);
                col.rgb = MixFog(col.rgb, fogFactor); // jave.lin fog 根据 fogFactor lerp 混合 src color & fog color
                return col;
            }
            ENDHLSL
        }
    }
}

效果就对了,如下:
Unity Shader - URP Fog - URP 管线下的雾效_第7张图片


References

  • Unity URP Shader 支持内置雾效

你可能感兴趣的:(unity,unity-shader,unity,unity,URP,Fog,unity,URP,雾效)