Unity Standard Shader 解析(二)之ForwardAdd(标准版)

一、ForwardAdd

        //  Additive forward pass (one light per pass)
        Pass
        {
    
    
            Name "FORWARD_DELTA"
            Tags {
    
     "LightMode" = "ForwardAdd" }
            Blend [_SrcBlend] One
            Fog {
    
     Color (0,0,0,0) } // in additive pass fog should be black
            ZWrite Off
            ZTest LEqual

            CGPROGRAM
            #pragma target 3.0

            // -------------------------------------


            #pragma shader_feature_local _NORMALMAP
            #pragma shader_feature_local _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON
            #pragma shader_feature_local _METALLICGLOSSMAP
            #pragma shader_feature_local_fragment _SMOOTHNESS_TEXTURE_ALBEDO_CHANNEL_A
            #pragma shader_feature_local_fragment _SPECULARHIGHLIGHTS_OFF
            #pragma shader_feature_local_fragment _DETAIL_MULX2
            #pragma shader_feature_local _PARALLAXMAP

            #pragma multi_compile_fwdadd_fullshadows
            #pragma multi_compile_fog
            // Uncomment the following line to enable dithering LOD crossfade. Note: there are more in the file to uncomment for other passes.
            //#pragma multi_compile _ LOD_FADE_CROSSFADE

            #pragma vertex vertAdd
            #pragma fragment fragAdd
            #include "UnityStandardCoreForward.cginc"

            ENDCG
        }

引用了UnityStandardCoreForward.cginc中的vertAddfragAdd

以下是UnityStandardCoreForward.cginc的源码

// Unity built-in shader source. Copyright (c) 2016 Unity Technologies. MIT license (see license.txt)

#ifndef UNITY_STANDARD_CORE_FORWARD_INCLUDED
#define UNITY_STANDARD_CORE_FORWARD_INCLUDED

#if defined(UNITY_NO_FULL_STANDARD_SHADER)
#   define UNITY_STANDARD_SIMPLE 1
#endif

#include "UnityStandardConfig.cginc"

#if UNITY_STANDARD_SIMPLE
    #include "UnityStandardCoreForwardSimple.cginc"
    VertexOutputBaseSimple vertBase (VertexInput v) {
    
     return vertForwardBaseSimple(v); }
    VertexOutputForwardAddSimple vertAdd (VertexInput v) {
    
     return vertForwardAddSimple(v); }
    half4 fragBase (VertexOutputBaseSimple i) : SV_Target {
    
     return fragForwardBaseSimpleInternal(i); }
    half4 fragAdd (VertexOutputForwardAddSimple i) : SV_Target {
    
     return fragForwardAddSimpleInternal(i); }
#else
    #include "UnityStandardCore.cginc"
    VertexOutputForwardBase vertBase (VertexInput v) {
    
     return vertForwardBase(v); }
    //------关键代码--------
    VertexOutputForwardAdd vertAdd (VertexInput v) {
    
     return vertForwardAdd(v); }
    half4 fragBase (VertexOutputForwardBase i) : SV_Target {
    
     return fragForwardBaseInternal(i); }
     //------关键代码--------
    half4 fragAdd (VertexOutputForwardAdd i) : SV_Target {
    
     return fragForwardAddInternal(i); }
#endif

#endif // UNITY_STANDARD_CORE_FORWARD_INCLUDED

UNITY_STANDARD_SIMPLE 属于 ‌简化版前向渲染路径‌ 的标识符,指令区分两种实现:

  • 简化版‌:减少复杂的光照计算(如间接光照、高光反射的精细处理),适用于移动端或低性能设备‌
  • 标准版‌:完整支持基于物理的渲染(PBR)特性,包含金属度、粗糙度等完整材质属性计算‌

先看标准版的

查看UnityStandardCore.cginc里的vertForwardAddfragForwardAddInternal

二、vertForwardAdd


// ------------------------------------------------------------------
//  Additive forward pass (one light per pass)
struct VertexOutputForwardAdd
{
    
    
    UNITY_POSITION(pos);
    float4 tex                          : TEXCOORD0;
    float4 eyeVec                       : TEXCOORD1;    // eyeVec.xyz | fogCoord
    float4 tangentToWorldAndLightDir[3] : TEXCOORD2;    // [3x3:tangentToWorld | 1x3:lightDir]
    float3 posWorld                     : TEXCOORD5;
    UNITY_LIGHTING_COORDS(6, 7)

    // next ones would not fit into SM2.0 limits, but they are always for SM3.0+
#if defined(_PARALLAXMAP)
    half3 viewDirForParallax            : TEXCOORD8;
#endif

