一、ForwardAdd
// Additive forward pass (one light per pass)Pass{Name "FORWARD_DELTA"Tags { "LightMode" = "ForwardAdd" }Blend [_SrcBlend] OneFog { Color (0,0,0,0) } // in additive pass fog should be blackZWrite OffZTest LEqualCGPROGRAM#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)特性,包含金属度、粗糙度等完整材质属性计算
标准版之前看过,现在看看简化版的
引用了UnityStandardCoreForwardSimple.cginc
的vertForwardAddSimple
和fragForwardAddSimpleInternal
二、vertForwardAddSimple
struct VertexOutputForwardAddSimple
{UNITY_POSITION(pos);float4 tex : TEXCOORD0;float3 posWorld : TEXCOORD1;#if !defined(_NORMALMAP) && SPECULAR_HIGHLIGHTSUNITY_FOG_COORDS_PACKED(2, half4) // x: fogCoord, yzw: reflectVec
#elseUNITY_FOG_COORDS_PACKED(2, half1)
#endifhalf3 lightDir : TEXCOORD3;#if defined(_NORMALMAP)#if SPECULAR_HIGHLIGHTShalf3 tangentSpaceEyeVec : TEXCOORD4;#endif
#elsehalf3 normalWorld : TEXCOORD4;
#endifUNITY_LIGHTING_COORDS(5, 6)UNITY_VERTEX_OUTPUT_STEREO
};VertexOutputForwardAddSimple vertForwardAddSimple(VertexInput v)
{// 定义输出结构体VertexOutputForwardAddSimple o;// 设置实例ID,确保多实例渲染正确工作UNITY_SETUP_INSTANCE_ID(v);// 初始化输出结构体的成员变量UNITY_INITIALIZE_OUTPUT(VertexOutputForwardAddSimple, o);// 初始化立体视图输出(如果需要)UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);// 将顶点从对象空间转换到世界空间float4 posWorld = mul(unity_ObjectToWorld, v.vertex);// 将顶点从对象空间转换到裁剪空间(用于最终渲染)o.pos = UnityObjectToClipPos(v.vertex);// 获取纹理坐标o.tex = TexCoords(v);// 存储世界空间中的顶点位置o.posWorld = posWorld.xyz;// 传递阴影和光照信息UNITY_TRANSFER_LIGHTING(o, v.uv1);// 计算光照方向向量half3 lightDir = _WorldSpaceLightPos0.xyz - posWorld.xyz * _WorldSpaceLightPos0.w;// 如果不是使用方向光,则对光照方向进行归一化#ifndef USING_DIRECTIONAL_LIGHTlightDir = NormalizePerVertexNormal(lightDir);#endif// 如果启用了镜面高光效果,计算视线方向向量#if SPECULAR_HIGHLIGHTShalf3 eyeVec = normalize(posWorld.xyz - _WorldSpaceCameraPos);#endif// 将法线从对象空间转换到世界空间half3 normalWorld = UnityObjectToWorldNormal(v.normal);// 如果启用了法线贴图#ifdef _NORMALMAP// 如果启用了镜面高光效果#if SPECULAR_HIGHLIGHTS// 调用 TangentSpaceLightingInput 函数,将光照方向和视线方向转换到切线空间TangentSpaceLightingInput(normalWorld, v.tangent, lightDir, eyeVec, o.lightDir, o.tangentSpaceEyeVec);#else// 如果未启用镜面高光效果,忽略视线方向half3 ignore;TangentSpaceLightingInput(normalWorld, v.tangent, lightDir, 0, o.lightDir, ignore);#endif#else// 如果未启用法线贴图,直接使用光照方向o.lightDir = lightDir;o.normalWorld = normalWorld;// 如果启用了镜面高光效果,计算反射向量并存储在 fogCoord 中#if SPECULAR_HIGHLIGHTSo.fogCoord.yzw = reflect(eyeVec, normalWorld);#endif#endif// 传递雾效信息UNITY_TRANSFER_FOG(o, o.pos);// 返回输出结构体return o;
}
三、vertForwardAddSimple和vertForwardAdd对比
以下是 vertForwardAddSimple
和 vertForwardAdd
两个顶点着色器函数的主要区别,分条列举如下:
1. 输出结构体类型
-
vertForwardAddSimple
:使用VertexOutputForwardAddSimple
作为输出结构体。 -
vertForwardAdd
: 使用VertexOutputForwardAdd
作为输出结构体。
2. 雾效处理方式
-
vertForwardAddSimple
:使用UNITY_TRANSFER_FOG
来传递雾效信息。UNITY_TRANSFER_FOG(o,o.pos);
-
vertForwardAdd
:使用UNITY_TRANSFER_FOG_COMBINED_WITH_EYE_VEC
来传递雾效信息。这个宏通常用于结合视线方向的雾效计算。UNITY_TRANSFER_FOG_COMBINED_WITH_EYE_VEC(o, o.pos);
3. 法线和切线空间处理
-
vertForwardAddSimple
:根据是否启用了_NORMALMAP
宏来决定如何处理法线和切线空间。#ifdef _NORMALMAP#if SPECULAR_HIGHLIGHTSTangentSpaceLightingInput(normalWorld, v.tangent, lightDir, eyeVec, o.lightDir, o.tangentSpaceEyeVec);#elsehalf3 ignore;TangentSpaceLightingInput(normalWorld, v.tangent, lightDir, 0, o.lightDir, ignore);#endif#elseo.lightDir = lightDir;o.normalWorld = normalWorld;#if SPECULAR_HIGHLIGHTSo.fogCoord.yzw = reflect(eyeVec, normalWorld);#endif#endif
-
vertForwardAdd
:根据是否启用了_TANGENT_TO_WORLD
宏来决定如何处理法线和切线空间。#ifdef _TANGENT_TO_WORLDfloat4 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];#elseo.tangentToWorldAndLightDir[0].xyz = 0;o.tangentToWorldAndLightDir[1].xyz = 0;o.tangentToWorldAndLightDir[2].xyz = normalWorld;#endif
4. 视图方向(视线方向)处理
-
vertForwardAddSimple
: 仅在启用了SPECULAR_HIGHLIGHTS
宏时计算视线方向 (eyeVec
)#if SPECULAR_HIGHLIGHTShalf3 eyeVec = normalize(posWorld.xyz - _WorldSpaceCameraPos);#endif
-
vertForwardAdd
:始终计算视线方向 (eyeVec
) ,无论是否启用了SPECULAR_HIGHLIGHTS
。o.eyeVec.xyz = NormalizePerVertexNormal(posWorld.xyz - _WorldSpaceCameraPos);
5. 光照方向处理
-
vertForwardAddSimple
:计算光照方向 (lightDir
) 存储在lightDir 。half3 lightDir = _WorldSpaceLightPos0.xyz - posWorld.xyz * _WorldSpaceLightPos0.w; #ifndef USING_DIRECTIONAL_LIGHTlightDir = NormalizePerVertexNormal(lightDir); #endif#ifdef _NORMALMAP #elseo.lightDir = lightDir; #endif
-
vertForwardAdd
: 计算光照方向存储在tangentToWorldAndLightDir
float3 lightDir = _WorldSpaceLightPos0.xyz - posWorld.xyz * _WorldSpaceLightPos0.w;#ifndef USING_DIRECTIONAL_LIGHTlightDir = NormalizePerVertexNormal(lightDir);#endifo.tangentToWorldAndLightDir[0].w = lightDir.x;o.tangentToWorldAndLightDir[1].w = lightDir.y;o.tangentToWorldAndLightDir[2].w = lightDir.z;
6. 附加功能支持
-
vertForwardAddSimple
:不包含对视差贴图的支持。 -
vertForwardAdd
:包含对视差贴图的支持。#ifdef _PARALLAXMAPTANGENT_SPACE_ROTATION;o.viewDirForParallax = mul (rotation, ObjSpaceViewDir(v.vertex));#endif
7. 初始化和设置
-
vertForwardAddSimple
:- 初始化输出结构体
VertexOutputForwardAddSimple
,并设置实例 ID 和立体视图输出。
- 初始化输出结构体
-
vertForwardAdd
:- 初始化输出结构体
VertexOutputForwardAdd
,并设置实例 ID 和立体视图输出。
- 初始化输出结构体
8. 细节处理
-
vertForwardAddSimple
:-
在未启用
_NORMALMAP
的情况下,直接将法线存储在o.normalWorld
中,并在启用了SPECULAR_HIGHLIGHTS
时计算反射向量。#ifdef _NORMALMAP//......#elseo.lightDir = lightDir;o.normalWorld = normalWorld;#if SPECULAR_HIGHLIGHTSo.fogCoord.yzw = reflect(eyeVec, normalWorld);#endif#endif
-
-
vertForwardAdd
:-
在未启用
_TANGENT_TO_WORLD
的情况下,将法线存储在o.tangentToWorldAndLightDir[2].xyz
中,并不进行额外的反射向量计算。#ifdef _TANGENT_TO_WORLD//...... #elseo.tangentToWorldAndLightDir[0].xyz = 0;o.tangentToWorldAndLightDir[1].xyz = 0;o.tangentToWorldAndLightDir[2].xyz = normalWorld; #endif
-
总结
特性/功能 | vertForwardAddSimple | vertForwardAdd |
---|---|---|
输出结构体 | VertexOutputForwardAddSimple | VertexOutputForwardAdd |
雾效处理 | UNITY_TRANSFER_FOG | UNITY_TRANSFER_FOG_COMBINED_WITH_EYE_VEC |
法线和切线空间处理 | 根据 _NORMALMAP 处理 | 根据 _TANGENT_TO_WORLD 处理 |
视线方向处理 | 仅在 SPECULAR_HIGHLIGHTS 时计算 | 始终计算并存储 |
光照方向处理 | 直接存储或传递给 TangentSpaceLightingInput | 分别存储在 tangentToWorldAndLightDir 的 .w 分量中 |
支持视差贴图 | 否 | 是 (通过 _PARALLAXMAP 宏) |
反射向量计算 | 在 SPECULAR_HIGHLIGHTS 时计算 | 不计算 |
四、fragForwardAddSimpleInternal
half4 fragForwardAddSimpleInternal (VertexOutputForwardAddSimple i)
{// 应用抖动交叉淡入效果,以减少在低分辨率下可能出现的锯齿现象。UNITY_APPLY_DITHER_CROSSFADE(i.pos.xy);// 根据传入的顶点信息,设置片段的基本材质属性(如漫反射颜色、镜面反射颜色和平滑度)。FragmentCommonData s = FragmentSetupSimpleAdd(i);// 计算直接光源下的BRDF(双向反射分布函数),考虑了材质的漫反射颜色、镜面反射颜色和平滑度以及视角与光方向之间的关系。half3 c = BRDF3DirectSimple(s.diffColor, s.specColor, s.smoothness, dot(REFLECTVEC_FOR_SPECULAR(i, s), i.lightDir));// 如果启用了镜面高光,则将计算出的颜色乘以光源的颜色,否则使用预乘了光颜色的diffColor。#if SPECULAR_HIGHLIGHTS // else diffColor has premultiplied light colorc *= _LightColor0.rgb;#endif// 获取当前片段的光照衰减,并将其应用于颜色值上。同时,根据法线和光方向间的夹角调整最终颜色强度。UNITY_LIGHT_ATTENUATION(atten, i, s.posWorld)c *= atten * saturate(dot(LightSpaceNormal(i, s), i.lightDir));// 应用雾效,使得物体在远处时逐渐融合到背景色中。这里特别地,雾效会趋向于黑色。UNITY_APPLY_FOG_COLOR(i.fogCoord, c.rgb, half4(0,0,0,0)); // fog towards black in additive pass// 返回最终的颜色结果,包括alpha通道的信息。return OutputForward (half4(c, 1), s.alpha);
}
五、fragForwardAddSimpleInternal和fragForwardAddInternal对比
以下是 fragForwardAddSimpleInternal
和 fragForwardAddInternal
两个片段着色器函数的主要区别,如下:
1. 输入参数类型:
fragForwardAddSimpleInternal
接受的参数类型是VertexOutputForwardAddSimple
。fragForwardAddInternal
接受的参数类型是VertexOutputForwardAdd
。
2. 材质属性设置:
fragForwardAddSimpleInternal
使用FragmentSetupSimpleAdd(i)
来设置基本的材质属性(如漫反射颜色、镜面反射颜色和平滑度)。fragForwardAddInternal
使用FRAGMENT_SETUP_FWDADD(s)
来设置材质属性,并且包含更多的细节处理(如视角向量和法线向量)。
3. 光照计算方法:
fragForwardAddSimpleInternal
使用BRDF3DirectSimple
函数来计算直接光照下的颜色贡献。这个函数相对简单,只考虑了基本的BRDF计算。fragForwardAddInternal
使用UNITY_BRDF_PBS
函数来进行更复杂的物理基础渲染(PBR)计算,包括漫反射和镜面反射的组合,并考虑了间接光照(虽然这里设为noIndirect
)。
4. 光源衰减和法线方向处理:
fragForwardAddSimpleInternal
直接使用UNITY_LIGHT_ATTENUATION(atten, i, s.posWorld)
获取光源衰减,并通过saturate(dot(LightSpaceNormal(i, s), i.lightDir))
计算法线与光方向之间的夹角影响最终颜色。fragForwardAddInternal
在获取光源衰减后,使用AdditiveLight (IN_LIGHTDIR_FWDADD(i), atten)
创建一个UnityLight
结构体,包含了光源方向和衰减信息。
5. 立体视觉支持:
fragForwardAddSimpleInternal
没有涉及立体视觉的支持。fragForwardAddInternal
包含了对立体视觉的支持,通过UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);
来设置立体眼索引。
6. 雾效应用:
fragForwardAddSimpleInternal
使用UNITY_APPLY_FOG_COLOR(i.fogCoord, c.rgb, half4(0,0,0,0));
直接应用雾效。fragForwardAddInternal
首先通过UNITY_EXTRACT_FOG_FROM_EYE_VEC(i);
提取雾坐标,然后使用UNITY_APPLY_FOG_COLOR(_unity_fogCoord, c.rgb, half4(0,0,0,0));
应用雾效。
7. 输出函数调用:
fragForwardAddSimpleInternal
最终调用OutputForward (half4(c, 1), s.alpha);
返回结果。fragForwardAddInternal
最终调用OutputForward (c, s.alpha);
返回结果,注意这里的c
已经是一个half4
类型。
总结来说,fragForwardAddSimpleInternal
更加简化,适用于基本的光照和材质计算;而 fragForwardAddInternal
则提供了更复杂和全面的物理基础渲染(PBR),并且支持更多高级特性如立体视觉等。这两者的选择取决于具体的渲染需求和性能考量。