Unity Standard Shader 解析(三)之ForwardBase(简化版)

一、ForwardBase

		//  Base forward pass (directional light, emission, lightmaps, ...)
        Pass
        {
    
    
            Name "FORWARD"
            Tags {
    
     "LightMode" = "ForwardBase" }

            Blend [_SrcBlend] [_DstBlend]
            ZWrite [_ZWrite]

            CGPROGRAM
            #pragma target 3.0

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

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

            #pragma multi_compile_fwdbase
            #pragma multi_compile_fog
            #pragma multi_compile_instancing
            // 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 vertBase
            #pragma fragment fragBase
            #include "UnityStandardCoreForward.cginc"

            ENDCG
        }

引用了UnityStandardCoreForward.cginc中的vertBasefragBase

以下是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)特性,包含金属度、粗糙度等完整材质属性计算‌

标准版之前看过,现在看看简化版的


引用了UnityStandardCoreForwardSimple.cgincvertForwardBaseSimplefragForwardBaseSimpleInternal

二、vertForwardBaseSimple

struct VertexOutputBaseSimple
{
    
    
    UNITY_POSITION(pos);
    float4 tex                          : TEXCOORD0;
    half4 eyeVec                        : TEXCOORD1; // w: grazingTerm

    half4 ambientOrLightmapUV           : TEXCOORD2; // SH or Lightmap UV
    SHADOW_COORDS(3)
    UNITY_FOG_COORDS_PACKED(4, half4) // x: fogCoord, yzw: reflectVec

    half4 normalWorld                   : TEXCOORD5; // w: fresnelTerm

#ifdef _NORMALMAP
    half3 tangentSpaceLightDir          : TEXCOORD6;
    #if SPECULAR_HIGHLIGHTS
        half3 tangentSpaceEyeVec        : TEXCOORD7;
    #endif
#endif
#if UNITY_REQUIRE_FRAG_WORLDPOS
    float3 posWorld                     : TEXCOORD8;
#endif

    UNITY_VERTEX_OUTPUT_STEREO
};

VertexOutputBaseSimple vertForwardBaseSimple(VertexInput v)
{
    
    
    // 设置实例ID,用于支持GPU实例化(Instance)
    UNITY_SETUP_INSTANCE_ID(v);

    // 初始化输出结构体 VertexOutputBaseSimple
    VertexOutputBaseSimple o;

    // 初始化输出结构体的所有字段,默认值为0
    UNITY_INITIALIZE_OUTPUT(VertexOutputBaseSimple, o);

    // 初始化立体视图输出(用于VR等立体显示技术)
    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

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

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

    // 获取纹理坐标
    o.tex = TexCoords(v);

    // 计算从顶点到摄像机的方向向量(在世界空间中)
    half3 eyeVec = normalize(posWorld.xyz - _WorldSpaceCameraPos);

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

    // 将世界空间中的法线存储到输出结构体中
    o.normalWorld.xyz = normalWorld;

    // 将世界空间中的视线方向向量存储到输出结构体中
    o.eyeVec.xyz = eyeVec;

    // 如果启用了法线贴图,则进行额外的计算
    #ifdef _NORMALMAP
        // 定义一个临时变量用于存储切线空间中的视线方向向量
        half3 tangentSpaceEyeVec;

        // 计算法线、切线和光源方向,并将结果存储到输出结构体中
        TangentSpaceLightingInput(normalWorld, v.tangent, _WorldSpaceLightPos0.xyz, eyeVec, o.tangentSpaceLightDir, tangentSpaceEyeVec);

        // 如果启用了镜面高光效果,则将切线空间中的视线方向向量存储到输出结构体中
        #if SPECULAR_HIGHLIGHTS
            o.tangentSpaceEyeVec = tangentSpaceEyeVec;
        #endif
    #endif

    // 传递阴影信息,用于接收阴影
    TRANSFER_SHADOW(o);

    // 计算环境光或光照贴图的UV坐标,并存储到输出结构体中
    o.ambientOrLightmapUV = VertexGIForward(v, posWorld, normalWorld);

    // 计算反射向量并存储到输出结构体中
    o.fogCoord.yzw = reflect(eyeVec, normalWorld);

    // 计算菲涅尔项(Fresnel term),并存储到输出结构体中
    o.normalWorld.w = Pow4(1 - saturate(dot(normalWorld, -eyeVec)));

    // 如果未启用光泽贴图,则计算并存储漫反射系数(grazing term)
    #if !GLOSSMAP
        o.eyeVec.w = saturate(_Glossiness + UNIFORM_REFLECTIVITY());
    #endif

    // 传递雾效信息,用于后续处理
    UNITY_TRANSFER_FOG(o, o.pos);

    // 返回处理后的顶点输出结构体
    return o;
}

三、 vertForwardBaseSimple和vertForwardBase对比

