Unity Standard Shader 解析(一)之ForwardBase(标准版)

一、ForwardBase

csharp 复制代码
		//  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的源码,

csharp 复制代码
// 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.cgincvertForwardBasefragForwardBaseInternal

二、vertForwardBase

csharp 复制代码
// ------------------------------------------------------------------
//  Base forward pass (directional light, emission, lightmaps, ...)

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)

    // next ones would not fit into SM2.0 limits, but they are always for SM3.0+
#if UNITY_REQUIRE_FRAG_WORLDPOS && !UNITY_PACK_WORLDPOS_WITH_TANGENT
    float3 posWorld                     : TEXCOORD8;
#endif

    UNITY_VERTEX_INPUT_INSTANCE_ID
    UNITY_VERTEX_OUTPUT_STEREO
};

// 顶点着色器函数:将输入的顶点数据转换为前向渲染所需的输出结构
VertexOutputForwardBase vertForwardBase (VertexInput v)
{
    // 设置实例ID(用于GPU实例化渲染)
    UNITY_SETUP_INSTANCE_ID(v);
    
    // 声明输出结构变量
    VertexOutputForwardBase o;
    
    // 初始化输出结构中的各个字段(宏内部会设置默认值)
    UNITY_INITIALIZE_OUTPUT(VertexOutputForwardBase, o);
    
    // 将输入的实例ID传递给输出(保证实例数据一致)
    UNITY_TRANSFER_INSTANCE_ID(v, o);
    
    // 初始化立体渲染相关数据(用于VR或立体渲染)
    UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

    // 计算世界空间位置:将顶点从对象空间转换到世界空间
    float4 posWorld = mul(unity_ObjectToWorld, v.vertex);
    
    // 如果要求片元阶段需要世界空间位置数据
    #if UNITY_REQUIRE_FRAG_WORLDPOS
        // 如果需要将世界位置打包到切线数据的w分量中(节省传输带宽)
        #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
            // 否则直接将世界位置赋值给输出结构的posWorld变量(仅XYZ分量)
            o.posWorld = posWorld.xyz;
        #endif
    #endif

    // 将顶点从对象空间转换到裁剪空间,用于后续屏幕映射
    o.pos = UnityObjectToClipPos(v.vertex);

    // 计算纹理坐标
    o.tex = TexCoords(v);
    
    // 计算视线向量:从世界空间摄像机位置指向顶点位置,并进行归一化
    o.eyeVec.xyz = NormalizePerVertexNormal(posWorld.xyz - _WorldSpaceCameraPos);
    
    // 计算世界空间法线:将顶点法线从对象空间转换为世界空间
    float3 normalWorld = UnityObjectToWorldNormal(v.normal);
    
    #ifdef _TANGENT_TO_WORLD
        // 如果定义了将切线转换到世界空间,则计算切线方向
        float4 tangentWorld = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w);

        // 计算切线到世界空间的变换矩阵,依据法线、切线方向和切线的符号(w分量)
        float3x3 tangentToWorld = CreateTangentToWorldPerVertex(normalWorld, tangentWorld.xyz, tangentWorld.w);
        
        // 将变换矩阵的各行存入输出结构的tangentToWorldAndPackedData数组中(XYZ分量)
        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

    // 为阴影接收做数据传递:将第二套UV(v.uv1)传递给输出,用于光照和阴影计算
    UNITY_TRANSFER_LIGHTING(o, v.uv1);

    // 计算环境光或光照贴图的UV(用于全局光照信息)
    o.ambientOrLightmapUV = VertexGIForward(v, posWorld, normalWorld);

    #ifdef _PARALLAXMAP
        // 如果启用了视差贴图效果,则进行切线空间旋转(通常用于计算视差效果)
        TANGENT_SPACE_ROTATION;
        // 计算视差用的视线方向(在物体空间),通过旋转矩阵转换
        half3 viewDirForParallax = mul(rotation, ObjSpaceViewDir(v.vertex));
        // 将视差计算结果存入切线数据的w分量中(覆盖之前的打包数据)
        o.tangentToWorldAndPackedData[0].w = viewDirForParallax.x;
        o.tangentToWorldAndPackedData[1].w = viewDirForParallax.y;
        o.tangentToWorldAndPackedData[2].w = viewDirForParallax.z;
    #endif

    // 传递雾效信息:结合雾效和视线数据,计算最终雾信息
    UNITY_TRANSFER_FOG_COMBINED_WITH_EYE_VEC(o, o.pos);
    
    // 返回最终的顶点输出数据
    return o;
}

