本篇记录SRP/URP自带的Shader库、Unity内置参数、HLSL函数、光照模型。
如需了解URP渲染流水线的配置部分,可参见URP-Pipeline
如需了解Unity图形的数学部分,可参见Unity图形数学

参考资料:
1.HLSL微软官方文档
2.Unity SRP Github

HLSL语法

在Unity中编写Shader、Compute Shader时,我们可以选择使用HLSL语言,这是推荐的做法。
HLSL语法与C++类似,比如加减乘除余、变量/函数声明、循环控制、输入/输出等。

变量

变量需要被声明、赋值,作用范围取决于声明的位置,有指定的存储位置和读写限制。
▲Vector类型
float4 a = float4(b.xyz, 4.0);
Vector类型变量包含1-4个标量分量,每个分量的存储类型一致。
可以使用xyzw或rgba访问Vector的分量。
▲标量(Scalar)类型
常用的有bool、int、uint、float。

语句

a = b * (c / d) - Func(e);
一个表达式(语句)由许多变量、运算符和方法构成,以“;”结尾,包括“return”语句。

加减乘除余:+ - * / %
数组/矩阵取值操作:array[i] matrix[0][1]
赋值操作:= += -= *= /= %=
布尔操作:&& || ?:
比较操作:any(A4 < B4) all(A4 < B4) < > == != <= >=
前后置操作:++ --
注:比较操作仅限于单个分量,每个分量都会单独受到加减操作影响。
注:int数相除/相余时,结果省略小数部分。
注:布尔操作返回多元bool Vector,表达式两边的计算都会被执行,可能造成计算力浪费。
注:对矩阵的取值操作时,第一个数为行的序列值;如果只提供一个参数将得到对应行的多元Vector。

显式强制转换操作:
(float)i4 注:等同于float(i4.x),取4元int类型Vector的x分量转化为浮点数。
(float4)i 注:等同于float4(i, i, i, i)。
asfloat(i) asint(f) asuint(f) 注:根据转换目标有精度损失。

二进制操作(仅对int和uint有效):
~ 注:二进制的逻辑非。
<< 注:二进制下所有数字左移(小数点相对右移),高位移出,低位补0。
>> 注:二进制下所有数字右移,低位移出,高位补符号位(正数补0/负数补1)。
& 注:二进制的逻辑和。
| 注:二进制的逻辑或。
^ 注:二进制的逻辑异。
<<= >>= &= |= ^= 注:类似于 a += b,二进制操作后进行赋值。

一元运算:! - 注:false/true取反、正负取反。
注:表达式中操作的执行顺序同C++。
注:当语句块(statement block)中只有一个语句时,括号可省略。

流程控制

语义

声明一个结构体,可自定义结构体名称StructName和变量名称Name。结构体在使用时有特殊意义。
作为vertex函数输入值时,其代表了该顶点着色器需要的顶点数据集合,CPU会准备这些数据。
作为vertex函数输出值时,其代表了三角面片各顶点的数据集合,用于为片元提供差值数据。
作为fragment函数输入值时,其代表了该片元着色器需要的数据集合,用于计算片元最终颜色。
Type Semantic:变量类型和变量的语义,参考本篇中的CG语义。
vertex输入
float4 vertex : POSITION; 顶点在模型空间中的坐标。
float3 normal : NORMAL; 顶点在模型空间中的法线方向。
float4 tangent : TANGENT; 顶点在模型空间中的切线方向。
tangent.w用于确定副法线的方向。
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
float4 texcoord : TEXCOORD0; 顶点在第一套纹理中的坐标。
float4 texcoord1 : TEXCOORD1; 顶点在第二套纹理中的坐标。
float4 texcoord2 : TEXCOORD2; 顶点在第三套纹理中的坐标。
float4 texcoord3 : TEXCOORD3; 顶点在第四套纹理中的坐标。
fixed4 color : COLOR; 顶点的颜色值。
UNITY_VERTEX_INPUT_INSTANCE_ID 参考Manual
SV_POSITION 顶点在裁剪空间中的坐标。
COLOR0~COLOR1 自定义数据,fixed3/4。
TEXCOORD0~TEXCOORD7 自定义数据,float1/2/3/4。

