1 公式计算
从公式可以看出,要计算漫反射需要知道4 个参数:入射光线的颜色和强度 cgh”材质的漫反射系数mdiuse,表面法线n以及光源方向I。
为了防止点积结果为负值,我们需要使用max操作,而CG提供了这样的函数。在本例中使用CG的另一个函数可以达到同样的目的,即saturate函数。
2 顶点着色器实现
Shader "MyShader/Chapter6-DiffuseVertexLevel"{
//顶点着色器采用插值渲染,速度更快,但是效果有限
//声明反射color属性
Properties{
_Diffuse ("Diffuse", Color) =(1,1,1,1)
}
SubShader{
pass{
//LightMode标签是Pass标签中的一种,它用于定义该Pass在 Unity 的光照流水线中的角色
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
//CG代码
//声明顶点着色器和片元着色器 无分号
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
//从属性中获取材质漫反射颜色
fixed4 _Diffuse;
//顶点着色器输入结构体
struct a2v{
fixed4 vertex : POSITION;
//NORMAL语义告诉unity法线存到normal
float3 normal : NORMAL;
};
//顶点着色器输出结构体,也是片原着色器的输入结构体
struct v2f{
float4 pos : SV_POSITION;
fixed3 color : COLOR;
};
//实现顶点着色器
v2f vert(a2v v){
v2f o;
//转换到裁切空间
o.pos = UnityObjectToClipPos(v.vertex);
//获取环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//unity_WorldToObject 模型空间转换到世界空间
//mul矩阵点积
//获取当前顶点的世界坐标下法线
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
//获取当前光源向量
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
//saturate截取参数0-1防止负数
//计算光源颜色和漫反射颜色
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
Fallback "diffuse"
}
3 片元着色器实现
Shader "MyShader/Chapter6-Chapter6-DiffusePixelLevel"{
//片元着色器逐像素渲染,速度慢,但是精度高
//声明反射color属性
Properties{
_Diffuse ("Diffuse", Color) =(1,1,1,1)
}
SubShader{
pass{
//LightMode标签是Pass标签中的一种,它用于定义该Pass在 Unity 的光照流水线中的角色
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
//CG代码
//声明顶点着色器和片元着色器 无分号
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
//从属性中获取材质漫反射颜色
fixed4 _Diffuse;
//顶点着色器输入结构体
struct a2v{
fixed4 vertex : POSITION;
//NORMAL语义告诉unity法线存到normal
float3 normal : NORMAL;
};
//顶点着色器输出结构体,也是片原着色器的输入结构体
struct v2f{
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
};
//实现顶点着色器
v2f vert(a2v v){
v2f o;
//转换到裁切空间
o.pos = UnityObjectToClipPos(v.vertex);
//法线转换到世界法线
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);
//计算反射
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
fixed3 color = ambient + diffuse;
return fixed4(color, 1.0);
}
ENDCG
}
}
//fallback
Fallback "diffuse"
}
遗留问题:
逐像素光照可以得到更加平滑的光照效果。但是,即便使用了逐像素漫反射光照,有一个问题仍然存在。在光照无法到达的区域,模型的外观通常是全黑的,没有任何明暗变化,这会使模型的背光区域看起来就像一个平面一样,失去了模型细节表现。
半兰伯特光照模型
在6.4.1小节中,我们使用的漫反射光照模型也被称为兰伯特光照模型,因为它符合兰伯特定律一一在平面某点漫反射光的光强与该反射点的法向量和入射光角度的余弦值成正比。
Valve 公司在开发游戏《半条命》时提出了一种技术,由于该技术是在原兰伯特光照模型的基础上进行了一个简单的修改,因此被称为半兰伯特光照模型。
通过这样的方式,我们可以把分·I的结果范围从[-1,1]映射到[1]范围内。也就是说,对于模型的背光面,在原兰伯特光照模型中点积结果将映射到同一个值,即0值处;而在半兰伯特模型中,背光面也可以有明暗变化,不同的点积结果会映射到不同的值上。
//片元着色器计算漫反射光照模型
fixed4 frag(v2f i) : SV_Target {
//获取环境光色
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
//获取世界法线
fixed3 worldNormal = normalize(i.worldNormal);
//获取光源的世界法线
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
//半兰伯特参数
fixed halfLambert = dot(worldNormal ,worldLightDir) * 0.5 + 0.5;
//计算反射
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;
fixed3 color = ambient + diffuse;
return fixed4(color, 1.0);
}