    UNITY_VERTEX_OUTPUT_STEREO
};

VertexOutputForwardAdd vertForwardAdd(VertexInput v)
{
    
    
    // 设置实例ID,确保正确处理实例化渲染
    UNITY_SETUP_INSTANCE_ID(v);

    // 初始化输出结构体 o
    VertexOutputForwardAdd o;
    UNITY_INITIALIZE_OUTPUT(VertexOutputForwardAdd, o);
    
    // 初始化立体视觉相关的输出
    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

    // 将顶点从对象空间转换到世界空间
    float4 posWorld = mul(unity_ObjectToWorld, v.vertex);

    // 将顶点从对象空间转换到裁剪空间
    o.pos = UnityObjectToClipPos(v.vertex);

    // 计算并设置纹理坐标
    o.tex = TexCoords(v);

    // 计算并设置视角方向(从世界空间相机位置到顶点的世界位置)
    o.eyeVec.xyz = NormalizePerVertexNormal(posWorld.xyz - _WorldSpaceCameraPos);

    // 保存顶点的世界空间位置
    o.posWorld = posWorld.xyz;

    // 将法线从对象空间转换到世界空间
    float3 normalWorld = UnityObjectToWorldNormal(v.normal);

    // 如果启用了切线到世界空间的转换
    #ifdef _TANGENT_TO_WORLD
        // 将切线从对象空间转换到世界空间
        float4 tangentWorld = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w);

        // 创建从切线空间到世界空间的变换矩阵
        float3x3 tangentToWorld = CreateTangentToWorldPerVertex(normalWorld, tangentWorld.xyz, tangentWorld.w);

        // 将变换矩阵存储在输出结构体中
        o.tangentToWorldAndLightDir[0].xyz = tangentToWorld[0];
        o.tangentToWorldAndLightDir[1].xyz = tangentToWorld[1];
        o.tangentToWorldAndLightDir[2].xyz = tangentToWorld[2];
    #else
        // 如果未启用切线到世界空间的转换,则只存储法线
        o.tangentToWorldAndLightDir[0].xyz = 0;
        o.tangentToWorldAndLightDir[1].xyz = 0;
        o.tangentToWorldAndLightDir[2].xyz = normalWorld;
    #endif

    // 传递光照信息以支持阴影接收和光照计算
    UNITY_TRANSFER_LIGHTING(o, v.uv1);

    // 计算光源方向
    float3 lightDir = _WorldSpaceLightPos0.xyz - posWorld.xyz * _WorldSpaceLightPos0.w;

    // 如果不是平行光,则归一化光源方向
    #ifndef USING_DIRECTIONAL_LIGHT
        lightDir = NormalizePerVertexNormal(lightDir);
    #endif

    // 将光源方向存储在输出结构体中
    o.tangentToWorldAndLightDir[0].w = lightDir.x;
    o.tangentToWorldAndLightDir[1].w = lightDir.y;
    o.tangentToWorldAndLightDir[2].w = lightDir.z;

    // 如果启用了视差映射
    #ifdef _PARALLAXMAP
        // 计算切线空间旋转矩阵
        TANGENT_SPACE_ROTATION;

        // 将视图方向从对象空间转换到切线空间
        o.viewDirForParallax = mul(rotation, ObjSpaceViewDir(v.vertex));
    #endif

    // 传递雾效信息
    UNITY_TRANSFER_FOG_COMBINED_WITH_EYE_VEC(o, o.pos);

    // 返回输出结构体
    return o;
}

三、vertForwardBase和vertForwardAdd的区别

vertForwardBasevertForwardAdd 是两个不同的顶点着色器函数,它们分别用于处理基础光照和附加光源的前向渲染路径。

