一、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
中的vertAdd
和fragAdd
以下是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
里的vertForwardAdd
和 fragForwardAddInternal
二、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的区别
vertForwardBase
和 vertForwardAdd
是两个不同的顶点着色器函数,它们分别用于处理基础光照和附加光源的前向渲染路径。
主要区别
- 输入输出结构体:
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 };
-
世界位置的存储方式:
-
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;
-
-
环境光或光照贴图UV:
vertForwardBase
:计算并设置环境光或光照贴图UV。o.ambientOrLightmapUV = VertexGIForward(v, posWorld, normalWorld);
vertForwardAdd
:不涉及环境光或光照贴图UV的计算。
-
光源方向的计算:
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;
-
视差映射:
-
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
-
-
实例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的区别
fragForwardBaseInternal
和 fragForwardAddInternal
是两个不同的片段着色器函数,它们分别用于不同的渲染路径和光照计算方式。
主要区别
-
光源处理:
-
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 ();
-
-
全局光照(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);
-
-
自发光(Emission):
-
fragForwardBaseInternal
:在最终颜色中添加了自发光效果。c.rgb += Emission(i.tex.xy);
-
fragForwardAddInternal
:没有自发光的计算。
-
-
雾效处理:
-
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));
-
-
输入结构体:
fragForwardBaseInternal
:使用VertexOutputForwardBase
输入结构体,包含更多信息(如纹理坐标、法线等)。fragForwardAddInternal
:使用VertexOutputForwardAdd
输入结构体,可能包含较少的信息,专注于附加光源的计算。
-
实例化和立体视觉设置:
fragForwardBaseInternal
:包含了UNITY_SETUP_INSTANCE_ID
和UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX
的调用,确保实例化和立体视觉支持。fragForwardAddInternal
:同样包含这些调用,但可能由于附加光源的特殊性,对这些设置的需求有所不同。
总结
fragForwardBaseInternal
:适用于基本的前向渲染路径,处理所有类型的光源(包括主光源)和全局光照,提供更全面的光照计算。fragForwardAddInternal
:适用于附加光源的前向渲染路径,主要用于叠加额外的点光源或聚光灯,简化了光照计算,不涉及全局光照和自发光。
这两种函数的设计目的是为了优化不同场景下的渲染效率,同时保证渲染质量和效果的一致性。