在第6.4节中,书中给出了计算基本光照模型中漫反射光部分的计算公式分别表示为:
漫反射模型
兰伯特定律:反射光线的强度与表面法线和光源方向之间夹角的余弦值成正比。
共由四个参数,从左到右依次为光源颜色、漫反射颜色、表面法线、单位矢量。
逐像素光照(逐顶点光照改进版)
完整注释的程序(逐像素光照)细节如下
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 6/Diffuse Pixel-Level" {
Properties{
_Diffuse("Diffuse", Color) = (1, 1, 1, 1)//便于可调颜色,为了可以使用该字段,需要后续与该字段存在一个类型相同,名称相同变量
}
SubShader{
Pass {
Tags { "LightMode" = "ForwardBase" }//LightMode标签在语法中应在pass语义块下,定义unity的光照流水线中的角色,这里定义了ForwardBase
//在定义后,我们就可以得到一些unity内置光照变量,如_LightColor()
CGPROGRAM
#pragma vertex vert//告诉unity顶点着色器被命名为vert函数
#pragma fragment frag//告诉unity片元着色器被命名为frag函数
#include "Lighting.cginc"//包含内置库,以便后续使用,如_LightColor()
fixed4 _Diffuse;//便于可调颜色,为了可以使用Properties字段,需要Pass语义块下定义该字段,名称类型相同
/*
然后我们定义了顶点着色器的输入和输出结构体
(输出结构体同时也是片元着色器的输入结构体)
*/
//输入结构体,顶点着色器常用
struct a2v {
float4 vertex : POSITION;//必要字段,模型空间中的顶点坐标,用vertex表示,通常类型是float4
float3 normal : NORMAL;//定义法线名称为normal
};
//输出结构体,片元着色器常用
struct v2f {
float4 pos : SV_POSITION;//必要字段,裁剪空间中的顶点坐标,用变量pos表示,类型同POSITION
float3 worldNormal : TEXCOORD0;//有时TEXCOORD0可以换为COLOR
/*
当TEXCOORD0出现在a2v结构体中所代表的含义是表示第n+1组纹理坐标,如TEXCOORD0代表第一组纹理坐标,类型是float2或float4类型
当TEXCOORD0出现在v2f结构体中所代表的含义是表示输出纹理坐标,但不是必须的
*/
};
/*开始输入*/
v2f vert(a2v v) {
v2f o;//o代表着色器
//将模型空间转到裁剪空间
o.pos = UnityObjectToClipPos(v.vertex);
/*将法线转移到世界空间.公式:世界法线 = 模型法线向量 * 法线转换矩阵,其中法线转换矩阵不能使用顶点变换矩阵(float3*3)unity_ObjectToWorld,
否则会出现法线无法正常缩放,因为法线缩放与常规缩放不同,法线变换矩阵是顶点变换矩阵的逆矩阵的转置,推导参见https://www.zhihu.com/question/400660113*/
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
return o;//返回顶点着色
}
fixed4 frag(v2f i) : SV_Target {
// 得到环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// 转换法线到世界坐标系
fixed3 worldNormal = normalize(i.worldNormal);
// 得到在世界坐标系中的光
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// 计算漫反射光照模型。模型公式:反射光=(入射光线颜色和强度·漫反射系数)max(0,法线向量·光源方向单位向量)
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
//显示在屏幕上的实际颜色=环境光+反射光
fixed3 color = ambient + diffuse;
//返回(R,G,B,D) D为透明度
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
以下两图分别是兰伯特逐像素光照模型(上)与环境光(下)的对比
逐顶点光照(兰伯特原版)
与逐像素光照类似
区别:(1)着色器的输出结构体v2f为color语义。
(2)顶点着色器需要光照模型。
(3)片元着色器无计算光照模型步骤。
// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 6/Diffuse Vertex-Level" {
Properties{
_Diffuse("Diffuse", Color) = (1, 1, 1, 1)
}
SubShader{
Pass {
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
fixed3 color : COLOR;//使用传递颜色的语义
};
v2f vert(a2v v) {
v2f o;
// 从模型空间到裁剪矩阵(不用专门调用MVP矩阵,unity5.0已优化api)
o.pos = UnityObjectToClipPos(v.vertex);
// 得到环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// 转换法线到世界坐标
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
// 得到光源方向
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
// 计算反射光
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLight));
o.color = ambient + diffuse;
return o;
}
fixed4 frag(v2f i) : SV_Target {
return fixed4(i.color, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
以下是逐顶点漫反射光照的对比图(上为逐顶点光照,下为无材质)
可见存在部分锯齿,逐像素模型更优。
半兰伯特光照模型
此模型是对计算方法与效果的改进,并无物理依据。使用此模型可以将n与l向量的点积范围从[-1,1]变为[0,1],这样既不用比较最大值,加快了运行速度,也为背光面提供了明暗的变化
修改逐像素光照模型frag函数即可
fixed4 frag(v2f i) : SV_Target {
// Get ambient term
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// Get the normal in world space
fixed3 worldNormal = normalize(i.worldNormal);
// Get the light direction in world space
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// Compute diffuse term
fixed halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;
fixed3 color = ambient + diffuse;
从做到右依次为逐顶点,逐像素,半兰伯特。
总结:在需要表示游戏阴影的条件下优选逐像素光照模型,而半兰伯特光照模型,即使被遮挡,依然存在有层次光照,且计算也更有效率