主要区别

  1. 输入输出结构体
    • vertForwardBase:使用 VertexOutputForwardBase 输出结构体,包含更多的信息(如环境光或光照贴图UV)。
    • vertForwardAdd:使用 VertexOutputForwardAdd 输出结构体,主要关注附加光源的计算。
  • VertexOutputForwardBase

    struct VertexOutputForwardBase
    {
          
          
        UNITY_POSITION(pos);
        float4 tex : TEXCOORD0;
        float4 eyeVec : TEXCOORD1;
        float4 tangentToWorldAndPackedData[3] : TEXCOORD2;
        half4 ambientOrLightmapUV : TEXCOORD5;
        UNITY_LIGHTING_COORDS(6, 7)
    #if UNITY_REQUIRE_FRAG_WORLDPOS && !UNITY_PACK_WORLDPOS_WITH_TANGENT
        float3 posWorld : TEXCOORD8;
    #endif
        UNITY_VERTEX_INPUT_INSTANCE_ID
        UNITY_VERTEX_OUTPUT_STEREO
    };
    
  • VertexOutputForwardAdd

    struct VertexOutputForwardAdd
    {
          
          
        UNITY_POSITION(pos);
        float4 tex : TEXCOORD0;
        float4 eyeVec : TEXCOORD1;
        float4 tangentToWorldAndLightDir[3] : TEXCOORD2;
        float3 posWorld : TEXCOORD5;
        UNITY_LIGHTING_COORDS(6, 7)
    #if defined(_PARALLAXMAP)
        half3 viewDirForParallax : TEXCOORD8;
    #endif
        UNITY_VERTEX_OUTPUT_STEREO
    };
    
  1. 世界位置的存储方式

    • vertForwardBase:根据编译条件选择是否打包世界位置数据到切线空间矩阵中。

           #if UNITY_REQUIRE_FRAG_WORLDPOS
               #if UNITY_PACK_WORLDPOS_WITH_TANGENT
                   o.tangentToWorldAndPackedData[0].w = posWorld.x;
                   o.tangentToWorldAndPackedData[1].w = posWorld.y;
                   o.tangentToWorldAndPackedData[2].w = posWorld.z;
               #else
                   o.posWorld = posWorld.xyz;
               #endif
           #endif
      
    • vertForwardAdd:直接存储世界位置

       o.posWorld = posWorld.xyz;
      
  2. 环境光或光照贴图UV

    • vertForwardBase:计算并设置环境光或光照贴图UV。
      o.ambientOrLightmapUV = VertexGIForward(v, posWorld, normalWorld);
      
    • vertForwardAdd:不涉及环境光或光照贴图UV的计算。
  3. 光源方向的计算

    • vertForwardBase:不涉及光源方向的计算。
    • vertForwardAdd:计算光源方向并存储在输出结构体中。
      float3 lightDir = _WorldSpaceLightPos0.xyz - posWorld.xyz * _WorldSpaceLightPos0.w;
      #ifndef USING_DIRECTIONAL_LIGHT
          lightDir = NormalizePerVertexNormal(lightDir);
      #endif
      o.tangentToWorldAndLightDir[0].w = lightDir.x;
      o.tangentToWorldAndLightDir[1].w = lightDir.y;
      o.tangentToWorldAndLightDir[2].w = lightDir.z;
      
  4. 视差映射

    • vertForwardBase:将视图方向转换到切线空间,并存储在输出结构体中。

      #ifdef _PARALLAXMAP
          TANGENT_SPACE_ROTATION;
          half3 viewDirForParallax = mul(rotation, ObjSpaceViewDir(v.vertex));
          o.tangentToWorldAndPackedData[0].w = viewDirForParallax.x;
          o.tangentToWorldAndPackedData[1].w = viewDirForParallax.y;
          o.tangentToWorldAndPackedData[2].w = viewDirForParallax.z;
      #endif
      
    • vertForwardAdd:类似地将视图方向转换到切线空间,但存储方式不同。

      #ifdef _PARALLAXMAP
          TANGENT_SPACE_ROTATION;
          o.viewDirForParallax = mul(rotation, ObjSpaceViewDir(v.vertex));
      #endif
      
  5. 实例ID和立体视觉设置

    • vertForwardBase:包括 UNITY_TRANSFER_INSTANCE_ID(v, o) 的调用,确保实例化渲染的支持。
    • vertForwardAdd:没有 UNITY_TRANSFER_INSTANCE_ID(v, o) 的调用,但两者都初始化了立体视觉相关的输出。

总结

  • vertForwardBase 主要用于基础前向渲染,包含 全局光照(GI)计算,适用于标准渲染路径。
  • vertForwardAdd 主要用于 额外的光照计算(如点光源、聚光灯),没有 GI 计算,但增加了 光照方向计算,适用于 加法混合光照 的场景。
  • vertForwardAdd 额外存储 光源方向,用于非方向光(如点光源、聚光灯)计算,而 vertForwardBase 只处理环境光和主光源。
  • 两者都支持 视差贴图(Parallax Mapping)雾效计算

如果你是实现 基本光照,使用 vertForwardBase,如果你是实现 额外光源的累加计算,使用 vertForwardAdd

四、fragForwardAddInternal