以下是 vertForwardBaseSimplevertForwardBase 两个顶点着色器函数的主要区别,分条总结:

1. 输出结构体类型

  • vertForwardBaseSimple:使用 VertexOutputBaseSimple 输出结构体

    struct VertexOutputBaseSimple
    {
          
          
        UNITY_POSITION(pos);
        float4 tex                          : TEXCOORD0;
        half4 eyeVec                        : TEXCOORD1; 
        half4 ambientOrLightmapUV           : TEXCOORD2; // SH or Lightmap UV
        SHADOW_COORDS(3)
        UNITY_FOG_COORDS_PACKED(4, half4) // x: fogCoord, yzw: reflectVec
        half4 normalWorld                   : TEXCOORD5; // w: fresnelTerm
    #ifdef _NORMALMAP
        half3 tangentSpaceLightDir          : TEXCOORD6;
        #if SPECULAR_HIGHLIGHTS
            half3 tangentSpaceEyeVec        : TEXCOORD7;
        #endif
    #endif
    #if UNITY_REQUIRE_FRAG_WORLDPOS
        float3 posWorld                     : TEXCOORD8;
    #endif
        UNITY_VERTEX_OUTPUT_STEREO
    };
    
  • vertForwardBase:使用 VertexOutputForwardBase 输出结构体

    struct VertexOutputForwardBase
    {
          
          
        UNITY_POSITION(pos);
        float4 tex                            : TEXCOORD0;
        float4 eyeVec                         : TEXCOORD1;    // eyeVec.xyz | fogCoord
        float4 tangentToWorldAndPackedData[3] : TEXCOORD2;    // [3x3:tangentToWorld | 1x3:viewDirForParallax or worldPos]
        half4 ambientOrLightmapUV             : TEXCOORD5;    // SH or Lightmap UV
        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
    };
    

2. 实例ID传递

  • vertForwardBaseSimple: 只设置了实例ID。

    UNITY_SETUP_INSTANCE_ID(v)
    
  • vertForwardBase: 设置了实例ID并将其传递给输出结构体

       UNITY_SETUP_INSTANCE_ID(v);
       UNITY_TRANSFER_INSTANCE_ID(v, o);
    

3. 法线和视线方向计算

  • vertForwardBaseSimple:基本相同

     half3 eyeVec = normalize(posWorld.xyz - _WorldSpaceCameraPos);
        half3 normalWorld = UnityObjectToWorldNormal(v.normal);
    
  • vertForwardBase

     o.eyeVec.xyz = NormalizePerVertexNormal(posWorld.xyz - _WorldSpaceCameraPos);
        float3 normalWorld = UnityObjectToWorldNormal(v.normal);
    

4. 切线空间处理

  • vertForwardBaseSimple: 如果启用了法线贴图,则进行额外的切线空间计算,并存储结果。

    #ifdef _NORMALMAP
            half3 tangentSpaceEyeVec;
            TangentSpaceLightingInput(normalWorld, v.tangent, _WorldSpaceLightPos0.xyz, eyeVec, o.tangentSpaceLightDir, tangentSpaceEyeVec);
            #if SPECULAR_HIGHLIGHTS
                o.tangentSpaceEyeVec = tangentSpaceEyeVec;
            #endif
        #endif
    
  • vertForwardBase:如果启用了切线到世界空间的转换 ,则创建切线到世界空间的矩阵并存储结果。

       #ifdef _TANGENT_TO_WORLD
            float4 tangentWorld = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w);
    
            float3x3 tangentToWorld = CreateTangentToWorldPerVertex(normalWorld, tangentWorld.xyz, tangentWorld.w);
            o.tangentToWorldAndPackedData[0].xyz = tangentToWorld[0];
            o.tangentToWorldAndPackedData[1].xyz = tangentToWorld[1];
            o.tangentToWorldAndPackedData[2].xyz = tangentToWorld[2];
        #else
            o.tangentToWorldAndPackedData[0].xyz = 0;
            o.tangentToWorldAndPackedData[1].xyz = 0;
            o.tangentToWorldAndPackedData[2].xyz = normalWorld;
        #endif
    

5. 阴影信息传递

  • vertForwardBaseSimple:使用 TRANSFER_SHADOW 传递阴影信息。

    //We need this for shadow receving
    TRANSFER_SHADOW(o);
    
  • vertForwardBase: 使用 UNITY_TRANSFER_LIGHTING 传递阴影信息。

    //We need this for shadow receving
     UNITY_TRANSFER_LIGHTING(o, v.uv1);
    

6. 反射向量计算

  • vertForwardBaseSimple:计算反射向量。

    o.fogCoord.yzw = reflect(eyeVec, normalWorld);
    
  • vertForwardBase:没有显式计算反射向量。

