Unity Shader 实现光照模型
实现漫反射光照模型
漫反射部分计算公式:cdiffuse = (clight · mdiffuse)max(0,n · I);
计算漫反射需要的4个参数:
1. 入射光线的颜色和强度clight ;
2. 材质的漫反射系数mdiffuse;
3. 表面法线n;
4. 光源方向I;
防止点积结果负值CG中提供函数saturate函数
函数:saturate(x)
参数:x:为用于操作的标量或矢量,可以是float、float2、float3等类型;
描述:把x截取在[0,1]范围内,如果x是一个矢量,那么会对它的每一个分量进行这样的操作。
逐顶点光照

// Upgrade NOTE: replaced '_World2Object' with 'unity_WorldToObject'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Shader Test/Chapter 6/Diffuse Vertex-Level"
{
// 为了得到且控制材质的漫反射贪色,声明一个Color类型的属性并把初始值设置为白色
Properties
{
_Diffuse("Diffuse",Color) = (1,1,1,1)
}
SubShader
{
// 顶点片元着色器要写在 Pass 语义块中
Pass
{
// 指明该 Pass 的光照模式
// LightMode标签是Pass标签的一种,用于定义该Pass在Unity光照流水线中的角色
// 只有定义了正确的LightMode才能得到一些Unity的内置光照变量
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// 使用Unity的一些内置变量需包含进Unity的Lighting.cginc文件
#include "Lighting.cginc"
// 为使用 Properties 语义块中声明的属性,我们需要定义一个和该属性类型相匹配的变量
// 通过这种方式可获得漫反射公式的参数材质的漫反射属性。由于颜色属性范围在 0-1 ,因此使用fixed的变量存储
fixed4 _Diffuse;
// 定义顶点着色器输入输出结构体(输出结构体也是片元着色器的输入结构体)
struct a2v {
float4 vertex : POSITION;
// 为访问顶点法线,通过NORMAL语义告诉Unity要把模型顶点的法线信息存储到normal变量中
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
// 为把在顶点着色器中计算的光照颜色传递给片元着色器,需定义一个color变量,且并不是必须使用COLOR,一些资料使用TEXCOORD0语义
fixed3 color : COLOR;
};
// 逐顶点的漫反射光照计算都将在顶点着色器中进行
v2f vert(a2v v)
{
v2f o;
// 坐标转换把顶点位置从模型空间转换到裁剪空间中
o.pos = UnityObjectToClipPos(v.vertex);
// Unity内置变量 UNITY_LIGHTMODEL_AMBIENT 得到了环境光部分
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// 计算法线和光源方向的点积时两者需在同意坐标系,在这使用世界坐标空间
// a2v中的顶点法线时位于模型空间下的,需要转换到世界空间中
// 使用顶点变换矩阵的逆转置矩阵对法线进行变换,得到模型空间到世界空间的变换矩阵的逆矩阵_World2Object
// 调换mul参数位置,得到和转置矩阵相同的矩阵乘法。法线是三维矢量,_World2Object只需截取前三和前三列,
// 得到世界空间中的法线和光源方向需要进行归一化处理
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
// _WorldSpaceLightPos0:提供光源方向
// (注:对光源方向的计算并不具有通用性,在这使用的是平行光且光源唯一,
// 若多个元且光源类型可能是其他类型直接使用_WorldSpaceLightPos0 就不能得到正确的结果)
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
// _LightColor0:Unity提供的内置变量,用来访问该Pass处理的光源的颜色和强度信息(注:想要得到正确的值需要定义合适的LightMode标签)
// 得到世界空间中的法线和光源方向的点积结果后需防止结果为负值,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
}
}
// 把Unity Shader 的回调 shader 设置为内置的 Diffuse
Fallback "Diffuse"
}
对细分程度较高的模型,逐顶点光照已经可以得到比较好的光照效果了。但对于一些细分程度较低的模型,逐顶点光照就会出现一些视觉问题,例如在背光面与向光面交界处有一些锯齿。为了解决这些问题可以使用逐像素的漫反射光照。
逐像素光照

Shader "Shader Test/Chapter 6/Diffuse Pixel-Level"
{
// 为了得到且控制材质的漫反射贪色,声明一个Color类型的属性并把初始值设置为白色
Properties
{
_Diffuse("Diffuse",Color) = (1,1,1,1)
}
SubShader
{
// 顶点片元着色器要写在 Pass 语义块中
Pass
{
// 指明该 Pass 的光照模式
// LightMode标签是Pass标签的一种,用于定义该Pass在Unity光照流水线中的角色
// 只有定义了正确的LightMode才能得到一些Unity的内置光照变量
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// 使用Unity的一些内置变量需包含进Unity的Lighting.cginc文件
#include "Lighting.cginc"
// 为使用 Properties 语义块中声明的属性,我们需要定义一个和该属性类型相匹配的变量
// 通过这种方式可获得漫反射公式的参数材质的漫反射属性。由于颜色属性范围在 0-1 ,因此使用fixed的变量存储
fixed4 _Diffuse;
// 定义顶点着色器输入输出结构体(输出结构体也是片元着色器的输入结构体)
struct a2v {
float4 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
}
}
// 把Unity Shader 的回调 shader 设置为内置的 Diffuse
Fallback "Diffuse"
}
逐像素光照可以得到更加平滑的光照效果。
但即便使用了逐像素漫反射光照,仍有一个问题,在光照无法到达的区域,模型的外观通常是全黑的,没有任何明暗变化,这会使模型的背光区域看起来就像一个平面一样,失去了模型细节表现。
可以通过添加环境光来得到非全黑的效果,但即便这样仍然无法解决背光面明暗一样的缺点。为此,有一种改善技术被提出来,这就是**半兰伯特(Half Lambert)**光照模型。
半兰伯特模型
兰伯特定律:在平面某点漫反射光的光强与该反射点的法向量和入射光角度的余弦值成正比。
半兰伯特光照模型是在原兰伯特光照模型的基础上进行了简单的修改;
广义的半兰伯特光照模型的公式:cdiffuse = (clight · mdiffuse)( α \alpha α(0, n · I) + β \beta β)
半兰伯特光照模型没有使用max操作防止** n ^ \widehat{n} n
** 和 I的点积为负值,而是对其结果进行 α \alpha α倍的缩放在加上一个 β \beta β大小的偏移;绝大多数情况下, α \alpha

最低0.47元/天 解锁文章
2111

被折叠的 条评论
为什么被折叠?



