一、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
中的vertBase
和fragBase
以下是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
的vertForwardBaseSimple
和fragForwardBaseSimpleInternal
二、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对比
以下是 vertForwardBaseSimple
和 vertForwardBase
两个顶点着色器函数的主要区别,分条总结:
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对比
以下是 fragForwardBaseInternal
和 fragForwardBaseSimpleInternal
两个片段着色器函数的主要区别,分条列举如下:
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);
- 包含对实例ID的支持:
-
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
则更加简化,适用于基本的光照和材质计算,性能更高但功能相对较少。这两个函数的选择取决于具体的渲染需求和性能考量。