7. 菲涅尔项计算

  • vertForwardBaseSimple:计算菲涅尔项

     o.normalWorld.w = Pow4(1 - saturate(dot(normalWorld, -eyeVec))); // fresnel term
    
  • vertForwardBase: 没有显式计算菲涅尔项。

8. 漫反射系数计算

  • vertForwardBaseSimple: 如果未启用光泽贴图,则计算漫反射系数
       #if !GLOSSMAP
            o.eyeVec.w = saturate(_Glossiness + UNIFORM_REFLECTIVITY()); // grazing term
        #endif
    
  • vertForwardBase:没有显式计算漫反射系数。

9. 雾效信息传递

  • vertForwardBaseSimple:使用 `UNITY_TRANSFER_FOG传递雾效信息。

     UNITY_TRANSFER_FOG(o, o.pos);
    
  • vertForwardBase:使用 `UNITY_TRANSFER_FOG_COMBINED_WITH_EYE_VEC传递雾效信息

    UNITY_TRANSFER_FOG_COMBINED_WITH_EYE_VEC(o,o.pos);
    

10. 视差映射支持

  • vertForwardBaseSimple:不支持视差映射。

  • 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
    

11. 世界位置打包

  • vertForwardBaseSimple:不进行世界位置的打包。

  • 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
    

12.总结

通过这些对比,可以看出 vertForwardBaseSimple 是一个较为简化版本的顶点着色器,主要用于基本的渲染需求;而 vertForwardBase 则是一个功能更全面的顶点着色器,支持更多的高级特性如视差映射、切线空间转换等。

四、fragForwardBaseSimpleInternal

half4 fragForwardBaseSimpleInternal (VertexOutputBaseSimple i)
{
    
    
    // 应用抖动交叉淡入效果,以减少在低分辨率下可能出现的锯齿现象。
    UNITY_APPLY_DITHER_CROSSFADE(i.pos.xy);

    // 设置基础的材质属性(如漫反射颜色、镜面反射颜色和平滑度)。
    FragmentCommonData s = FragmentSetupSimple(i);

    // 获取主光源信息,包括方向和颜色。
    UnityLight mainLight = MainLightSimple(i, s);

    // 根据是否启用了光照贴图和法线贴图来选择不同的法线方向进行点积计算。
    #if !defined(LIGHTMAP_ON) && defined(_NORMALMAP)
        // 如果未启用光照贴图且启用了法线贴图,则使用切线空间法线与切线空间光方向之间的点积。
        half ndotl = saturate(dot(s.tangentSpaceNormal, i.tangentSpaceLightDir));
    #else
        // 否则,直接使用世界空间法线与主光源方向之间的点积。
        half ndotl = saturate(dot(s.normalWorld, mainLight.dir));
    #endif

    // 提取烘焙遮挡(即阴影遮罩),并获取实时阴影衰减值。
    // 注意:在SM 2.0设备上无法获取世界位置,因此无法应用阴影渐变。
    half shadowMaskAttenuation = UnitySampleBakedOcclusion(i.ambientOrLightmapUV, 0);
    half realtimeShadowAttenuation = SHADOW_ATTENUATION(i);

    // 混合实时阴影和烘焙阴影的衰减。
    half atten = UnityMixRealtimeAndBakedShadows(realtimeShadowAttenuation, shadowMaskAttenuation, 0);

    // 计算环境遮挡(occlusion),模拟物体被其他物体遮挡的程度。
    half occlusion = Occlusion(i.tex.xy);

    // 计算视角方向与光源方向的反射向量点积,用于后续的BRDF计算。
    half rl = dot(REFLECTVEC_FOR_SPECULAR(i, s), LightDirForSpecular(i, mainLight));

    // 获取全局光照(GI)信息,包括间接光照和环境光。
    UnityGI gi = FragmentGI (s, occlusion, i.ambientOrLightmapUV, atten, mainLight);

    // 计算经过衰减后的主光源颜色。
    half3 attenuatedLightColor = gi.light.color * ndotl;

    // 计算间接光照贡献。
    half3 c = BRDF3_Indirect(s.diffColor, s.specColor, gi.indirect, PerVertexGrazingTerm(i, s), PerVertexFresnelTerm(i));

    // 计算直接光照贡献,并乘以经过衰减的主光源颜色。
    c += BRDF3DirectSimple(s.diffColor, s.specColor, s.smoothness, rl) * attenuatedLightColor;

    // 添加自发光(Emission)贡献。
    c += Emission(i.tex.xy);

    // 应用雾效,使远处的物体逐渐融合到背景色中。
    UNITY_APPLY_FOG(i.fogCoord, c);

    // 返回最终的颜色结果。
    return OutputForward (half4(c, 1), s.alpha);
}

五、fragForwardBaseSimpleInternal和fragForwardBaseInternal对比

