光照模型
着色
着色指的是,根据材质的属性,比如漫反射属性,还有光源的信息,用一个等式去计算从某个方向去观察物体,该方向上光的出射度的过程。这个过程称为光照模型。
标准光照模型
标准光照模型只关心光照,就是那些直接从光源发射出来照射到物体表面后,经过物体表面的一次反射直接进入摄像机的光线。
标准光照模型把进入到摄像机的光线分位4个部分:
- 自发光:使用Cemissvie来表示。用来描述当给定一个方向时,一个表面本身会向该方向发射多少的辐射量。
- 高光反射:使用Cspecular来表示。用于描述当光线照射到模型表面时,该表面会在完全镜面反射方向散射多少辐射量。
- 漫反射:使用Cdiffuse来表示。用于描述光线照射到模型表面时,该表面会向每个方向散射多少辐射度。
- 环境光:使用Cambient来表示。用来表示其他所有的间接光照。在标准光照模型中,场景中所有物体都使用一个通用的光照变量来表示环境光:Cambient = gambient.
自发光使用了材质的自发光颜色:Cemissvie = memissvie.
漫反射符合兰伯特定律(Lambert’s law):反射光线的强度与表面法线和光源方向之间夹角的余弦值成正比。
Cdiffuse = (Clight · mdiffuse)max(0, n·l).,Clight是光源颜色,mdiffuse是材质漫反射颜色.
在标准光照模型中,场景中所有物体都使用一个通用的光照变量来表示环境光:Cambient = gambient.
高光反射需要的信息比较多:
表面法线,视角方向,光源方向,反射方向等。反射方向可以通过其他信息计算得到:
r = 2(n·l)n - l
Phong模型计算高光反射公式为:
Cspecular = (Clight · mspecular)max(0, v·r)mgloss
公式里,mgloss是光泽度或者反光度,用来控制高光区域的亮点有多大,mgloss越大,亮点就越小。
mspeuclar是材质的高光反射颜色。
Blinn-Phong模型引入一个新的矢量h:
h = (v + l) / |v + l|
Blinn模型计算公式为:
Cspecular = (Clight · mspecular)max(0, n·h)mgloss
UnityShader中实现漫反射光照模型
漫反射计算公式为:
Cdiffuse = (Clight · mdiffuse)max(0, n·l).,Clight是光源颜色,mdiffuse是材质漫反射颜色.
逐顶点光照
Shader "Unity Shaders Book/Chapter 6/Diffuse Vertex-Level" {
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
}
SubShader {
Pass {
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#include "Lighting.cginc"
#pragma vertex vert
#pragma fragment frag
fixed4 _Diffuse;
// application to vertex
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
// vertex to fragment
struct v2f {
float4 pos : SV_POSITION;
fixed3 color : COLOR;
};
v2f vert(a2v v){
v2f o;
// 把顶点位置从模型空间转换到裁剪空间
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
// 得到环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// normalize()进行归一化
// 法线方向, 从模型空间转换到世界空间,使用模型到世界变换矩阵的逆矩阵_World2Object
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)_World2Object));
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
// 漫反射公式 saturate相当于max();
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"
}
LighMode是Pass标签的一种,用来定义该Pass在Unity中光照流水线的角色。我们要使用_LightColor0,需要引入 “Lighting.cginc”。
上述代码中,我们使用了UNITY_LIGHTMODEL_AMBIENT来获取环境光部分。
在计算法线和光源方向之间的点积时,需要将二者所处的坐标系统一后计算才有意义。
逐像素光照
Shader "Unity Shaders Book/Chapter 6/Diffuse Pixel-Level" {
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
}
SubShader {
Pass {
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#include "Lighting.cginc"
#pragma vertex vert
#pragma fragment frag
fixed4 _Diffuse;
// application to vertex
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
// vertex to fragment
struct v2f {
float4 pos : SV_POSITION;
fixed3 worldNormal : TEXCOORD0;
};
v2f vert(a2v v){
v2f o;
// 把顶点位置从模型空间转换到裁剪空间
o.pos = mul(UNITY_MATRIX_MVP, 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 worldLight = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb + saturate(dot(worldNormal, worldLight));
fixed3 color = ambient + diffuse;
return fixed4(color, 1.0);
}
ENDCG
}
}
Fallback "Diffuse"
}
半兰伯特光照模型
计算公式为:
Cdiffuse = (Clight · mdiffuse)(α(n·l) + β).
半兰伯特模型没有使用max操作来防止点积值为负值,而是对结果进行了一个α倍的缩放和β大小的偏移,多数情况下,二者均为0.5:
Cdiffuse = (Clight · mdiffuse)(0.5(n·l) + 0.5).
UnityShader中实现高光反射模型
Phong模型计算高光反射部分的公式为:
Cspecular = (Clight · mspecular)max(0, v·r)mgloss
逐顶点光照
Shader "Unity Shaders Book/Chapter 6/Specular Vertex-Level" {
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Pass {
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#include "Lighting.cginc"
#pragma vertex vert
#pragma fragment frag
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
// application to vertex
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
// vertex to fragment
struct v2f {
float4 pos : SV_POSITION;
fixed3 color : COLOR;
};
v2f vert(a2v v){
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
// 计算出反射光线方向并归一化
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
// 计算出世界坐标系中物体的视角方向,然后归一化
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);
// 高光反射公式计算
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
o.color = ambient + diffuse + specular;
return o;
}
// 片元着色器计算光照模型
fixed4 frag(v2f i) : SV_Target {
return fixed4(i.color, 1.0);
}
ENDCG
}
}
Fallback "Specular"
}
Properties里,_Specular用来控制材质的高光反射颜色,_Gloss来控制高光区域的大小。
逐像素光照
Shader "Unity Shaders Book/Chapter 6/Specular Pixel-Level" {
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Pass {
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#include "Lighting.cginc"
#pragma vertex vert
#pragma fragment frag
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
// application to vertex
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
// vertex to fragment
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert(a2v v){
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
//o.color = ambient + diffuse + specular;
return o;
}
// 片元着色器计算光照模型
fixed4 frag(v2f i) : SV_Target {
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 worldNormal = normalize(i.worldNormal);
//fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// 使用UnigtCG公式
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.pos));
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
// 计算出反射光线方向并归一化
fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
// 计算出世界坐标系中物体的视角方向,然后归一化
//fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
//使用UnigtCG公式
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
// 高光反射公式计算
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
fixed3 color = ambient + diffuse + specular;
return fixed4(color, 1.0);
}
ENDCG
}
}
Fallback "Specular"
}
Blinn-Phong光照模型
Blinn计算高光反射的公式为:
Cspecular = (Clight · mspecular)max(0, n·h)mgloss
而其中,h = (v + l) / |v + l|
Shader "Unity Shaders Book/Chapter 6/Specular BlinnPhong" {
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader {
Pass {
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#include "Lighting.cginc"
#pragma vertex vert
#pragma fragment frag
fixed4 _Diffuse;
fixed4 _Specular;
float _Gloss;
// application to vertex
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
// vertex to fragment
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
};
v2f vert(a2v v){
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
//o.color = ambient + diffuse + specular;
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 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
// 计算出世界坐标系中物体的视角方向,然后归一化
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
fixed3 halfDir = normalize(worldLightDir + viewDir);
// Blinn-Phong模型公式
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(worldNormal, halfDir)), _Gloss);
fixed3 color = ambient + diffuse + specular;
return fixed4(color, 1.0);
}
ENDCG
}
}
Fallback "Specular"
}
Unity内置的函数
UnityCG.cginc中常用的帮助函数:
例如:UnityWorldSpaceViewDir的实现如下:
inline float3 UnityWorldSpaceViewDir(in float3 worldPos)
{
return _WorldSpaceCameraPos.xyz - worldPos;
}
就是从世界坐标系中某个点worldPos到世界坐标系中摄像机的向量。
我们在使用这些函数前,需要把方向矢量归一化,我们使用normalize来进行此操作。