half4 fragForwardAddInternal(VertexOutputForwardAdd i)
{
    
    
    // 应用抖动交叉淡入效果,以减少纹理走样的视觉伪影
    UNITY_APPLY_DITHER_CROSSFADE(i.pos.xy);

    // 设置立体视觉的眼睛索引,确保正确处理立体渲染
    UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);

    // 设置片段着色器所需的各种参数,如颜色、法线等
    FRAGMENT_SETUP_FWDADD(s)

    // 计算当前像素的光照衰减(attenuation)
    UNITY_LIGHT_ATTENUATION(atten, i, s.posWorld);

    // 获取附加光源的方向和衰减值,并生成 UnityLight 结构体
    UnityLight light = AdditiveLight(IN_LIGHTDIR_FWDADD(i), atten);

    // 初始化无间接光的 UnityIndirect 结构体
    UnityIndirect noIndirect = ZeroIndirect();

    // 使用物理基础渲染(PBR)计算最终的颜色
    // 参数包括:漫反射颜色、镜面反射颜色、反射率、光滑度、世界空间法线、视角方向、光源信息和间接光信息
    half4 c = UNITY_BRDF_PBS(
        s.diffColor, 
        s.specColor, 
        s.oneMinusReflectivity, 
        s.smoothness, 
        s.normalWorld, 
        -s.eyeVec, 
        light, 
        noIndirect
    );

    // 提取视图向量中的雾效坐标
    UNITY_EXTRACT_FOG_FROM_EYE_VEC(i);

    // 应用雾效,使颜色朝向黑色(在叠加通道中使用),避免颜色溢出
    UNITY_APPLY_FOG_COLOR(_unity_fogCoord, c.rgb, half4(0, 0, 0, 0));

    // 输出最终颜色,包含透明度
    return OutputForward(c, s.alpha);
}

五、fragForwardBaseInternal和fragForwardAddInternal的区别

fragForwardBaseInternalfragForwardAddInternal 是两个不同的片段着色器函数,它们分别用于不同的渲染路径和光照计算方式。

主要区别

  1. 光源处理

    • fragForwardBaseInternal:处理主光源,并计算全局光照(GI)。它通过 FragmentGI 函数计算直接光照、环境光和间接光照。

      //主光源
       UnityLight mainLight = MainLight ();
       //全局光照
       UnityGI gi = FragmentGI (s, occlusion, i.ambientOrLightmapUV, atten, mainLight);
      
    • fragForwardAddInternal:仅处理附加光源(通常是点光源、聚光灯等),并且不计算间接光照(noIndirect)。

      //附加光源
      UnityLight light = AdditiveLight (IN_LIGHTDIR_FWDADD(i), atten);
      //不计算间接光照
      UnityIndirect noIndirect = ZeroIndirect ();
      
  2. 全局光照(GI)

    • fragForwardBaseInternal:包含完整的全局光照计算,包括环境光、遮挡(AO)和间接光。

      half4 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect);
      
    • fragForwardAddInternal:不涉及全局光照,只处理直接光照。

      half4 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, light, noIndirect);
      
  3. 自发光(Emission)

    • fragForwardBaseInternal:在最终颜色中添加了自发光效果。

      c.rgb += Emission(i.tex.xy);
      
    • fragForwardAddInternal:没有自发光的计算。

  4. 雾效处理

    • fragForwardBaseInternal:使用 _unity_fogCoord 应用标准雾效。

      UNITY_APPLY_FOG(_unity_fogCoord, c.rgb);
      
    • fragForwardAddInternal:应用雾效时将颜色混合为黑色(half4(0, 0, 0, 0)),这通常用于叠加通道以避免颜色溢出。

       UNITY_APPLY_FOG_COLOR(_unity_fogCoord, c.rgb, half4(0,0,0,0));
      
  5. 输入结构体

    • fragForwardBaseInternal:使用 VertexOutputForwardBase 输入结构体,包含更多信息(如纹理坐标、法线等)。
    • fragForwardAddInternal:使用 VertexOutputForwardAdd 输入结构体,可能包含较少的信息,专注于附加光源的计算。
  6. 实例化和立体视觉设置

    • fragForwardBaseInternal:包含了 UNITY_SETUP_INSTANCE_IDUNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX 的调用,确保实例化和立体视觉支持。
    • fragForwardAddInternal:同样包含这些调用,但可能由于附加光源的特殊性,对这些设置的需求有所不同。

总结

  • fragForwardBaseInternal:适用于基本的前向渲染路径,处理所有类型的光源(包括主光源)和全局光照,提供更全面的光照计算。
  • fragForwardAddInternal:适用于附加光源的前向渲染路径,主要用于叠加额外的点光源或聚光灯,简化了光照计算,不涉及全局光照和自发光。

这两种函数的设计目的是为了优化不同场景下的渲染效率,同时保证渲染质量和效果的一致性。

猜你喜欢

转载自blog.csdn.net/qq_40924071/article/details/145888926
今日推荐