本篇暂时记录一些ShaderLab关于计算方面的函数。
本篇只讨论ShaderLab相关的vertex(顶点着色器)和fragment(片元着色器)的编程。
如需了解渲染流水线的配置部分,可参见ShaderLab笔记
如需了解ShaderLab数学部分,可参见ShaderLab数学

注:Shader调试方法Frame Debugger。
注:QQ输入法造词模式:ctrl+alt+=
注:inline关键字,表示对函数的一种优化,细节未知。
注:in关键字,表示对函数参数的一种优化,细节未知。
注:变量的声明需要遵守Unity的设置才能使用某些内置宏。
a2v结构体声明中,顶点坐标变量名为vertex:struct a2v { float4 vertex : POSITION; };
v2f结构体声明中,顶点坐标变量名为pos:struct v2f { float4 pos : SV_POSITION; };
vertex函数的输入变量名为v:v2f vert(appdata_full v){}

ShaderLab函数

这部分函数是在vertex和fragment函数中通用的,xyzw和rgba这8个字母代表分量和颜色通道。
float4 a = float4(1.0, 2.0, 3.0, 4.0); 手动创建四元数,可代表顶点、颜色。
float4 a = float4(b.xyz, 1.0); 使用b的xyz分量参与构成四元数a。
dot(a,b) 矢量与矢量的点积操作,结果为一元数。
a.xy*b.xy 四元数a的xy分量分别乘以四元数b的xy分量,得到新的二元数(a.x*b.x,a.y*b.y)。
a.xy*b.w 四元数a的xy分量分别乘以四元数b的w分量,得到新的二元数(a.x*w,a.y*w)。
a.xy/b.xy 四元数a的xy分量分别除以四元数b的xy分量,得到新的二元数(a.x/b.x,a.y/b.y)。
a.xy/b.w 四元数a的xy分量分别除以四元数b的w分量,得到新的二元数(a.x/b.w,a.y/b.w)。
a.xy+b.xy 四元数a的xy分量分别加上四元数b的xy分量,得到新的二元数(a.x+b.x,a.y+b.y)。
a.xy+b.w 四元数a的xy分量分别加上四元数b的w分量,得到新的二元数(a.x+b.w,a.y+b.w)。
transpose(M) 矩阵的转置操作。
float4x4 M = float4x4(1.0, 0.0...); 手动创建矩阵,数字将被横排列。
(float3x3)M 取4X4矩阵的前3个维度的分量构成新的3X3矩阵。
mul(M,M) 矩阵相乘操作
当矢量被当做mul的参数时,默认放在右边,此时矢量会被竖排序。
当矢量被放在左边时,矢量会被横排序。两张排序方式的结果转化为相同矢量。
float3 row = M[0]; 取矩阵M的第1行的所有元素,M可以是宏。
float element = M[1][0]; 取矩阵M的第2行第1个元素,M可以是宏。
struct StructName { Type Name : Semantic;... };
声明一个结构体,可自定义结构体名称StructName和变量名称Name。结构体在使用时有特殊意义。
作为vertex函数输入值时,其代表了该顶点着色器需要的顶点数据集合,CPU会准备这些数据。
作为vertex函数输出值时,其代表了三角面片各顶点的数据集合,用于为片元提供差值数据。
作为fragment函数输入值时,其代表了该片元着色器需要的数据集合,用于计算片元最终颜色。
Type/Semantic:变量类型和变量的语义,参考本篇中的CG语义。
cross(a.xyz,b.xyz) 矢量的✖积,得到同时垂直于a.xyz和b.xyz的新矢量,方向固定。
saturate(a) 对a的各个分量截取[0,1]范围内的值。
取a的某一个分量x,当x小于0时,返回0;当x大于1时,返回1;其余情况,返回x。
frac(a) 对a的各个分量余1。
取a的某个分量x,取x的小数部分。
any(a) bool判断,a的任意分量不为0时返回true;a的全部分量均为0时,返回false。
reflect(i,n) 获得光源反射方向
参数:i,入射方向,也就是光源方向的取反;n,法线方向。
可以是float、float2、float3等类型,分别代表一维、二维、三维坐标系。
pow(a,b) 返回a的b次幂。
normalize(a) 归一化向量。
max(a,b) 返回一元数a和b中,值更大的那个。
sqrt(a) 开根号操作,返回√a。

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

vertex输出
SV_POSITION 顶点在裁剪空间中的坐标。
COLOR0~COLOR1 自定义数据,fixed3/4。
TEXCOORD0~TEXCOORD7 自定义数据,float1/2/3/4。

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

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

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;矢量先归一化
半兰伯特模型削减了漫反射颜色的变化率,使非光源正面区域也有一定亮度


关注成长,注重因果。