五、Standard Shader中正向基础渲染通道源码
Standard Shader正向渲染基础通道(Shader Model 3.0版)的Shader源代码:
//------------------------------------【子着色器1】------------------------------------
// 此子着色器用于Shader Model 3.0
//----------------------------------------------------------------------------------------
SubShader
{
//渲染类型设置:不透明
Tags { "RenderType"="Opaque" "PerformanceChecks"="False" }
//细节层次设为:300
LOD 300
//--------------------------------通道1-------------------------------
// 正向基础渲染通道(Base forward pass)
// 处理方向光,自发光,光照贴图等 ...
Pass
{
//设置通道名称
Name "FORWARD"
//于通道标签中设置光照模型为ForwardBase,正向渲染基础通道
Tags { "LightMode" = "ForwardBase" }
//混合操作:源混合乘以目标混合
Blend [_SrcBlend] [_DstBlend]
// 根据_ZWrite参数,设置深度写入模式开关与否
ZWrite [_ZWrite]
//===========开启CG着色器语言编写模块===========
CGPROGRAM
//着色器编译目标:Model 3.0
#pragma target 3.0
//编译指令:不使用GLES渲染器编译
#pragma exclude_renderers gles
// ---------编译指令:着色器编译多样化--------
#pragma shader_feature _NORMALMAP
#pragma shader_feature _ _ALPHATEST_ON _ALPHABLEND_ON _ALPHAPREMULTIPLY_ON
#pragma shader_feature _EMISSION
#pragma shader_feature _METALLICGLOSSMAP
#pragma shader_feature ___ _DETAIL_MULX2
#pragma shader_feature _PARALLAXMAP
//这边定义了很多的“宏”、 _NORMALMAP、_ALPHATEST_ON、_ALPHABLEND_ON、_EMISSION、_METALLICGLOSSMAP、_DETAIL_MULX2、_PARALLAXMAP,在顶点和片段着色器实现部分,可以用#ifdef _EMISSION类似的宏命令来对不同情况下的实现进行区别对待。
//--------着色器编译多样化快捷指令------------
//编译指令:编译正向渲染基础通道(用于正向渲染中,应用环境光照、主方向光照和顶点/球面调和光照)所需的所有变体。
//这些变体用于处理不同的光照贴图类型、主要方向光源的阴影选项的开关与否
#pragma multi_compile_fwdbase
//编译指令:编译几个不同变种来处理不同类型的雾效(关闭/线性/指数/二阶指数/)
#pragma multi_compile_fog
//编译指令:告知编译器顶点和片段着色函数的名称
#pragma vertex vertForwardBase
#pragma fragment fragForwardBase
//指明了这个pass中顶点着色函数和片段着色函数分别是名为vertForwardBase和fragForwardBase的函数
//包含辅助CG头文件
#include "UnityStandardCore.cginc"
//vertForwardBase和 fragForwardBase的函数全都定义于此“UnityStandardCore.cginc”头文件中
//===========结束CG着色器语言编写模块===========
ENDCG
}
……
}
5.1 顶点着色函数vertForwardBase
源码如下:
//-----------------------------------【vertForwardBase函数】---------------------------
// 用途:正向渲染基础通道的顶点着色函数
// 说明:实例化一个VertexOutputForwardBase结构体对象,并进行相应的填充
// 输入:VertexInput结构体
// 输出:VertexOutputForwardBase结构体
// 附:VertexInput结构体原型:
/*
struct VertexInput
{
float4 vertex : POSITION;
half3 normal : NORMAL;
float2 uv0 : TEXCOORD0;
float2 uv1 : TEXCOORD1;
#if defined(DYNAMICLIGHTMAP_ON) || defined(UNITY_PASS_META)
float2 uv2 : TEXCOORD2;
#endif
#ifdef _TANGENT_TO_WORLD
half4 tangent : TANGENT;
#endif
};
*/
//---------------------------------------------------------------------------------------------------------
VertexOutputForwardBase vertForwardBase (VertexInput v)
{
//【1】实例化一个VertexOutputForwardBase结构体对象
VertexOutputForwardBase o;
//用Unity内置的宏初始化参数
UNITY_INITIALIZE_OUTPUT(VertexOutputForwardBase, o);
//【2】通过物体坐标系到世界坐标系的变换矩阵乘以物体的顶点位置,得到对象在世界坐标系中的位置
float4 posWorld = mul(_Object2World, v.vertex);
//【3】若定义了镜面立方体投影宏,将计算得到的世界坐标系的xyz坐标作为输出参数的世界坐标值
#if UNITY_SPECCUBE_BOX_PROJECTION
o.posWorld = posWorld.xyz;
#endif
//【4】输出的顶点位置(像素位置)为模型视图投影矩阵乘以顶点位置,也就是将三维空间中的坐标投影到了二维窗口
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
//【5】计算纹理坐标,使用UnityStandardInput.cginc头文件中的辅助函数。
o.tex = TexCoords(v);
//【6】视线的方向= 对象在世界坐标系中的位置减去摄像机的世界空间位置,并进行逐顶点归一化
o.eyeVec = NormalizePerVertexNormal(posWorld.xyz - _WorldSpaceCameraPos);
//【7】计算物体在世界空间中的法线坐标
float3 normalWorld = UnityObjectToWorldNormal(v.normal);
//【8】进行世界空间中的切线相关参数的计算与赋值
//若定义了_TANGENT_TO_WORLD
#ifdef _TANGENT_TO_WORLD
//世界空间中的物体的法线值
float4 tangentWorld = float4(UnityObjectToWorldDir(v.tangent.xyz), v.tangent.w);
//在世界空间中为每个顶点创建切线
float3x3 tangentToWorld = CreateTangentToWorldPerVertex(normalWorld, tangentWorld.xyz, tangentWorld.w);
//分别为3个分量赋值
o.tangentToWorldAndParallax[0].xyz = tangentToWorld[0];
o.tangentToWorldAndParallax[1].xyz = tangentToWorld[1];
o.tangentToWorldAndParallax[2].xyz = tangentToWorld[2];
//否则,三个分量直接取为0,0和上面计算得到的normalWorld
#else
o.tangentToWorldAndParallax[0].xyz = 0;
o.tangentToWorldAndParallax[1].xyz = 0;
o.tangentToWorldAndParallax[2].xyz = normalWorld;
#endif
//【9】阴影的获取
TRANSFER_SHADOW(o);
//【10】进行顶点正向相关的全局光照操作
o.ambientOrLightmapUV = VertexGIForward(v, posWorld, normalWorld);
//【11】若定义了_PARALLAXMAP宏,则计算视差的视角方向并赋值
#ifdef _PARALLAXMAP
//声明一个由切线空间的基组成的3x3矩阵“rotation”
TANGENT_SPACE_ROTATION;
//计算视差的视角方向
half3 viewDirForParallax = mul (rotation, ObjSpaceViewDir(v.vertex));
//分别将三个分量赋值给VertexOutputForwardBase结构体对象o的tangentToWorldAndParallax的三个分量
o.tangentToWorldAndParallax[0].w = viewDirForParallax.x;
o.tangentToWorldAndParallax[1].w = viewDirForParallax.y;
o.tangentToWorldAndParallax[2].w = viewDirForParallax.z;
#endif
//【12】若定义了UNITY_OPTIMIZE_TEXCUBELOD,便计算反射光方向向量并赋值
#if UNITY_OPTIMIZE_TEXCUBELOD
//使用CG语言内置函数reflect计算反射光方向向量
o.reflUVW = reflect(o.eyeVec, normalWorld);
#endif
//【13】从顶点中输出雾数据
UNITY_TRANSFER_FOG(o,o.pos);
//【14】返回已经附好值的VertexOutputForwardBase类型的对象
return o;
}
5.2 顶点输入结构体VertexInput
此结构体定义于UnityStandardInput.cginc头文件中,是顶点着色函数vertForwardBase的输入参数
源码如下:
//顶点输入结构体
struct VertexInput
{
float4 vertex : POSITION;//位置坐标
half3 normal : NORMAL;//法线向量
float2 uv0 : TEXCOORD0;//一级纹理坐标
float2 uv1 : TEXCOORD1;//二级纹理坐标
//若DYNAMICLIGHTMAP_ON或者UNITY_PASS_META选项为开,则还定义一个三级纹理
#if defined(DYNAMICLIGHTMAP_ON) || defined(UNITY_PASS_META)
float2 uv2 : TEXCOORD2;//三级纹理
#endif
#ifdef _TANGENT_TO_WORLD
half4 tangent : TANGENT;//切线向量
#endif
};
5.3顶点输出结构体VertexOutputForwardBase、
VertexOutputForwardBase结构体就是正向基础渲染通道特有的输出结构体,定义于UnityStandardCore.cginc头文件中
源码如下:
//正向渲染基础通道的输出结构体
struct VertexOutputForwardBase
{
float4 pos : SV_POSITION;//像素坐标
float4 tex : TEXCOORD0;//一级纹理
half3 eyeVec : TEXCOORD1;//二级纹理(视线向量)
half4 tangentToWorldAndParallax[3] : TEXCOORD2; //3x3为切线到世界矩阵的值,1x3为视差方向的值
half4 ambientOrLightmapUV : TEXCOORD5; // 球谐函数(Spherical harmonics)或光照贴图的UV坐标
SHADOW_COORDS(6)//阴影坐标
UNITY_FOG_COORDS(7)//雾效坐标
//若定义了镜面立方体投影宏,定义一个posWorld
#if UNITY_SPECCUBE_BOX_PROJECTION
float3 posWorld : TEXCOORD8;
#endif
//若定义了优化纹理的立方体LOD宏,还将定义如下的参数reflUVW
#if UNITY_OPTIMIZE_TEXCUBELOD
#if UNITY_SPECCUBE_BOX_PROJECTION
half3 reflUVW : TEXCOORD9;
#else
half3 reflUVW : TEXCOORD8;
#endif
#endif
};
5.4 UNITY_INITIALIZE_OUTPUT宏
UNITY_INITIALIZE_OUTPUT(type,name) –此宏用于将给定类型的名称变量初始化为零。
5.5 _Object2World矩阵
_Object2World,Unity的内置矩阵,世界坐标系到对象坐标系的变换矩阵,简称“世界-对象矩阵”。
5.6 UNITY_MATRIX_MVP矩阵
UNITY_MATRIX_MVP为当前的模型矩阵x视图矩阵x投影矩阵,简称“模型-视图-投影矩阵”。其常用于在顶点着色函数中,通过将它和顶点位置相乘,从而可以把顶点位置从模型空间转换到裁剪空间(clip space)中。也就是通过此矩阵,将三维空间中的坐标投影到了二维窗口中。
5.7 TexCoords函数
TexCoords函数用于获取纹理坐标,定义UnityStandardInput.cginc头文件中,
源码如下:
float4 TexCoords(VertexInput v)
{
float4 texcoord;
texcoord.xy = TRANSFORM_TEX(v.uv0, _MainTex); // Always source from uv0
texcoord.zw = TRANSFORM_TEX(((_UVSec == 0) ? v.uv0 : v.uv1), _DetailAlbedoMap);
return texcoord;
}
函数实现代码中的_MainTex、_UVSec、_DetailAlbedoMap都是此头文件定义的全局的变量。
其中还涉及到了一个TRANSFORM_TEX宏,在这边也提一下,它定义于UnityCG.cginc头文件中,
源码如下:
// 按比例和偏移进行二维UV坐标的变换
#define TRANSFORM_TEX(tex,name) (tex.xy *name##_ST.xy + name##_ST.zw)
5.8 NormalizePerVertexNormal函数
此函数位于unitystandardcore.cginc头文件中
源码如下:
//--------------------------【函数NormalizePerVertexNormal】-------------------------
// 用途:归一化每顶点法线
// 说明:若满足特定条件,便归一化每顶点法线并返回,否则,直接返回原始值
// 输入:half3类型的法线坐标
// 输出:若满足判断条件,返回half3类型的、经过归一化后的法线坐标,否则返回输入的值
//-----------------------------------------------------------------------------------------------
half3 NormalizePerVertexNormal (half3 n)
{
//满足着色目标模型的版本小于Shader Model 3.0,或者定义了UNITY_STANDARD_SIMPLE宏,返回归一化后的值
#if (SHADER_TARGET < 30) || UNITY_STANDARD_SIMPLE
return normalize(n);
//否则,直接返回输入的参数,后续应该会进行逐像素的归一化
#else
return n;
#endif
}
其中,SHADER_TARGET宏代表的值为和着色器的目标编译模型(shader model)相关的一个数值。
5.9 UnityObjectToWorldNormal函数
UnityObjectToWorldNormal是Unity内置的函数,可以将法线从模型空间变换到世界空间中,定义于UnityCG.cginc头文件中
源码如下:
//将法线从模型空间变换到世界空间
inline float3 UnityObjectToWorldNormal( in float3 norm )
{
// 将分量分别相乘,并进行归一化
//Multiply by transposed inverse matrix, actually using transpose() generates badly optimized code
return normalize(_World2Object[0].xyz * norm.x + _World2Object[1].xyz * norm.y + _World2Object[2].xyz * norm.z);
}
其中的normalize( )函数很常见,是来自CG语言中的函数,作用是归一化向量。
5.10 UnityObejctToWorldDir函数
UnityObjectToWorldDir函数用于方向值从物体空间切换到世界空间,定义于UnityCG.cginc头文件中
源码如下
//将方向值从物体空间切换到世界空间
inline float3 UnityObjectToWorldDir( in float3 dir )
{
return normalize(mul((float3x3)_Object2World, dir));
}
5.11 CreateTangentToWorldPerVertex函数
CreateTangentToWorldPerVertex函数用于在世界空间中为每个顶点创建切线,定义于UnityStandardUtils.cginc头文件中
源码如下:
half3x3 CreateTangentToWorldPerVertex(half3 normal, half3 tangent, half tangentSign)
{
//对于奇数负比例变换,我们需要将符号反向||For odd-negative scale transforms we need to flip the sign
half sign = tangentSign * unity_WorldTransformParams.w;
half3 binormal = cross(normal, tangent) * sign;
return half3x3(tangent, binormal, normal);
}
其中的unity_WorldTransformParams是UnityShaderVariables.cginc头文件中定义的一个uniform float4型的变量,其w分量用于标定奇数负比例变换(odd-negativescale transforms),通常取值为1.0或者-1.0。
5.12 TRANSFER_SHADOW(a)宏
此宏用于进行阴影在各种空间中的转换,定义于AutoLight.cginc中。在不同的情况下,此宏代表的意义并不相同。
1 Screen space shadows(对于屏幕空间中的阴影)
对应于屏幕空间中的阴影,也就是#if defined (SHADOWS_SCREEN)
源码如下:
#if defined (SHADOWS_SCREEN)
……
#if defined(UNITY_NO_SCREENSPACE_SHADOWS)
#define TRANSFER_SHADOW(a) a._ShadowCoord = mul( unity_World2Shadow[0], mul( _Object2World, v.vertex ) );
#else // not UNITY_NO_SCREENSPACE_SHADOWS
#define TRANSFER_SHADOW(a) a._ShadowCoord = ComputeScreenPos(a.pos);
……
#endif
将世界-阴影坐标乘以世界-模型坐标和物体顶点坐标的积,也就是先将物体坐标转换成世界坐标,再将世界坐标转换成阴影坐标,并将结果存放于a._ShadowCoord中。
2.Spor light shadows(对于聚光灯阴影)
对于聚光灯的阴影,也就是#if defined (SHADOWS_DEPTH)&& defined (SPOT)
#if defined (SHADOWS_DEPTH) && defined (SPOT)
#define TRANSFER_SHADOW(a) a._ShadowCoord = mul (unity_World2Shadow[0], mul(_Object2World,v.vertex));
……
#endif
用途就是先将物体坐标转换成世界坐标,再将世界坐标转换成阴影坐标,并将结果存放于a._ShadowCoord中。
3.Point light shadows(关于点光源阴影)
#if defined (SHADOWS_CUBE),有如下定义:
#if defined (SHADOWS_CUBE)
#define TRANSFER_SHADOW(a) a._ShadowCoord = mul(_Object2World, v.vertex).xyz - _LightPositionRange.xyz;
……
#endif
也就是说,这种情况下的TRANSFER_SHADOW(a)宏代表语句a._ShadowCoord = mul(_Object2World, v.vertex).xyz -_LightPositionRange.xyz;
想了解此代码的含义,先要知道_LightPositionRange变量的含义。
这个变量是UnityShaderVariables.cginc头文件中定义的一个全局变量:
uniform float4 _LightPositionRange; // xyq= pos, w = 1/range
此参数的x,y,z分量表示世界空间下光源的坐标,而w为世界空间下范围的倒数。
就是先将物体-世界矩阵乘以物体顶点坐标,得到物体的世界空间坐标,然后取坐标的xyz分量,与光源的坐标相减,并将结果赋给a._ShadowCoord。
4.Shadow off(关闭阴影)
对于关闭阴影的情况,也就是#if !defined (SHADOWS_SCREEN)&& !defined (SHADOWS_DEPTH) && !defined (SHADOWS_CUBE),有如下定义:
#if !defined (SHADOWS_SCREEN) && !defined (SHADOWS_DEPTH) && !defined (SHADOWS_CUBE)
#define TRANSFER_SHADOW(a)
……
#endif
这种情况下的TRANSFER_SHADOW(a)宏代表的是空白
5.13 VertexGlForward函数
定义于UnityStandardCore.cginc头文件中
源码如下:
//顶点正向全局光照函数
inline half4 VertexGIForward(VertexInput v, float3 posWorld, half3 normalWorld)
{
//【1】定义一个half4型的ambientOrLightmapUV变量,并将四个分量都置为0
half4 ambientOrLightmapUV = 0;
//【2】对ambientOrLightmapUV变量的四个分量赋值
// 【2-1】若没有定义LIGHTMAP_OFF(关闭光照贴图)宏,也就是此情况下启用静态的光照贴图,则计算对应的光照贴图坐标
//static lightmap
#ifndef LIGHTMAP_OFF
ambientOrLightmapUV.xy = v.uv1.xy * unity_LightmapST.xy + unity_LightmapST.zw;
ambientOrLightmapUV.zw = 0;
//【2-2】若定义了UNITY_SHOULD_SAMPLE_SH宏,则表示对动态的对象采样(不对静态或者动态的光照贴图采样)
// || Sample light probe for Dynamic objects only (no static or dynamic lightmaps)
#elif UNITY_SHOULD_SAMPLE_SH
//【2-2-1】若定义了如下的UNITY_SAMPLE_FULL_SH_PER_PIXEL宏(即采样计算全部的每像素球面调和光照),便给ambientOrLightmapUV.rgb赋值为0
#if UNITY_SAMPLE_FULL_SH_PER_PIXEL
ambientOrLightmapUV.rgb = 0;
//【2-2-2】若满足着色目标模型的版本小于Shader Model 3.0,或者定义了UNITY_STANDARD_SIMPLE宏
//便使用球面调和函数ShadeSH9给ambientOrLightmapUV.rgb赋值
#elif (SHADER_TARGET < 30) || UNITY_STANDARD_SIMPLE
ambientOrLightmapUV.rgb = ShadeSH9(half4(normalWorld, 1.0));
//【2-2-3】否则,使用三序球面调和函数ShadeSH3Order给ambientOrLightmapUV.rgb赋值
#else
//优化操作:光源L0、L1逐像素,光源L2逐顶点 || Optimization: L2 per-vertex, L0..L1 per-pixel
ambientOrLightmapUV.rgb = ShadeSH3Order(half4(normalWorld, 1.0));
#endif
//【2-2-4】 从非重要的点光源中添加近似的照明 || Add approximated illumination from non-important point lights
//若定义了如下的VERTEXLIGHT_ON宏(即开启顶点光照),便使用Shade4PointLights函数给ambientOrLightmapUV.rgb赋值,添加环境光
#ifdef VERTEXLIGHT_ON
// Shade4PointLights为Unity内置的逐顶点光照处理函数,定义于unityCG.cginc头文件中
ambientOrLightmapUV.rgb += Shade4PointLights (
unity_4LightPosX0, unity_4LightPosY0, unity_4LightPosZ0,
unity_LightColor[0].rgb, unity_LightColor[1].rgb, unity_LightColor[2].rgb, unity_LightColor[3].rgb,
unity_4LightAtten0, posWorld, normalWorld);
#endif
#endif
//【2-3】若定义了如下的VERTEXLIGHT_ONDYNAMICLIGHTMAP_ON宏(即开启动态光照贴图),则给变量的zw分量赋值
#ifdef DYNAMICLIGHTMAP_ON
ambientOrLightmapUV.zw = v.uv2.xy * unity_DynamicLightmapST.xy + unity_DynamicLightmapST.zw;
#endif
//【3】返回ambientOrLightmapUV变量的值
return ambientOrLightmapUV;
}
1.unity_LightmapST变量
unity_LightmapST变量类型为float4型,定义于UnityShaderVariables.cginc头文件中,存放着光照贴图操作的参数的值
float4 unity_LightmapST
2.UNITY_SHOULD_SAMPLE_SH宏
定义于UnityCG.cginc中
源码如下:
//包含间接漫反射的动态&静态光照贴图,所以忽略掉球面调和光照 || Dynamic & Static lightmaps contain indirect diffuse ligthing, thus ignore SH
#define UNITY_SHOULD_SAMPLE_SH ( defined (LIGHTMAP_OFF) && defined(DYNAMICLIGHTMAP_OFF) )
就是将LIGHTMAP_OFF(关闭光照贴图)宏和DYNAMICLIGHTMAP_OFF(关闭动态光照贴图)宏的定义进行了封装
3.UNITY_SAMPLE_FULL_SH_PER_PIXEL宏
UNITY_SAMPLE_FULL_SH_PER_PIXEL宏定义于UnityStandardConfig.cginc头文件中。其实也就是一个标识符,用0标示UNITY_SAMPLE_FULL_SH_PER_PIXEL宏是否已经定义。按字面上理解,启用此宏表示我们将采样计算每像素球面调和光照,而不是默认的逐顶点计算球面调和光照并且线性插值到每像素中。
源码如下:
#ifndef UNITY_SAMPLE_FULL_SH_PER_PIXEL
#define UNITY_SAMPLE_FULL_SH_PER_PIXEL 0
#endif
4.ShadeSH9函数
ShadeSH9即球面调和函数,定义于UnityCG.cginc头文件中
源码如下:
//球面调和函数
//法线需被初始化,w=1.0 || normal should be normalized, w=1.0
half3 ShadeSH9 (half4 normal)
{
half3 x1, x2, x3;
//线性+常数多项式 || Linear + constant polynomial terms
x1.r = dot(unity_SHAr,normal);
x1.g = dot(unity_SHAg,normal);
x1.b = dot(unity_SHAb,normal);
//二次多项式的四个参数 || 4 of the quadratic polynomials
half4 vB = normal.xyzz * normal.yzzx;
x2.r = dot(unity_SHBr,vB);
x2.g = dot(unity_SHBg,vB);
x2.b = dot(unity_SHBb,vB);
//最终二次多项式 || Final quadratic polynomial
half vC = normal.x*normal.x - normal.y*normal.y;
x3 = unity_SHC.rgb * vC;
return x2 + x3 + x1;
}
5.ShadeSH3Order函数
定义于UnityCG.cginc头文件中
源码如下:
//三序球面调和函数
//法线需被初始化,w=1.0 || normal should be normalized, w=1.0
half3 ShadeSH3Order(half4 normal)
{
half3 x2, x3;
//二次多项式的四个参数 || 4 of the quadratic polynomials
half4 vB = normal.xyzz * normal.yzzx;
x2.r = dot(unity_SHBr,vB);
x2.g = dot(unity_SHBg,vB);
x2.b = dot(unity_SHBb,vB);
//最终的二次多项式 || Final quadratic polynomial
half vC = normal.x*normal.x - normal.y*normal.y;
x3 = unity_SHC.rgb * vC;
return x2 + x3;
}
6.Shade4PointLights函数
Shade4PointLights为逐顶点光照处理函数,定义于unityCG.cginc头文件中
//在正向基础渲染通道中使用,根据4个不同的点光源计算出漫反射光照参数的rgb值|| Used in ForwardBase pass: Calculates diffuse lighting from 4 point lights, with data packed in a special way.
float3 Shade4PointLights (
float4 lightPosX, float4 lightPosY, float4 lightPosZ,
float3 lightColor0, float3 lightColor1, float3 lightColor2, float3 lightColor3,
float4 lightAttenSq,
float3 pos, float3 normal)
{
// 【1】将输入参数转换为光照矢量 || to light vectors
float4 toLightX = lightPosX - pos.x;
float4 toLightY = lightPosY - pos.y;
float4 toLightZ = lightPosZ - pos.z;
// 【2】计算平方的值 || squared lengths
float4 lengthSq = 0;
lengthSq += toLightX * toLightX;
lengthSq += toLightY * toLightY;
lengthSq += toLightZ * toLightZ;
// 【3】法线方向点乘光线方向|| NdotL
float4 ndotl = 0;
ndotl += toLightX * normal.x;
ndotl += toLightY * normal.y;
ndotl += toLightZ * normal.z;
// 【4】修正NdotL(法线方向点乘光线方向)的值 || correct NdotL
float4 corr = rsqrt(lengthSq);
ndotl = max (float4(0,0,0,0), ndotl * corr);
// 【5】计算衰减系数 || attenuation
float4 atten = 1.0 / (1.0 + lengthSq * lightAttenSq);
float4 diff = ndotl * atten;
// 【6】得到最终的颜色 || final color
float3 col = 0;
col += lightColor0 * diff.x;
col += lightColor1 * diff.y;
col += lightColor2 * diff.z;
col += lightColor3 * diff.w;
return col;
}
5.14 TANGENT_SPACE_ROTATION宏
TANGENT_SPACE_ROTATION宏定义于UnityCG.cginc中,作用是声明一个由切线空间的基组成的3x3矩阵
源码如下:
//声明一个由切线空间的基组成的3x3矩阵 || Declares 3x3 matrix 'rotation', filled with tangent space basis
#define TANGENT_SPACE_ROTATION \
float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w; \
float3x3 rotation = float3x3( v.tangent.xyz, binormal, v.normal )
使用TANGENT_SPACE_ROTATION宏也就表示定义了上述代码所示的float3 类型的binormal和float3x3类型的rotation两个变量。且其中的rotation为3x3的矩阵,由切线空间的基组成。可以使用它把物体空间转换到切线空间中。
5.15 UNITY_OPTIMIZE_TEXCUBELOD宏
用0标识是否开启此功能
源码如下:
#ifndef UNITY_OPTIMIZE_TEXCUBELOD
#define UNITY_OPTIMIZE_TEXCUBELOD 0
#endif
5.16 reflect函数
reflect函数是CG语言的内置函数。
reflect(I, N) 根据入射光方向向量I,和顶点法向量N,计算反射光方向向量。其中I 和N必须被归一化,需要特别注意的是,这个I 是指向顶点的;且此函数只对三元向量有效。
5.17 UNITY_TRANSFER_FOG宏
UNITY_TRANSFER_FOG宏相关代码定义于UnityCG.Cginc头文件中
源码如下:
//【0】实现不同版本的UNITY_CALC_FOG_FACTOR宏。
#if defined(FOG_LINEAR)
// factor = (end-z)/(end-start) = z * (-1/(end-start)) + (end/(end-start))
#define UNITY_CALC_FOG_FACTOR(coord) float unityFogFactor = (coord) * unity_FogParams.z + unity_FogParams.w
#elif defined(FOG_EXP)
// factor = exp(-density*z)
#define UNITY_CALC_FOG_FACTOR(coord) float unityFogFactor = unity_FogParams.y * (coord); unityFogFactor = exp2(-unityFogFactor)
#elif defined(FOG_EXP2)
// factor = exp(-(density*z)^2)
#define UNITY_CALC_FOG_FACTOR(coord) float unityFogFactor = unity_FogParams.x * (coord); unityFogFactor = exp2(-unityFogFactor*unityFogFactor)
#else
#define UNITY_CALC_FOG_FACTOR(coord) float unityFogFactor = 0.0
#endif
//【1】若已经定义了FOG_LINEAR、FOG_EXP、FOG_EXP2宏三者之中至少之一,便可以进行到此#if实现部分
#if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
//【1-1】定义UNITY_FOG_COORDS(idx)宏
#define UNITY_FOG_COORDS(idx) float fogCoord : TEXCOORD##idx;
//【1-2】定义UNITY_TRANSFER_FOG(o,outpos)宏
//【1-2-1】若满足着色目标模型的版本小于Shader Model 3.0,或者定义了SHADER_API_MOBILE宏,便可以进行到此#if实现部分
//UNITY_CALC_FOG_FACTOR宏的实现见上
#if (SHADER_TARGET < 30) || defined(SHADER_API_MOBILE)
// 移动平台和Shader Model 2.0:计算每顶点的雾效因子 || mobile or SM2.0: calculate fog factor per-vertex
#define UNITY_TRANSFER_FOG(o,outpos) UNITY_CALC_FOG_FACTOR((outpos).z); o.fogCoord = unityFogFactor
//【1-2-2】否则
#else
// Shader Model 3.0和PC/游戏主机平台:计算每顶点的雾距离,以及每像素雾效因子 || SM3.0 and PC/console: calculate fog distance per-vertex, and fog factor per-pixel
#define UNITY_TRANSFER_FOG(o,outpos) o.fogCoord = (outpos).z
#endif
//【2】否则,直接用UNITY_FOG_COORDS宏计算雾效参数
#else
#define UNITY_FOG_COORDS(idx)
#define UNITY_TRANSFER_FOG(o,outpos)
#endif
5.18 fragForwardBase(片段着色器函数)
源码如下:
//----------------------------------------【fragForwardBase函数】----------------------
// 用途:正向渲染基础通道的片段着色函数
// 输入:VertexOutputForwardBase结构体
// 输出:一个half4类型的颜色值
//------------------------------------------------------------------------------------------------------------------
half4 fragForwardBase (VertexOutputForwardBase i) : SV_Target
{
//定义并初始化类型为FragmentCommonData的变量s
FRAGMENT_SETUP(s)
//若定义了UNITY_OPTIMIZE_TEXCUBELOD,则由输入的顶点参数来设置反射光方向向量
#if UNITY_OPTIMIZE_TEXCUBELOD
s.reflUVW = i.reflUVW;
#endif
//设置主光照
UnityLight mainLight = MainLight (s.normalWorld);
//设置阴影的衰减系数
half atten = SHADOW_ATTENUATION(i);
//计算全局光照
half occlusion = Occlusion(i.tex.xy);
UnityGI gi = FragmentGI (s, occlusion, i.ambientOrLightmapUV, atten, mainLight);
//加上BRDF-基于物理的光照
half4 c = UNITY_BRDF_PBS (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, gi.light, gi.indirect);
//加上BRDF-全局光照
c.rgb += UNITY_BRDF_GI (s.diffColor, s.specColor, s.oneMinusReflectivity, s.oneMinusRoughness, s.normalWorld, -s.eyeVec, occlusion, gi);
//加上自发光
c.rgb += Emission(i.tex.xy);
//设置雾效
UNITY_APPLY_FOG(i.fogCoord, c.rgb);
//返回最终的颜色
return OutputForward (c, s.alpha);
}
1.FRAGMENT_SETUP(x) 宏
FRAGMENT_SETUP(x)宏定义于UnityStandardCore.cginc头文件中,其作用其实就是用FragmentSetup函数初始化括号中的x变量。
#define FRAGMENT_SETUP(x) FragmentCommonData x = \
FragmentSetup(i.tex, i.eyeVec, IN_VIEWDIR4PARALLAX(i), i.tangentToWorldAndParallax, IN_WORLDPOS(i));
调用此宏,也就是表示写了如下的代码,定义了一个x变量:
FragmentCommonData x =FragmentSetup(i.tex, i.eyeVec, IN_VIEWDIR4PARALLAX(i), i.tangentToWorldAndParallax, IN_WORLDPOS(i));
其中FragmentSetup函数也定义于UnityStandardCore.cginc头文件中,用于填充一个FragmentCommonData结构体并于返回值中返回,也就是进行片段函数相关参数的初始化
源码如下:
//函数FragmentSetup:填充一个FragmentCommonData结构体并于返回值中返回,进行片段函数相关参数的初始化
inline FragmentCommonData FragmentSetup (float4 i_tex, half3 i_eyeVec, half3 i_viewDirForParallax, half4 tangentToWorld[3], half3 i_posWorld)
{
i_tex = Parallax(i_tex, i_viewDirForParallax);
half alpha = Alpha(i_tex.xy);
#if defined(_ALPHATEST_ON)
clip (alpha - _Cutoff);
#endif
FragmentCommonData o = UNITY_SETUP_BRDF_INPUT (i_tex);
o.normalWorld = PerPixelWorldNormal(i_tex, tangentToWorld);
o.eyeVec = NormalizePerPixelNormal(i_eyeVec);
o.posWorld = i_posWorld;
// NOTE: shader relies on pre-multiply alpha-blend (_SrcBlend = One, _DstBlend = OneMinusSrcAlpha)
o.diffColor = PreMultiplyAlpha (o.diffColor, alpha, o.oneMinusReflectivity, /*out*/ o.alpha);
return o;
}
其中的FragmentCommonData结构体也是定义于UnityStandardCore.cginc头文件中:
//FragmentCommonData结构体:存放片段着色常用变量
struct FragmentCommonData
{
half3 diffColor, specColor;//漫反射颜色;镜面反射颜色
// Note: oneMinusRoughness & oneMinusReflectivity for optimization purposes, mostly for DX9 SM2.0 level.
// Most of the math is being done on these (1-x) values, and that saves a few precious ALU slots.
half oneMinusReflectivity, oneMinusRoughness;//1减去反射率;1减去粗糙度
half3 normalWorld, eyeVec, posWorld;//世界空间中的法线向量坐标;视角向量坐标;在世界坐标中的位置坐标
half alpha;//透明度
#if UNITY_OPTIMIZE_TEXCUBELOD || UNITY_STANDARD_SIMPLE
half3 reflUVW;//反射率的UVW
#endif
#if UNITY_STANDARD_SIMPLE
half3 tangentSpaceNormal;//切线空间中的法线向量
#endif
};
2.MainLight函数
MainLight函数定义于UnityStandardCore.cginc头文件中,用途是实例化一个UnityLight结构体对象,并进行相应的填充,其返回值作为主光源
源码如下:
// 用途:该函数为主光照函数
// 说明:实例化一个UnityLight结构体对象,并进行相应的填充
/*
//注:UnityLight结构体定义于UnityLightingCommon.cginc文件中,原型如下:
struct UnityLight
{
half3 color;
half3 dir;
half ndotl;
};
*/
//------------------------------------【函数3】MainLight函数-----------------------------------------
// 用途:该函数为主光照函数
// 说明:实例化一个UnityLight结构体对象,并进行相应的填充
//---------------------------------------------------------------------------------------------------------
UnityLight MainLight (half3 normalWorld)
{
//【1】实例化一个UnityLight的对象
UnityLight l;
//【2】填充UnityLight的各个参数
//若光照贴图选项为关,使用Unity内置变量赋值
#ifdef LIGHTMAP_OFF
//获取光源的颜色
l.color = _LightColor0.rgb;
//获取光源的方向
l.dir = _WorldSpaceLightPos0.xyz;
//获取法线与光源方向的点乘的积
l.ndotl = LambertTerm (normalWorld, l.dir);
//光照贴图选项为开,将各项值设为0
#else
l.color = half3(0.f, 0.f, 0.f);
l.ndotl = 0.f;
l.dir = half3(0.f, 0.f, 0.f);
#endif
//返回赋值完成的UnityLight结构体对象
return l;
}
3.SHADOW_ATTENUATION宏
SHADOW_ATTENUATION宏相关的代码位于AutoLight.cginc头文件中,用于实现阴影渲染相关的辅助工作
源码如下:
// ----------------
// 阴影相关工具代码 || Shadow helpers
// ----------------
// ---- 屏幕空间阴影 || Screen space shadows
#if defined (SHADOWS_SCREEN)
……
#define SHADOW_ATTENUATION(a) unitySampleShadow(a._ShadowCoord)
#endif
// ----聚光灯光源阴影 || Spot light shadows
#if defined (SHADOWS_DEPTH) && defined (SPOT)
#define SHADOW_COORDS(idx1) unityShadowCoord4 _ShadowCoord : TEXCOORD##idx1;
#define TRANSFER_SHADOW(a) a._ShadowCoord = mul (unity_World2Shadow[0], mul(_Object2World,v.vertex));
#define SHADOW_ATTENUATION(a) UnitySampleShadowmap(a._ShadowCoord)
#endif
// ----点光源阴影 || Point light shadows
#if defined (SHADOWS_CUBE)
#define SHADOW_COORDS(idx1) unityShadowCoord3 _ShadowCoord : TEXCOORD##idx1;
#define TRANSFER_SHADOW(a) a._ShadowCoord = mul(_Object2World, v.vertex).xyz - _LightPositionRange.xyz;
#define SHADOW_ATTENUATION(a) UnitySampleShadowmap(a._ShadowCoord)
#endif
// ---- 关闭阴影 || Shadows off
#if !defined (SHADOWS_SCREEN) && !defined (SHADOWS_DEPTH) && !defined (SHADOWS_CUBE)
#define SHADOW_COORDS(idx1)
#define TRANSFER_SHADOW(a)
#define SHADOW_ATTENUATION(a) 1.0
#endif
SHADOW_ATTENUATION(a)宏除了在关闭阴影的状态是等于1以外,其他几种情况都是等价于UnitySampleShadowmap(a._ShadowCoord)函数的调用。而这里的UnitySampleShadowmap函数,定于于UnityShadowLibrary.cginc函数中
源码如下:
//------------------------------【UnitySampleShadowmap函数】-----------------------
// 用途:采样阴影贴图,得到阴影衰减值
// 输入参数: float3型的阴影向量坐标vec
// 返回值:阴影衰减值
//-------------------------------------------------------------------------------------------------------
inline half UnitySampleShadowmap (float3 vec)
{
float mydist = length(vec) * _LightPositionRange.w;
mydist *= 0.97; // bias
#if defined (SHADOWS_SOFT)
float z = 1.0/128.0;
float4 shadowVals;
shadowVals.x = SampleCubeDistance (vec+float3( z, z, z));
shadowVals.y = SampleCubeDistance (vec+float3(-z,-z, z));
shadowVals.z = SampleCubeDistance (vec+float3(-z, z,-z));
shadowVals.w = SampleCubeDistance (vec+float3( z,-z,-z));
half4 shadows = (shadowVals < mydist.xxxx) ? _LightShadowData.rrrr : 1.0f;
return dot(shadows,0.25);
#else
float dist = SampleCubeDistance (vec);
return dist < mydist ? _LightShadowData.r : 1.0;
#endif
}
4.Occlusion函数
Occlusion函数用于进行全局光照的第一步。其输入参数为一个float2型的纹理坐标,而其half型的返回值将作为FragmentGI函数的一个输入参数。
源码如下:
half Occlusion(float2 uv)
{
#if (SHADER_TARGET < 30)
// SM20: instruction count limitation
// SM20: simpler occlusion
return tex2D(_OcclusionMap, uv).g;
#else
half occ = tex2D(_OcclusionMap, uv).g;
return LerpOneTo (occ, _OcclusionStrength);
#endif
}
其中的LerpOneTo函数很简单,用于线性插值,输入两个值b和t,返回1+(b-1)*t,具体定义如下:
half LerpOneTo(half b, half t)
{
half oneMinusT = 1 - t;
return oneMinusT + b * t;
}
5.UnityGl结构体
UnityGI结构体是Unity中存放全局光照光源信息的结构体,定义于UnityLightingCommon.cginc头文件中
源码如下:
//全局光照结构体
struct UnityGI
{
UnityLight light;//定义第一个光源参数结构体,表示第一个光源
//若定义了DIRLIGHTMAP_SEPARATE(单独的方向光源光照贴图)
#ifdef DIRLIGHTMAP_SEPARATE
//若定义了LIGHTMAP_ON(打开光照贴图)
#ifdef LIGHTMAP_ON
UnityLight light2;//定义第二个光源参数结构体,表示第二个光源
#endif
//若定义了DYNAMICLIGHTMAP_ON(打开动态光照贴图)
#ifdef DYNAMICLIGHTMAP_ON
UnityLight light3;//定义第三个光源参数结构体,表示第三个光源
#endif
#endif
UnityIndirect indirect;//Unity中间接光源参数的结构体
};
其中包含了UnityLight结构体和UnityIndirect结构体,其中UnityLight结构体是Unity Shader中最基本的光照结构体,而UnityIndirect是Unity中存放间接光源信息的结构体。它们两者也定义于UnityLightingCommon.cginc头文件中
源码如下:
//Unity中光源参数的结构体
struct UnityLight
{
half3 color;//光源颜色
half3 dir;//光源方向
half ndotl; //入射光方向和当前表面法线方向的点积
};
//Unity中间接光源参数的结构体
struct UnityIndirect
{
half3 diffuse;//漫反射颜色
half3 specular;//镜面反射颜色
};
6.FragmentGl函数
FragmentGI函数是片段着色部分全局光照的处理函数,定义于UnityStandardCore.cginc头文件中
源码如下:
//函数:片段着色部分全局光照的处理函数
inline UnityGI FragmentGI (FragmentCommonData s, half occlusion, half4 i_ambientOrLightmapUV, half atten, UnityLight light, bool reflections)
{
//【1】实例化一个UnityGIInput的对象
UnityGIInput d;
//【2】填充此UnityGIInput对象的各个值
d.light = light;
d.worldPos = s.posWorld;
d.worldViewDir = -s.eyeVec;
d.atten = atten;
#if defined(LIGHTMAP_ON) || defined(DYNAMICLIGHTMAP_ON)
d.ambient = 0;
d.lightmapUV = i_ambientOrLightmapUV;
#else
d.ambient = i_ambientOrLightmapUV.rgb;
d.lightmapUV = 0;
#endif
d.boxMax[0] = unity_SpecCube0_BoxMax;
d.boxMin[0] = unity_SpecCube0_BoxMin;
d.probePosition[0] = unity_SpecCube0_ProbePosition;
d.probeHDR[0] = unity_SpecCube0_HDR;
d.boxMax[1] = unity_SpecCube1_BoxMax;
d.boxMin[1] = unity_SpecCube1_BoxMin;
d.probePosition[1] = unity_SpecCube1_ProbePosition;
d.probeHDR[1] = unity_SpecCube1_HDR;
//【3】根据填充好的UnityGIInput结构体对象,调用一下UnityGlobalIllumination函数
if(reflections)
{
Unity_GlossyEnvironmentData g;
g.roughness = 1 - s.oneMinusRoughness;
#if UNITY_OPTIMIZE_TEXCUBELOD || UNITY_STANDARD_SIMPLE
g.reflUVW = s.reflUVW;
#else
g.reflUVW = reflect(s.eyeVec, s.normalWorld);
#endif
return UnityGlobalIllumination (d, occlusion, s.normalWorld, g);
}
else
{
return UnityGlobalIllumination (d, occlusion, s.normalWorld);
}
}
inline UnityGI FragmentGI (FragmentCommonData s, half occlusion, half4 i_ambientOrLightmapUV, half atten, UnityLight light)
{
return FragmentGI(s, occlusion, i_ambientOrLightmapUV, atten, light, true);
}
其中的UnityGIInput结构体定义了全局光照所需要的一些函数,定义如下:
//全局光照的输入参数结构体
struct UnityGIInput
{
UnityLight light; // 像素光源,由引擎准备并传输过来 || pixel light, sent from the engine
float3 worldPos;//世界空间中的位置坐标
half3 worldViewDir;//世界空间中的视角方向向量坐标
half atten;//衰减值
half3 ambient;//环境光颜色
half4 lightmapUV; //光照贴图的UV坐标,其中 取.xy = static lightmapUV(静态光照贴图的UV) , .zw = dynamic lightmap UV(动态光照贴图的UV)
float4 boxMax[2];//box最大值
float4 boxMin[2];//box最小值
float4 probePosition[2];//光照探针的位置
float4 probeHDR[2];//光照探针的高动态范围图像(High-Dynamic Range)
};
UnityGIInput 中还包含了UnityLight结构体
FragmentGI函数最终利用了UnityGlobalIllumination函数,其定义于UnityGlobalIllumination.cginc头文件中,实现如下:
inline UnityGI UnityGlobalIllumination (UnityGIInput data, half occlusion, half3 normalWorld)
{
return UnityGI_Base(data, occlusion, normalWorld);
}
此FragmentGI函数就是嵌套了一层UnityGI_Base函数位于UnityGlobalIllumination.cginc头文件中,源码如下:
//UnityGI_Base函数:Unity的全局光照Base版
inline UnityGI UnityGI_Base(UnityGIInput data, half occlusion, half3 normalWorld)
{
//【1】实例化一个UnityGI类型的结构体
UnityGI o_gi;
//【2】重置此UnityGI的结构体
ResetUnityGI(o_gi);
//【3】开始逐个填充参数
#if !defined(LIGHTMAP_ON)
o_gi.light = data.light;
o_gi.light.color *= data.atten;
#endif
#if UNITY_SHOULD_SAMPLE_SH
#if UNITY_SAMPLE_FULL_SH_PER_PIXEL
half3 sh = ShadeSH9(half4(normalWorld, 1.0));
#elif (SHADER_TARGET >= 30) && !UNITY_STANDARD_SIMPLE
half3 sh = data.ambient + ShadeSH12Order(half4(normalWorld, 1.0));
#else
half3 sh = data.ambient;
#endif
o_gi.indirect.diffuse = sh;
#endif
#if defined(LIGHTMAP_ON)
// Baked lightmaps
fixed4 bakedColorTex = UNITY_SAMPLE_TEX2D(unity_Lightmap, data.lightmapUV.xy);
half3 bakedColor = DecodeLightmap(bakedColorTex);
#ifdef DIRLIGHTMAP_OFF
o_gi.indirect.diffuse = bakedColor;
#ifdef SHADOWS_SCREEN
o_gi.indirect.diffuse = MixLightmapWithRealtimeAttenuation (o_gi.indirect.diffuse, data.atten, bakedColorTex);
#endif // SHADOWS_SCREEN
#elif DIRLIGHTMAP_COMBINED
fixed4 bakedDirTex = UNITY_SAMPLE_TEX2D_SAMPLER (unity_LightmapInd, unity_Lightmap, data.lightmapUV.xy);
o_gi.indirect.diffuse = DecodeDirectionalLightmap (bakedColor, bakedDirTex, normalWorld);
#ifdef SHADOWS_SCREEN
o_gi.indirect.diffuse = MixLightmapWithRealtimeAttenuation (o_gi.indirect.diffuse, data.atten, bakedColorTex);
#endif // SHADOWS_SCREEN
#elif DIRLIGHTMAP_SEPARATE
// Left halves of both intensity and direction lightmaps store direct light; right halves - indirect.
// Direct
fixed4 bakedDirTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_LightmapInd, unity_Lightmap, data.lightmapUV.xy);
o_gi.indirect.diffuse = DecodeDirectionalSpecularLightmap (bakedColor, bakedDirTex, normalWorld, false, 0, o_gi.light);
// Indirect
half2 uvIndirect = data.lightmapUV.xy + half2(0.5, 0);
bakedColor = DecodeLightmap(UNITY_SAMPLE_TEX2D(unity_Lightmap, uvIndirect));
bakedDirTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_LightmapInd, unity_Lightmap, uvIndirect);
o_gi.indirect.diffuse += DecodeDirectionalSpecularLightmap (bakedColor, bakedDirTex, normalWorld, false, 0, o_gi.light2);
#endif
#endif
#ifdef DYNAMICLIGHTMAP_ON
// Dynamic lightmaps
fixed4 realtimeColorTex = UNITY_SAMPLE_TEX2D(unity_DynamicLightmap, data.lightmapUV.zw);
half3 realtimeColor = DecodeRealtimeLightmap (realtimeColorTex);
#ifdef DIRLIGHTMAP_OFF
o_gi.indirect.diffuse += realtimeColor;
#elif DIRLIGHTMAP_COMBINED
half4 realtimeDirTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_DynamicDirectionality, unity_DynamicLightmap, data.lightmapUV.zw);
o_gi.indirect.diffuse += DecodeDirectionalLightmap (realtimeColor, realtimeDirTex, normalWorld);
#elif DIRLIGHTMAP_SEPARATE
half4 realtimeDirTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_DynamicDirectionality, unity_DynamicLightmap, data.lightmapUV.zw);
half4 realtimeNormalTex = UNITY_SAMPLE_TEX2D_SAMPLER(unity_DynamicNormal, unity_DynamicLightmap, data.lightmapUV.zw);
o_gi.indirect.diffuse += DecodeDirectionalSpecularLightmap (realtimeColor, realtimeDirTex, normalWorld, true, realtimeNormalTex, o_gi.light3);
#endif
#endif
o_gi.indirect.diffuse *= occlusion;
//【4】返回此UnityGI类型的结构体
return o_gi;
}
7.UNITY_BRDF_PBS宏
定义于UnityPBSLighting.cginc头文件中,根据不同的情况,将UNITY_BRDF_PBS宏定义为不同版本的UNITY_BRDF_PBS宏——是BRDF3_Unity_PBS、BRDF2_Unity_PBS还是BRDF1_Unity_PBS。
源码如下:
//-------------------------------------------------------------------------------------
// 默认使用BRDF || Default BRDF to use:
#if !defined (UNITY_BRDF_PBS) // 允许显式地在自定义着色器中重写BRDF的实现细节 || allow to explicitly override BRDF in custom shader
//满足着色目标模型的版本小于Shader Model 3.0,或者是PlayStation 2平台
#if (SHADER_TARGET < 30) || defined(SHADER_API_PSP2)
// 为小于SM3.0的着色模型回退为低保真度的BRDF版本 || Fallback to low fidelity one for pre-SM3.0
#define UNITY_BRDF_PBS BRDF3_Unity_PBS
#elif defined(SHADER_API_MOBILE)
// 为移动平台简化的BRDF版本 || Somewhat simplified for mobile
#define UNITY_BRDF_PBS BRDF2_Unity_PBS
#else
//最高特效的SM3、PC平台或者游戏主机平台的BRDF版本 || Full quality for SM3+ PC / consoles
#define UNITY_BRDF_PBS BRDF1_Unity_PBS
#endif
#endif
三种情况下,BRDF3_Unity_PBS、BRDF2_Unity_PBS、 BRDF1_Unity_PBS三个函数的参数和返回值都一样,区别是内部的实现。在这边,以BRDF1_Unity_PBS为例。
half4 BRDF1_Unity_PBS (half3 diffColor,half3 specColor, half oneMinusReflectivity, half oneMinusRoughness,half3 normal,half3 viewDir,UnityLight light, UnityIndirect gi)
第一个参数,half3型的diffColor,表示漫反射颜色的值。
第二个参数,half3型的specColor,表示镜面反射颜色值。
第三个参数,half型的oneMinusReflectivity,表示1减去反射率的值。
第四个参数,half型的oneMinusRoughness,表示1减去粗糙度的值。
第五次参数,half3型的normal,表示法线的方向。
第六个参数,half3型的viewDir,表示视线的方向。
第七个参数,UnityLight型的light,表示Unity中光源参数的结构体,包含half3型的光源颜色color,half3型的光源方向dir,half型的入射光方向和当前表面法线方向的点乘的积ndotl。
struct UnityLight
{
half3 color;//光源颜色
half3 dir;//光源方向
half ndotl; //入射光方向和当前表面法线方向的点积
};
第八个参数,UnityIndirect类型的gi ,一个包含了half3型的漫反射颜色diffuse和half3型的镜面反射颜色specular的光线反射结构体,表示间接光照信息。
struct UnityIndirect
{
half3 diffuse;//漫反射颜色
half3 specular;//镜面反射颜色
};
三种版本的函数分别贴出来,它们都定义于UnityStandardBRDF.cginc头文件中。
(1)BRDF1_Unity_PBS
//最高特效的SM3、PC平台或者游戏主机平台的BRDF版本 || Full quality for SM3+ PC / consoles
//-------------------------------------------------------------------------------------
// Note: BRDF entry points use oneMinusRoughness (aka "smoothness") and oneMinusReflectivity for optimization
// purposes, mostly for DX9 SM2.0 level. Most of the math is being done on these (1-x) values, and that saves
// a few precious ALU slots.
// Main Physically Based BRDF
// Derived from Disney work and based on Torrance-Sparrow micro-facet model
//
// BRDF = kD / pi + kS * (D * V * F) / 4
// I = BRDF * NdotL
//
// * NDF (depending on UNITY_BRDF_GGX):
// a) Normalized BlinnPhong
// b) GGX
// * Smith for Visiblity term
// * Schlick approximation for Fresnel
half4 BRDF1_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half oneMinusRoughness,
half3 normal, half3 viewDir,
UnityLight light, UnityIndirect gi)
{
half roughness = 1-oneMinusRoughness;
half3 halfDir = Unity_SafeNormalize (light.dir + viewDir);
half nl = light.ndotl;
half nh = BlinnTerm (normal, halfDir);
half nv = DotClamped (normal, viewDir);
half lv = DotClamped (light.dir, viewDir);
half lh = DotClamped (light.dir, halfDir);
#if UNITY_BRDF_GGX
half V = SmithGGXVisibilityTerm (nl, nv, roughness);
half D = GGXTerm (nh, roughness);
#else
half V = SmithBeckmannVisibilityTerm (nl, nv, roughness);
half D = NDFBlinnPhongNormalizedTerm (nh, RoughnessToSpecPower (roughness));
#endif
half nlPow5 = Pow5 (1-nl);
half nvPow5 = Pow5 (1-nv);
half Fd90 = 0.5 + 2 * lh * lh * roughness;
half disneyDiffuse = (1 + (Fd90-1) * nlPow5) * (1 + (Fd90-1) * nvPow5);
// HACK: theoretically we should divide by Pi diffuseTerm and not multiply specularTerm!
// BUT 1) that will make shader look significantly darker than Legacy ones
// and 2) on engine side "Non-important" lights have to be divided by Pi to in cases when they are injected into ambient SH
// NOTE: multiplication by Pi is part of single constant together with 1/4 now
half specularTerm = max(0, (V * D * nl) * unity_LightGammaCorrectionConsts_PIDiv4);// Torrance-Sparrow model, Fresnel is applied later (for optimization reasons)
half diffuseTerm = disneyDiffuse * nl;
half grazingTerm = saturate(oneMinusRoughness + (1-oneMinusReflectivity));
half3 color = diffColor * (gi.diffuse + light.color * diffuseTerm)
+ specularTerm * light.color * FresnelTerm (specColor, lh)
+ gi.specular * FresnelLerp (specColor, grazingTerm, nv);
return half4(color, 1);
}
(2)BRDF2_Unity_PBS
// 为移动平台简化的BRDF版本 || Somewhat simplified for mobile
// Based on Minimalist CookTorrance BRDF
// Implementation is slightly different from original derivation: http://www.thetenthplanet.de/archives/255
//
// * BlinnPhong as NDF
// * Modified Kelemen and Szirmay-Kalos for Visibility term
// * Fresnel approximated with 1/LdotH
half4 BRDF2_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half oneMinusRoughness,
half3 normal, half3 viewDir,
UnityLight light, UnityIndirect gi)
{
half3 halfDir = Unity_SafeNormalize (light.dir + viewDir);
half nl = light.ndotl;
half nh = BlinnTerm (normal, halfDir);
half nv = DotClamped (normal, viewDir);
half lh = DotClamped (light.dir, halfDir);
half roughness = 1-oneMinusRoughness;
half specularPower = RoughnessToSpecPower (roughness);
// Modified with approximate Visibility function that takes roughness into account
// Original ((n+1)*N.H^n) / (8*Pi * L.H^3) didn't take into account roughness
// and produced extremely bright specular at grazing angles
// HACK: theoretically we should divide by Pi diffuseTerm and not multiply specularTerm!
// BUT 1) that will make shader look significantly darker than Legacy ones
// and 2) on engine side "Non-important" lights have to be divided by Pi to in cases when they are injected into ambient SH
// NOTE: multiplication by Pi is cancelled with Pi in denominator
half invV = lh * lh * oneMinusRoughness + roughness * roughness; // approx ModifiedKelemenVisibilityTerm(lh, 1-oneMinusRoughness);
half invF = lh;
half specular = ((specularPower + 1) * pow (nh, specularPower)) / (unity_LightGammaCorrectionConsts_8 * invV * invF + 1e-4h); // @TODO: might still need saturate(nl*specular) on Adreno/Mali
// Prevent FP16 overflow on mobiles
#if SHADER_API_GLES || SHADER_API_GLES3
specular = clamp(specular, 0.0, 100.0);
#endif
half grazingTerm = saturate(oneMinusRoughness + (1-oneMinusReflectivity));
half3 color = (diffColor + specular * specColor) * light.color * nl
+ gi.diffuse * diffColor
+ gi.specular * FresnelLerpFast (specColor, grazingTerm, nv);
return half4(color, 1);
}
(3)BRDF3_Unity_PBS
// 为小于SM3.0的着色模型回退为低保真度的BRDF版本 || Fallback to low fidelity one for pre-SM3.0
// Old school, not microfacet based Modified Normalized Blinn-Phong BRDF
// Implementation uses Lookup texture for performance
//
// * Normalized BlinnPhong in RDF form
// * Implicit Visibility term
// * No Fresnel term
//
// TODO: specular is too weak in Linear rendering mode
half4 BRDF3_Unity_PBS (half3 diffColor, half3 specColor, half oneMinusReflectivity, half oneMinusRoughness,
half3 normal, half3 viewDir,
UnityLight light, UnityIndirect gi)
{
half3 reflDir = reflect (viewDir, normal);
half nl = light.ndotl;
half nv = DotClamped (normal, viewDir);
// Vectorize Pow4 to save instructions
half2 rlPow4AndFresnelTerm = Pow4 (half2(dot(reflDir, light.dir), 1-nv)); // use R.L instead of N.H to save couple of instructions
half rlPow4 = rlPow4AndFresnelTerm.x; // power exponent must match kHorizontalWarpExp in NHxRoughness() function in GeneratedTextures.cpp
half fresnelTerm = rlPow4AndFresnelTerm.y;
half grazingTerm = saturate(oneMinusRoughness + (1-oneMinusReflectivity));
half3 color = BRDF3_Direct(diffColor, specColor, rlPow4, oneMinusRoughness);
color *= light.color * nl;
color += BRDF3_Indirect(diffColor, specColor, gi, grazingTerm, fresnelTerm);
return half4(color, 1);
}
ps:BRDF1_Unity_PBS函数的实现部分用到了最多的变量,最终表现效果最好,主要用于Shader Model 3.0、PC平台或者游戏主机平台。BRDF2_Unity_PBS简化了一部分计算,主要用于移动平台,而BRDF3_Unity_PBS是为Shader Model 小于3.0的着色模型提供基本版的BRDF,实现细节最为简陋。
8.UNITY_BRDF_GI宏
UNITY_BRDF_GI宏位于UnityPBSLighting.cginc头文件中
源码如下:
//-------------------------------------------------------------------------------------
// 从间接的方向光照贴图中进行BRDF(双向反射分布函数)的光照提取 || BRDF for lights extracted from *indirect* directional lightmaps (baked and realtime).
// 使用UNITY_BRDF_PBS从方向光源烘焙方向光照贴图, || Baked directional lightmap with *direct* light uses UNITY_BRDF_PBS.
// 若想得到更好的效果,可以使用BRDF1_Unity_PBS || For better quality change to BRDF1_Unity_PBS.
// SM2.0中的非方向光照贴图|| No directional lightmaps in SM2.0.
//若没有定义UNITY_BRDF_PBS_LIGHTMAP_INDIRECT宏
#if !defined(UNITY_BRDF_PBS_LIGHTMAP_INDIRECT)
//定义UNITY_BRDF_PBS_LIGHTMAP_INDIRECT = BRDF2_Unity_PBS
#define UNITY_BRDF_PBS_LIGHTMAP_INDIRECT BRDF2_Unity_PBS
#endif
//若没有定义UNITY_BRDF_GI宏
#if !defined (UNITY_BRDF_GI)
//定义UNITY_BRDF_GI = BRDF_Unity_Indirect
#define UNITY_BRDF_GI BRDF_Unity_Indirect
#endif
上面这段代码中关于UNITY_BRDF_GI宏的地方,就是说若没有定义UNITY_BRDF_GI宏,就定义一个UNITY_BRDF_GI宏等价于BRDF_Unity_Indirect。这边的BRDF_Unity_Indirect是一个函数名,就紧紧跟在上面这段宏代码的后面:
//间接光照的BRDF
inline half3 BRDF_Unity_Indirect (half3 baseColor, half3 specColor, half oneMinusReflectivity, half oneMinusRoughness, half3 normal, half3 viewDir, half occlusion, UnityGI gi)
{
half3 c = 0;
#if defined(DIRLIGHTMAP_SEPARATE)
gi.indirect.diffuse = 0;
gi.indirect.specular = 0;
#ifdef LIGHTMAP_ON
c += UNITY_BRDF_PBS_LIGHTMAP_INDIRECT (baseColor, specColor, oneMinusReflectivity, oneMinusRoughness, normal, viewDir, gi.light2, gi.indirect).rgb * occlusion;
#endif
#ifdef DYNAMICLIGHTMAP_ON
c += UNITY_BRDF_PBS_LIGHTMAP_INDIRECT (baseColor, specColor, oneMinusReflectivity, oneMinusRoughness, normal, viewDir, gi.light3, gi.indirect).rgb * occlusion;
#endif
#endif
return c;
}
关于此段代码,BRDF_Unity_Indirect 函数的核心部分其实就是在调用UNITY_BRDF_PBS_LIGHTMAP_INDIRECT,而上文的宏有交代过,UNITY_BRDF_PBS_LIGHTMAP_INDIRECT宏等价于 BRDF2_Unity_PBS。而BRDF2_Unity_PBS函数,其定义于UnityStandardBRDF.cginc中
9.Emission函数
Emission函数定于于UnityStandardInput.cginc头文件中,根据指定的自发光光照贴图,利用tex2D函数,对输入的纹理进行光照贴图的采样
源码如下:
//---------------------------------------【Emission函数】--------------------------------
// 用途:根据指定的自发光光照贴图,利用tex2D函数,对输入的纹理进行光照贴图的采样
// 输入参数:float2型的纹理坐标
// 输出参数:经过将自发光纹理和输入纹理进行tex2D采样得到的half3型的自发光颜色
//-----------------------------------------------------------------------------------------------
half3 Emission(float2 uv)
{
#ifndef _EMISSION
return 0;
#else
return tex2D(_EmissionMap, uv).rgb * _EmissionColor.rgb;
#endif
}
其中用于采样的自发光贴图对应的函数定义于UnityStandardInput.cginc头文件的一开始部分。
sampler2D _EmissionMap;
相当于在CGPROGRAM中的顶点和片段着色函数之前,对这个变量进行声明,以便于CG语言块中使用的时候,能识别到他的含义。因为在Standard.shader源码的一开始,Properties块也就是属性值声明部分,对其进行了属性的声明:
//自发光纹理图
_EmissonMap(“Emisson”,2D) = “white” { }
10.UNITY_APPLY_FOG宏
UNITY_APPLY_FOG宏相关的一些代码用于雾效的启用与否的辅助工作,定义于UnityCG.cginc头文件中
源码如下:
//UNITY_FOG_LERP_COLOR宏的定义
#define UNITY_FOG_LERP_COLOR(col,fogCol,fogFac) col.rgb = lerp((fogCol).rgb, (col).rgb, saturate(fogFac))
//【1】若已经定义了FOG_LINEAR、FOG_EXP、FOG_EXP2宏三者至少之一,便可以进行到此#if实现部分
#if defined(FOG_LINEAR) || defined(FOG_EXP) || defined(FOG_EXP2)
//【1-1】若满足着色目标模型的版本小于Shader Model 3.0,或者定义了SHADER_API_MOBILE宏,便可以进行到此#if实现部分
#if (SHADER_TARGET < 30) || defined(SHADER_API_MOBILE)
//移动平台和Shader Model 2.0:已经计算了每顶点的雾效因子,所以插一下值就可以了 ||mobile or SM2.0: fog factor was already calculated per-vertex, so just lerp the color
//定义 UNITY_APPLY_FOG_COLOR(coord,col,fogCol) 等价于UNITY_FOG_LERP_COLOR(col,fogCol,coord)
#define UNITY_APPLY_FOG_COLOR(coord,col,fogCol) UNITY_FOG_LERP_COLOR(col,fogCol,coord)
//【1-2】 Shader Model 3.0和PC/游戏主机平台:计算雾效因子以及进行雾颜色的插值 ||SM3.0 and PC/console: calculate fog factor and lerp fog color
#else
//定义 UNITY_APPLY_FOG_COLOR(coord,col,fogCol)等价于UNITY_CALC_FOG_FACTOR(coord); UNITY_FOG_LERP_COLOR(col,fogCol,unityFogFactor)
#define UNITY_APPLY_FOG_COLOR(coord,col,fogCol) UNITY_CALC_FOG_FACTOR(coord); UNITY_FOG_LERP_COLOR(col,fogCol,unityFogFactor)
#endif
//【2】否则,直接定义UNITY_APPLY_FOG_COLOR宏
#else
#define UNITY_APPLY_FOG_COLOR(coord,col,fogCol)
#endif
//【3】若定义了UNITY_PASS_FORWARDADD(正向附加渲染通道)宏
#ifdef UNITY_PASS_FORWARDADD
//定义UNITY_APPLY_FOG(coord,col) 等价于UNITY_APPLY_FOG_COLOR(coord,col,fixed4(0,0,0,0))
#define UNITY_APPLY_FOG(coord,col) UNITY_APPLY_FOG_COLOR(coord,col,fixed4(0,0,0,0))
//【4】否则,UNITY_APPLY_FOG(coord,col) 等价于 UNITY_APPLY_FOG_COLOR(coord,col,unity_FogColor)
#else
#define UNITY_APPLY_FOG(coord,col) UNITY_APPLY_FOG_COLOR(coord,col,unity_FogColor)
#endif
11.OutputForward函数
OutputForward函数定义于UnityStandardCore.cginc头文件中,其为正向渲染通道的输出函数
源码如下:
//-----------------------------【函数OutputForward】-----------------------------------
// 用途:正向渲染通道输出函数
// 输入参数:一个half4类型的一个颜色值output,一个half型的透明度值alphaFromSurface
// 返回值:经过透明处理的half4型的输出颜色值
//-------------------------------------------------------------------------------------------------
half4 OutputForward (half4 output, half alphaFromSurface)
{
#if defined(_ALPHABLEND_ON) || defined(_ALPHAPREMULTIPLY_ON)
output.a = alphaFromSurface;
#else
UNITY_OPAQUE_ALPHA(output.a);
#endif
return output;
}
其中UNITY_OPAQUE_ALPHA宏的定义为:
#define UNITY_OPAQUE_ALPHA(outputAlpha) outputAlpha = 1.0
六、Bidirectional ReflectanceDistribution Function,BRDF(双向反射分布函数)
双向反射分布函数(Bidirectional ReflectanceDistribution Function,BRDF)用来定义给定入射方向上的辐射照度(irradiance)如何影响给定出射方向上的辐射率(radiance)。更笼统地说,它描述了入射光线经过某个表面反射后如何在各个出射方向上分布——这可以是从理想镜面反射到漫反射、各向同性(isotropic)或者各向异性(anisotropic)的各种反射。
参考:
- 如何正确理解 BRDF (双向反射分布函数)? - 计算机 - 知乎
2.图形学理论知识:BRDF 双向反射分布函数 - An Introduction to BRDF-based Lighting –Nvidia