WPOS
VPOS float4 屏幕坐标。
xy分量为该片元在屏幕空间中的坐标,左下角为[0.5,0.5],单位为像素;
z分量表示屏幕深度,值范围是[0,1],近裁剪面处为0,远裁剪面处为1;
w分量为裁剪空间中顶点的w分量的倒数,对于透视相机范围在[1/Near,1/Far]之间,对于正交相机值固定为1。

fragment输出
SV_Target
输出该片元的颜色值到屏幕空间,如果该片元通过了模板测试和深度测试,将使用该片元的颜色值参与混合。
一般使用float4/half4作为输出类型。

自定义函数

内置函数

注:红色函数需求SM5.0及以上,蓝色函数需求SM4.0及以上,部分函数Unity中没实现
abort() 注:报错并终止DrawCall。
▲abs(x) 注:返回逐分量的绝对值,abs(-1)为1。
▲acos(x) 注:返回逐分量(在-1至1范围内)的反余玄值(弧度),乘以57.29578转换为角度。
▲all(x) 注:如果x的所有分量都不为0,返回true;否则返回false。
AllMemoryBarrier() 注:异步读写时,等待group内线程读写完毕。
AllMemoryBarrierWithGroupSync() 注:异步读写时,等待group内线程逻辑执行到此调用。
▲any(x) 注:如果x的任一分量不为0,返回true;否则返回false。
asdouble(x, y) 注:将2个uint Vector转换为1个double Vector,分量数不变。
asfloat(x) 注:将1个任意Vector转换为float Vector。
▲asin(x) 注:返回逐分量(在-1至1范围内)的反正玄值(弧度),乘以57.29578转换为角度。
asint 注:将1个任意Vector转换为int Vector。
asuint 注:将1个任意Vector转换为uint Vector。
▲atan(x) 注:返回逐分量的反正切值(弧度),乘以57.29578转换为角度。
▲atan2(y, x) 注:返回向量(x, y)与X轴的反正切值(弧度),乘以57.29578转换为角度。
▲ceil(x) 注:如果x有小数部分,返回比x大的最小的整数。
CheckAccessFullyMapped(x)
▲clamp(a, x, y) 注:if(a < x) retrun x; if(a > y) retrun y; retrun a;
▲clip(x) 注:如果x的任意分量小于0,则忽略当前片元;也就是透明度测试alphatest。
▲cos(x) 注:返回x(弧度)的余玄值。
▲cosh(x) 注:双曲线余玄函数
countbits(x) 注:返回逐分量(uint类型)的二进制格式中1的数量。
▲cross(x, y) 注:返回x和y(参数为多组件Vector类型)的叉积。
▲D3DCOLORtoUBYTE4(x) 注:返回x的UBYTE4表达形式。
▲ddx(x) 注:范围Vector x的偏导数
ddx_coarse(x) 注:返回Vector x的低精度偏导数。
ddx_fine(x) 注:返回Vector x的高精度偏导数。
▲ddy ▲ddy_coarseddy_fine 注:参考ddx。
▲degree(x) 注:将x(弧度)转化为角度。
▲determinant(x) 注:返回x(方形-浮点数-矩阵)的行列式
DeviceMemoryBarrier() 注:同步节点,group内所有线程完成对device memory的访问。
DeviceMemoryBarrierWithGroupSync() 注:同步节点,设备内存访问+逻辑同步。
▲distance(x, y) 注:返回x和y(Vector)之间的距离,√((xa-xb)² + (ya-yb)²)。
▲dot(x, y) 注:返回x和y(Vector)的点积,xa * xb + ya * yb。
dst(x, y) 注:返回(1, x.y * y.y, x.z, y.w)
errof(string, ...) 注:将错误消息提交到信息队列。
EvaluateAttributeAtCentroid(x) 注:评估像素质心。
EvaluateAttributeAtSample(x, y) 注:评估采样位置。
EvaluateAttributeSnapped(x, y) 注:使用偏移量评估像素质心。
▲exp(x) 注:返回以自然数e(2.71828)为底数,x为指数的值。
▲exp2(x) 注:返回以2为底数,x为指数的值。
f16tof32(x) 注:返回x(被保存为unit的16位浮点数)的32位浮点数表达形式。
f32tof16(x) 注:返回x(32位浮点数)的16位浮点数表达形式(被保存为unit)。
▲faceforward(n1, v, n2) 注:使法线方向正对观察方向,-n1 * sign(dot(v, n2))。
firstbithigh(x) 注:返回逐分量(int或uint)的二进制格式中从高位数第一个1的位置。
firstbitlow(x) 注:返回逐分量(uint)的二进制格式中从低位数第一个1的位置。
▲floor(x) 注:如果x有小数部分,返回比x小的最大整数,floor(-2.3) = -3。
fma(a, b, c) 注:参数为double类型,a * b + c。
▲fmod(x, y) 注:返回x/y的小数部分。
▲frac(x) 注:返回x的小数部分。
▲frexp(x, exp) 注:返回输入值的小数部分和指数。
▲fwidth(x) 注:返回abs(ddx(x) + abs(ddy(x))。
GetRenderTargetSampleCount() 注:返回渲染目标的采样器数量。
GetRenderTargetSamplePosition(x) 注:返回采样器x对应的采样位置(x, y)。
GroupMemoryBarrier() 注:group shared内存访问同步节点。
GroupMemoryBarrierWithGroupSync() 注:group shared内存访问+逻辑同步节点。
InterlockedAdd(dest, value, original_value)
InterlockedAnd(dest, value, original_value)
InterlockedCompareExchange(dest, compare_value, value, original_value)
InterlockedCompareStore(dest, compare_value, value)
InterlockedExchange(dest, value, original_value)
InterlockedMax(dest, value, original_value)
InterlockedMin(dest, value, original_value)
InterlockedOr(dest, value, original_value)
InterlockedXor(dest, value, original_value)
▲isfinite(x) 注:逐分量判断浮点数的值是否为finite(有限的)。
▲isinf(x) 注:逐分量判断浮点数的值是否为infinite(无限的)。
▲isnan(x) 注:逐分量判断浮点数的值是否为NAN或QNAN。
▲ldexp(x, y) 注:逐分量返回x * 2^y。
▲length(x) 注:返回x(Vector)的长度。
▲lerp(x, y, a) 注:等效于x + a(y-x)。
▲lit(NdotL, NdotH, 平滑度) 注:返回Blinn光照模型Vector4(环境光, 漫反射光, 镜面高光, 1)。
▲log(x) 注:求对数,返回以e为底数,x为值时对应的指数。
▲log10(x) 注:求对数,返回以10为底数,x为值时对应的指数。
▲log2(x) 注:求对数,返回以2位底数,x为值时对应的指数。
mad(a, x, y) 注:返回a * x + y。
▲max(x, y) 注:返回逐分量最大值。
▲min(x, y) 注:返回逐分量最小值。
▲modf(x, a) 注:逐分量返回x的有符号的小数部分,将x的整数部分保存到a。
msad4(reference, source, accum)
▲mul(x, y) 注:矩阵相乘;矢量在右边时,被竖排序;矢量在左边时,被横排序。
▲noise(x) 注:Perlin噪声函数,返回值在-1至1内。
▲normalize(x) 注:归一化x(Vector)。
▲pow(x, y) 注:幂运算,x为底数,y为指数。
printf(string, ...) 注:将自定义着色器消息提交到信息队列。
Process2DQuadTessFactorsAvg(x, y, a, b, c)
Process2DQuadTessFactorsMax(x, y, a, b, c)
Process2DQuadTessFactorsMin(x, y, a, b, c)
ProcessIsolineTessFactors(x, y, a, b)
ProcessQuadTessFactorsAvg(x, y, a, b, c)
ProcessQuadTessFactorsMax(x, y, a, b, c)
ProcessQuadTessFactorsMin(x, y, a, b, c)
ProcessTriTessFactorsAvg(x, y, a, b, c)
ProcessTriTessFactorsMax(x, y, a, b, c)
ProcessTriTessFactorsMin(x, y, a, b, c)
▲radians(x) 注:将角度x转换为弧度。
rcp(x) 注:逐分量返回近似倒数。
▲reflect(input, normal) 注:求反射方向, input为入射方向,返回input - 2 * normal * dot(input, normal)。
▲refract(input, normal, 折射率) 注:求折射方向。
reversebits(x) 注:逐分量使x的高位和低位互换位置,0001 ⇒ 1000。
▲round(x) 注:逐分量四舍五入。
▲rsqrt(x) 注:逐分量平方根的倒数。
▲saturate(a) 注:等效于clamp(a, 0, 1)
▲sign(x) 注:相当于if(x<0) return -1;if(x>0) return 1; return 0;。
▲sin(x) 注:返回x(弧度)的正玄值。
▲sincos(x, s, c) 注:void,将x(弧度)的sin值和cos值分别赋值到s和c。
▲sinh(x) 注:双曲线正玄函数。
▲smoothstep(x, y, a) 注:平滑梯度,类似于clamp(a, x, y);
当y>x时,a<x时返回0,a>y时返回1,腰部值根据曲线算。
当y<x时,a>x时返回0,a<y时返回1,腰部值根据曲线算。
▲sqrt(x) 注:逐分量返回x的平方根。
▲step(a, b) 注:等效于(b >= a) ? 1 : 0
▲tan(x) 注:逐分量返回x(弧度)的正切值。
▲tanh(x) 注:双曲线正切函数。
▲tex1D(s, t) ▲tex1D(s, t, ddx, ddy) ▲tex1Dbias ▲tex1Dgrad ▲tex1Dlod ▲tex1Dproj
▲tex2D(sampler, uv) 注:采样2D texture(pixel shader only),自动选择Mipmap。
▲tex2D(sampler, uv, ddx, ddy) 注:相比tex2D,使用屏幕坐标x和y方向上的梯度指定Mipmap。
▲tex2Dbias(sampler, uv) 注:相比tex2D,uv.w中包含采样位置偏移量。
▲tex2Dgrad(sampler, uv, ddx, ddy) 注:相比tex2D,使用屏幕坐标x和y方向上的梯度指定Mipmap。
▲tex2Dlod(sampler, uv) 注:相比tex2D,uv.w中指定Mipmap层级。
▲tex2Dproj(tex, uv) 注:相比tex2D,UV除以w分量:uv.xy / uv.w。
▲tex3D(s, t) ▲tex3D(s, t, ddx, ddy) ▲tex3Dbias ▲tex3Dgrad ▲tex3Dlod ▲tex3Dproj
▲texCUBE(s, t) ▲texCUBE(s, t, ddx, ddy) ▲texCUBEbias ▲texCUBEgrad ▲texCUBElod ▲texCUBEproj
▲transpose(x) 注:返回x(矩阵)的转置矩阵(将行列互换)。
▲trunc(x) 注:逐分量去除小数部分,trunc(-2.3)= -2。

Legacy库

由于目前大部分网络上的资料都由内置管线编写,Legacy库依然有学习价值。
▲ComputeGrabScreenPos(float4)

inline float4 ComputeGrabScreenPos (float4 pos) //输入Clip空间4元坐标
{
    #if UNITY_UV_STARTS_AT_TOP
    float scale = -1.0;
    #else
    float scale = 1.0;
    #endif
    float4 o = pos * 0.5f;
    o.xy = float2(o.x, o.y*scale) + o.w; //输出x分量为pos.w * (pos.x / pos.w + 1) * 0.5
#ifdef UNITY_SINGLE_PASS_STEREO          //输出y分量会额外在DX平台反转
    o.xy = TransformStereoScreenSpaceTex(o.xy, pos.w);
#endif
    o.zw = pos.zw;                       //输出zw分量为pos.zw
    return o;
}

ComputeGrabScreenPos用于计算屏幕空间的位置,在vertex中调用,fragment中xy分量除以w分量(tex2Dproj)后可作为UV坐标使用。提供DX平台的UV反转,使DX平台屏幕左下角对应(0, 1),可直接使用tex2Dproj采样基于屏幕空间的Texture,tex2Dproj采样结果与Screen空间(0-1的2D坐标系)位置对应,区别于通常tex2D采样结果与Object空间位置对应。也就是,将A相机看到的结果提供给B相机采样。

SRP-Core库

SpaceTransforms.hlsl
▲GetObjectToWorldMatrix() 注:返回Object-World矩阵。
▲GetWorldToObjectMatrix() 注:返回World-Object矩阵。
▲GetWorldToViewMatrix() 注:返回World-View矩阵。
▲GetWorldToHClipMatrix() 注:返回World-Clip矩阵。
▲GetViewToHClipMatrix() 注:返回View-Clip矩阵。
▲GetAbsolutePositionWS(x)
▲GetCameraRelativePositionWS(x)
▲GetOddNegativeScale
▲TransformObjectToWorld
▲TransformWorldToObject
▲TransformWorldToView
▲TransformObjectToHClip
▲TransformWorldToHClip
▲TransformWViewToHClip
▲TransformObjectToWorldDir
▲TransformWorldToObjectDir
▲TransformWorldToViewDir
▲TransformWorldToHClipDir
▲TransformObjectToWorldNormal
▲CreateTangentToWorld
▲TransformTangentToWorld
▲TransformWorldToTangent
▲TransformTangentToObject
▲TransformObjectToTangent

SRP-URP库

SRP-HDRP库

C#库

在C#中经常需要设置矩阵、向量,实现旋转、反射等效果,也需要图形方面的计算。
▲Matrix4x4类型结构体
Matrix4x4 test = new Matrix4x4(Vector4.one, new Vector4(2F, 3F, 4F, 5F), Vector4.one, Vector4.one);
Matrix4x4结构使用4个Vector4参数填充矩阵的每一列。
test = test1 * test2; //矩阵相乘,对应mul(a, b)操作,作图时a在左侧,b在上侧。
test = test.transpose; //矩阵置转,交换行列。
test = test.inverse; //矩阵求逆,不一定有逆矩阵。
Debug.Log(test.m03); //可以查看矩阵的分量,两个数字分布代表所在行和列。
Debug.Log(test[4]); //使用数组取值方式时,参数为列排序,test[4]相当于test.m01。
Debug.Log(test); //可以直接查看矩阵的值。
test.MultiplyPoint3x4(pos); //使用矩阵转移顶点。
test.MultiplyVector(normal); //使用矩阵转移向量。
▲Vect4类型结构体
Vector4.Dot(test1, test2); //对应dot(a, b)操作。

Unity内置参数

UnityShaderVariables.cginc

包含了很多内置的全局变量,这个文件会被自动包含。
参考Unity用户手册
时间
float4 _Time;float4_SinTime;float4_CosTime;float4 unity_DeltaTime
常用矩阵
UNITY_MATRIX_M 将顶点/矢量从模型空间变换到世界空间
UNITY_MATRIX_V 将顶点/矢量从世界空间变换到观察空间
UNITY_MATRIX_P 将顶点/矢量从观察空间变换到裁剪空间
UNITY_MATRIX_MV 将顶点/方向矢量从模型空间变换到观察空间
UNITY_MATRIX_VP 将顶点/矢量从世界空间变换到裁剪空间
UNITY_MATRIX_MVP 将顶点/方向矢量从模型空间变换到裁剪空间
经常被UnityObjectToClipPos(v.vertex)函数替换。
UNITY_MATRIX_I_V UNITY_MATRIX_V的逆矩阵
UNITY_MATRIX_T_MV UNITY_MATRIX_MV的转置矩阵。
unity_WorldToObject UNITY_MATRIX_M的逆矩阵。

全局光照变量
UNITY_LIGHTMODEL_AMBIENT
环境光颜色,可在Lighting Settings中确认。
float4 _WorldSpaceLightPos0
光源方向,对于平行光,w分量为0;对于非平行光,w分量为1。
需要取其xyz分量作为矢量并归一化,只可在ForwardBase/ForwardAdd中使用。
float4 unity_4LightPosX0;float4 unity_4LightPosY0;float4 unity_4LightPosZ0;float4 unity_4LightAtten0;half4 unity_LightColor[4];
仅用于ForwardBase,在顶点着色器中计算4个逐顶点光照,作为Shade4PointLights函数的参数。

全局相机参数
_WorldSpaceCameraPos float3 该相机在世界空间中的位置。
用于减去顶点/片元在世界空间中的坐标,也就是xyz分量,得到摄像机方向。
_ProjectionParams float4 Near和Far。
_ScreenParams float4 render target的像素宽度和高度,x为像素宽度,y为像素高度,单位为像素。
_ZBufferParams float4 x=1−Far/Near,y=Far/Near,z=x/Far,w=y/Far。
unity_OrthoParams float4 正交相机的W和H。
unity_CameraProjection float4x4 该相机的投影矩阵。
unity_CameraInvProjection float4x4 该相机的投影矩阵的逆矩阵。
unity_CameraWorldClipPlanes[6] float4 该相机的6个裁剪面在世界空间下的等式。

UnityCG.cginc

包含了最常使用的帮助函数、宏、结构体等。
视角方向
float3 UnityWorldSpaceViewDir(float3 worldPos)
输入一个世界空间中的顶点位置,返回世界空间中从该点到相机的观察方向。
在世界空间下,用相机位置减去顶点位置,没有被归一化。
float3 WorldSpaceViewDir(float4 v)
输入模型空间中的顶点位置,返回世界空间中从该点到相机的观察方向。
先将顶点从模型空间转换到世界空间,再使用UnityWorldSpaceViewDir函数,没有被归一化。
float3 ObjSpaceViewDir(float4 v)
输入一个模型空间中的顶点位置,返回模型空间中从该点到相机的观察方向。
将相机位置转换到模型空间,再减去顶点位置。没有被归一化。

光源方向 仅可用于ForwardBase/ForwardAdd。
float3 UnityWorldSpaceLightDir(float3 worldPos)
输入一个世界空间中的顶点位置,返回世界空间中从该点到光源的光照方向。
在世界空间下,用光源位置减去顶点位置。没有被归一化。
float3 WorldSpaceLightDir(float4 v)
输入一个模型空间中的顶点位置,返回世界空间中从该点到光源的光照方向。
先将顶点位置从模型空间转换到世界空间,再使用UnityWorldSpaceLightDir函数。没有被归一化。
float3 ObjSpaceLightDir(float4 v)
输入一个模型空间中的顶点位置,返回模型空间中从该点到光源的光照方向。
将光源的位置从世界空间转换到模型空间,再减去减去顶点位置。没有被归一化。

空间转换
float3 UnityObjectToWorldNormal(float3 norm)
把法线方向从模型空间转换到世界空间中。
判断模型是否统一缩放,非统一缩放时使用逆转置矩阵。已归一化。
float3 UnityObjectToWorldDir(float3 dir)
把方向矢量从模型空间转换到世界空间中。
使用unity_ObjectToWorld函数的3x3简化版本。已归一化。
float3 UnityWorldToObjectDir(float3 dir)
把方向矢量从世界空间转换到模型空间中。
使用unity_WorldToObject矩阵的3x3简化版本。已归一化。
TANGENT_SPACE_ROTATION;
这个宏相当于以下代码,需要提前定义好法线和切线,矩阵rotation可将矢量从模型空间转换到切线空间
float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w;
float3x3 rotation = float3x3( v.tangent.xyz, binormal, v.normal )
UnityObjectToClipPos(v.vertex);
顶点着色器中,将顶点从模型空间转换到裁剪空间,用于实现顶点着色器的基础任务。

功能函数/宏
float4 ComputeScreenPos(float4 pos)
输入:裁剪空间中的顶点坐标,4元数。
输出:x分量等于clipx/2+clipw/2;y分量等于clipy/2+clipw/2;z分量等于clipz;w分量等于clipw。
xy分量相当于对裁剪空间中的顶点转换到视口空间,但是没有除以w分量。
当ComputeScreenPos的输出结果作为vertex函数的输出时,参与了三角形遍历过程中的差值处理。
为了能得到正确的视口空间位置,需要在fragment函数中除以w分量。
float2 TRANSFORM_TEX(v.texcoord,_Maintex)
顶点着色器中用于得到偏移后的UV坐标。参数为第一套UV坐标和贴图纹理,返回经过了缩放和平移后的UV坐标。
内部计算:v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
变换后xy值可能超出[0,1],Repeat平铺模式,超出1的部分会无限重复;Clamp平铺模式则保持边缘值。
fixed4 tex2D(_NormalMap,i.uv);
片元着色器中对贴图采样,输入2D纹理贴图和UV坐标,得到指定位置的rgba值。
当UV值超出[0,1]时根据平铺模式返回值。使用Clamp平铺模式可解决Repeat平铺模式的边缘杂色。
对切线空间下的法线贴图的采样:需要将[-1,1]范围的值转化到[0,1];转化过程:y=x/2+0.5
对渐变纹理的采样:采样用的UV参数可以使用想要影响的颜色的系数,如通过光照角度实现渐变的反照率。
对遮罩纹理的采样:使用和纹理贴图一样的UV,实现像素级别的控制想要影响的颜色的系数。
fixed3 UnpackNormal(float4 packednormal)
法线纹理解压,当纹理贴图的导入设置中Texture Type被标记为Normal map时,Unity对贴图进行压缩。
压缩过程中只保留了rgba其中两个通道的值,我们无需关心Unity针对哪个平台使用了那种压缩方式。
normal.xy = packednormal.wy * 2 - 1;normal.z = sqrt(1 - saturate(dot(normal.xy, normal.xy)));
输入采样得到的float4值,得到解压后的rgb值,是一个单位向量。
当法线值为(0,0,1)时,表示平滑表面,可以通过缩放xy分量来增强法线的倾斜效果。
float3 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, o.worldPos, o.worldNormal);
ForwardBase的vertex函数中,一次性计算4个逐顶点光照颜色值。
half3 ShadeSH9 (float4(o.worldNormal, 1.0))
ForwardBase的vertex函数中,一次计算所有SH光源的光照颜色值,包含环境光。