三、fragForwardBaseInternal

csharp 复制代码
half4 fragForwardBaseInternal (VertexOutputForwardBase i)
{
    // 应用抖动交叉淡化(用于消除伪影或提高过渡平滑性),基于屏幕空间位置 i.pos.xy
    UNITY_APPLY_DITHER_CROSSFADE(i.pos.xy);

    // 设置片元阶段所需的局部变量结构 s(从顶点到片元传递的光照和材质数据)
    FRAGMENT_SETUP(s)

    // 设置实例ID,用于支持GPU实例化渲染
    UNITY_SETUP_INSTANCE_ID(i);
    // 设置立体渲染后处理的眼睛索引(用于VR或立体渲染)
    UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);

    // 获取主光源(通常为场景中的定向光)
    UnityLight mainLight = MainLight ();
    // 计算主光源对当前片元的光照衰减,基于世界空间位置 s.posWorld
    UNITY_LIGHT_ATTENUATION(atten, i, s.posWorld);

    // 计算遮蔽值,使用片元的纹理坐标 i.tex.xy(例如环境遮蔽、AO等效果)
    half occlusion = Occlusion(i.tex.xy);
    // 计算全局光照(GI)信息,传入基本材质参数、遮蔽值、环境/光照贴图UV、衰减值和主光源信息
    UnityGI gi = FragmentGI (s, occlusion, i.ambientOrLightmapUV, atten, mainLight);

    // 使用基于物理的BRDF计算片元颜色
    // 参数包括漫反射色 s.diffColor、镜面反射色 s.specColor、反射率的补值 s.oneMinusReflectivity、
    // 表面光滑度 s.smoothness、世界空间法线 s.normalWorld、观察方向(负的眼向量 -s.eyeVec),
    // 以及来自主光源和间接光照的光照信息
    half4 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.smoothness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect);
    
    // 添加自发光(Emission)效果,基于片元的纹理坐标 i.tex.xy
    c.rgb += Emission(i.tex.xy);

    // 从顶点输出中提取雾效所需的相关数据(例如雾的深度信息)
    UNITY_EXTRACT_FOG_FROM_EYE_VEC(i);
    // 根据计算出的雾坐标 _unity_fogCoord 对片元颜色进行雾效混合
    UNITY_APPLY_FOG(_unity_fogCoord, c.rgb);
    
    // 调用 OutputForward 函数输出最终的片元颜色,同时传递 alpha 值
    return OutputForward (c, s.alpha);
}

四、相关宏定义

五、相关函数定义

相关推荐
90后小陈老师1 天前
Unity教学 项目2 2D闯关游戏
游戏·unity·游戏引擎
噗噗夹的TA之旅1 天前
Unity Shader 学习20:URP LitForwardPass PBR 解析
学习·unity·游戏引擎·图形渲染·技术美术
nnsix1 天前
Unity ReferenceFinder插件 多选资源查找bug解决
unity·游戏引擎·bug
gzroy1 天前
Unity Shader Graph实现全息瞄准器
unity·游戏引擎
90后小陈老师1 天前
Unity教学 基础介绍
unity·游戏引擎
90后小陈老师1 天前
Unity教学 项目3 3D坦克大战
3d·unity·游戏引擎
秦奈1 天前
Unity复习学习随笔(五):Unity基础
学习·unity·游戏引擎
nnsix1 天前
Unity ReferenceFinder插件 窗口中选择资源时 同步选择Assets下的资源
java·unity·游戏引擎
麷飞花1 天前
unity3d scene窗口选中物体, 在 hierarchy高光显示
unity·editor·unity3d·u3d·hierarchy
ۓ明哲ڪ1 天前
Unity功能——关闭脚本自动编译(Unity2021.3)
unity·游戏引擎