以下是 fragForwardBaseInternalfragForwardBaseSimpleInternal 两个片段着色器函数的主要区别,分条列举如下:

1. 输入参数类型

  • fragForwardBaseInternal:

    • 接受的参数类型是 VertexOutputForwardBase
  • fragForwardBaseSimpleInternal:

    • 接受的参数类型是 VertexOutputBaseSimple

2. 材质属性设置

  • fragForwardBaseInternal:

    • 使用 FRAGMENT_SETUP(s) 设置材质属性,包含更多的细节处理(如视角向量和法线向量)。
  • fragForwardBaseSimpleInternal:

    • 使用 FragmentSetupSimple(i) 设置基础的材质属性(如漫反射颜色、镜面反射颜色和平滑度),相对简单。

3. 实例ID和立体视觉支持

  • fragForwardBaseInternal:

    • 包含对实例ID的支持:UNITY_SETUP_INSTANCE_ID(i);
    • 包含对立体视觉的支持:UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
  • fragForwardBaseSimpleInternal:

    • 没有涉及实例ID和立体视觉的支持。

4. 主光源获取方式

  • fragForwardBaseInternal:

    • 使用 UnityLight mainLight = MainLight(); 获取主光源信息。
  • fragForwardBaseSimpleInternal:

    • 使用 UnityLight mainLight = MainLightSimple(i, s); 获取主光源信息,可能包含额外的计算或简化。

5. 光源衰减和阴影处理

  • fragForwardBaseInternal:

    • 直接使用 UNITY_LIGHT_ATTENUATION(atten, i, s.posWorld); 获取光源衰减。
  • fragForwardBaseSimpleInternal:

    • 提取烘焙遮挡和实时阴影衰减值,并混合它们:
      half shadowMaskAttenuation = UnitySampleBakedOcclusion(i.ambientOrLightmapUV, 0);
      half realtimeShadowAttenuation = SHADOW_ATTENUATION(i);
      half atten = UnityMixRealtimeAndBakedShadows(realtimeShadowAttenuation, shadowMaskAttenuation, 0);
      

6. 法线方向选择

  • fragForwardBaseInternal:

    • 直接使用世界空间法线进行光照计算。
  • fragForwardBaseSimpleInternal:

    • 根据是否启用了光照贴图和法线贴图来选择不同的法线方向进行点积计算:
      #if !defined(LIGHTMAP_ON) && defined(_NORMALMAP)
          half ndotl = saturate(dot(s.tangentSpaceNormal, i.tangentSpaceLightDir));
      #else
          half ndotl = saturate(dot(s.normalWorld, mainLight.dir));
      #endif
      

7. 全局光照(GI)处理

  • fragForwardBaseInternal:

    • 使用 UNITY_BRDF_PBS 函数来进行复杂的物理基础渲染(PBR)计算,包括漫反射和镜面反射的组合,并考虑了间接光照:
      half4 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect);
      
  • fragForwardBaseSimpleInternal:

    • 分别计算直接光照和间接光照贡献,并将它们相加:
      half3 c = BRDF3_Indirect(s.diffColor, s.specColor, gi.indirect, PerVertexGrazingTerm(i, s), PerVertexFresnelTerm(i));
      c += BRDF3DirectSimple(s.diffColor, s.specColor, s.smoothness, rl) * attenuatedLightColor;
      

8. 自发光(Emission)处理

  • fragForwardBaseInternal:

    • 在最终颜色上直接加上自发光值:
      c.rgb += Emission(i.tex.xy);
      
  • fragForwardBaseSimpleInternal:

    • 同样在最终颜色上直接加上自发光值:
      c += Emission(i.tex.xy);
      

9. 雾效应用

  • fragForwardBaseInternal:

    • 提取雾坐标并应用雾效:
      UNITY_EXTRACT_FOG_FROM_EYE_VEC(i);
      UNITY_APPLY_FOG(_unity_fogCoord, c.rgb);
      
  • fragForwardBaseSimpleInternal:

    • 直接应用雾效:
      UNITY_APPLY_FOG(i.fogCoord, c);
      

10. 输出函数调用

  • fragForwardBaseInternal:

    • 最终调用 OutputForward (c, s.alpha); 返回结果。
  • fragForwardBaseSimpleInternal:

    • 最终调用 OutputForward (half4(c, 1), s.alpha); 返回结果,注意这里的 c 已经是一个 half3 类型,需要转换为 half4

总结

  • fragForwardBaseInternal 更加复杂,适用于更全面的物理基础渲染(PBR),并且支持更多高级特性如实例ID和立体视觉等。
  • fragForwardBaseSimpleInternal 则更加简化,适用于基本的光照和材质计算,性能更高但功能相对较少。这两个函数的选择取决于具体的渲染需求和性能考量。

猜你喜欢

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