Lighting.cginc

包含了各种内置的光照模型。
_LightColor0 该Pass处理的逐像素光源的颜色,用于向前渲染。
USING_DIRECTIONAL_LIGHT
keyword,判定光源类型是否为平行光。

AutoLight.cginc

_LightMatrix0;unity_WorldToLight
float4X4,从世界空间到光源空间的变换矩阵。可用于采样cookie和光强衰减纹理。
使用unity_WorldToLight替代_LightMatrix0;光源空间中,光源位置(0,0,0),光边缘处LightCoord模为1。
_LightTexture0;_LightTextureB0
_LightTexture0为点光源的衰减纹理,_LightTextureB0为聚光灯的衰减纹理。
POINT;SPOT;POINT_COOKIE;DIRECTIONAL_COOKIE
光源模式定义,通过判断keyword正确执行衰减纹理采样。
ForwardAdd中进行光源空间中的光强衰减采样
#if defined (POINT) //如果是点光源
float3 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1)).xyz;
fixed atten = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#elif defined (SPOT) //如果是聚光灯,转换矩阵会不同,w分量代表同距离时不同角度处的光照衰减。
float4 lightCoord = mul(unity_WorldToLight, float4(i.worldPos, 1));
fixed atten = (lightCoord.z > 0) * tex2D(_LightTexture0, lightCoord.xy / lightCoord.w + 0.5).w * tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).UNITY_ATTEN_CHANNEL;
#endif
聚光灯空间是一个三维空间,光源为(0,0,0),+Z轴方向为聚光灯方向,受光影响的区域是一个圆锥+球形截面。
在距离光源距离为1的地方光衰减为0,即时在距离1范围内,根据角度不同也有衰减,可创建demo场景进行观察。
0<n<1,所有距离光源距离为n的点构成了一个球形截面,光源-点的矢量与+Z轴之间的夹角越大衰减越大,如下图:

lightCoord.xy / lightCoord.w + 0.5的意义就是得到这个夹角,并进行采样。
SHADOW_COORDS(a);TRANSFER_SHADOW(o);fixed SHAOW_ATTENUATION(i)
阴影三剑客,分别用于声明v2f成员(阴影采样uv)、计算uv偏移、采样阴影值。
LIGHTING_COORDS(a,b);TRANSFER_VERTEX_TO_FRAGMENT(o);fixed LIGHT_ATTENUATION(i);
衰减三剑客,同时计算了阴影衰减和光照衰减。a和b是下一个可用的差值寄存器序列。
分别用于声明v2f成员(光照衰减采样uv、阴影采样uv)、计算uv偏移、采样阴影值。
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
配合阴影三剑客前2个宏,atten为阴影衰减x光照衰减,输入的世界坐标用于光照采样。

VERTEXLIGHT_ON keyword,判断是否存在有效的顶点光照。

光照模型

漫反射颜色=光源色*漫反射系数*反照率;与观察方向无关
漫反射系数:代表物体对光的吸收/漫反射的性质,用rgb分量表示漫反射光的比率(强度)。
兰伯特漫反射反照率=光线方向·法线方向;矢量归一化→积→saturate;反射光线的强度与表面法线和光源方向之间夹角的余玄值成正比。
镜面反射颜色=光源色*高光系数*镜面反射率;与观察方向有关
高光系数:对镜面反射颜色的rgb分量进行额外修正,不确定是否有科学依据。
Phong光照模型镜面反射率=pow(saturate(反光方向·视角方向),平滑度)
Blinn光照模型镜面反射率=pow(saturate(法线方向·(光源方向+视角方向)),平滑度)
平滑度:这里平滑度值越高,得到的反光率越小,呈指数级别的变化,仅作为参考模型。
环境光=全局环境光*漫反射系数
模拟所有的间接光,在物体之间的夹角环境光变弱,也就是环境光遮蔽(AO)。
自发光:一般不会影响周围物体,仅作为一般颜色来源。
片元颜色=自发光+环境光+漫反射颜色+镜面反射颜色
女神书第6章中提供的兰伯特模型与PBR中的镜面反射/光泽度工作流中有差别。

镜面反射/光泽度工作流
平滑度:灰度-线性,白色(1.0)表示光滑表面,黑色(0.0)表示粗糙表面。
能量守恒:光的总量是一定的,分别参与了漫反射和高光反射,导电性越高的物体参与漫反射的比率越低。
在PBR的Shader中,能量守恒由shader负责计算,这样参与漫反射比率低的金属可以反射更多的光。

半兰伯特模型
漫反射颜色=光源色*漫反射系数*半兰伯特系数
半兰伯特系数=(光线方向·法线方向)*0.5+0.5;矢量先归一化
半兰伯特模型削减了漫反射颜色的变化率,使非光源正面区域也有一定亮度


关注成长,注重因果。