文章目录
- 1、环境光和自发光
- 2、基本的逐顶点光照代码实现
- 3、基本逐像素光照的实现
- 4、半兰伯特光照模型的实现
- 5、新手实现的注意事项
1、环境光和自发光
环境光可以在Window->Redenering->Lighting->Enviroment->Intensity Multipler这个地方更改。
在 ShaderLab 中,可以通过内置的变量 UNITY_LIGHTMODEL_AMBIENT
来获取环境光的颜色和强度信息。
2、基本的逐顶点光照代码实现
Shader "Unity Shaders Book/Chapter 6/Diffuse Vertex-Level" {
Properties {
_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
}
首先,我们写出我们的 Shader 名字,然后再 Properties 语义块中声明一个 Color 类型的属性,并设置其初始值为白色。
SubShader {
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Diffuse;
SubShader中声明Pass,Pass中指令该Pass的光照模式为 ForwardBase。这个值定义了该 Pass 在光照流水线中的角色。为了获得我们需要进行计算的光照,我们还需要包含内置文件“Lighting.cginc”。还有,声明同名的 _Diffuse
可以获得和该属性类型相匹配的变量。这样,我们接下来就能用到漫反射的材质属性了。
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
fixed3 color : COLOR;
};
定义我们输入输出的结构体。我们需要访问的是模型顶点的位置和法线信息,需要得到的是传递给片元着色器的顶点和颜色信息。
v2f vert(a2v v) {
v2f o;
// Transform the vertex from object space to projection space
o.pos = UnityObjectToClipPos(v.vertex);
// Get ambient term,使用的是Unity内的宏定义
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// Transform the normal from object space to world space
fixed3 worldNormal = normalize(mul((float3x3)unity_ObjectToWorld, v.normal));
// Get the light direction in world space
fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
// Compute diffuse term
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * (dot(worldNormal, worldLight));
o.color = ambient + diffuse;
return o;
}
在第2行,我们定义了要输出的结构体。
第4行,我们将定点坐标从模型空间转换到投影空间。我们如上,可以直接使用 Unity 内置的函数:UnityObjectToClipPos(v.vertex)
将坐标转换到裁剪空间(投影空间)之外。我们还可以使用 o.pos = mul(NUITY_MATRIX_MVP, v.vertex);
手动乘法来得到裁剪空间的位置。
第7行,我们通过内置宏 UNITY_LIGHTMODEL_AMBIENT
获得环境光。
第10行,我们通过模型到世界的矩阵 UNITY_MATRIX_M
(Unity中也可以写作 unity_ObjectToWorld
),将法线也转换到世界空间。但是注意,我们这里应该使用左乘的方法用 unity_WorldToObject
,来避免 unity 中的隐形求逆。
第14行,我们采用的是Unity给我们提供的内置变量 _LightColor0
来访问该 Pass 处理的光源的颜色和强度信息。这里,因为我们假设场景中只有一个光源且是平行光,所以使用了 _WorldSpaceLightPos0
来获得光源的方向。
记得到我们的 漫反射光照 **兰伯特定律(Lambet’s law)**公式:
最后,相加环境光得到最后的结果。
fixed4 frag(v2f i) : SV_Target {
return fixed4(i.color, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
片元着色器什么也不用做,直接返回原来的颜色。
3、基本逐像素光照的实现
Shader "Unity Shaders Book/Chapter 6/Diffuse Pixel-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;
float3 worldNormal : TEXCOORD0;
};
相比上一段代码,这里使用了不同的语义,worldNormal的语义是采用的纹理坐标的语义。
v2f vert(a2v v) {
v2f o;
// Transform the vertex from object space to projection space
o.pos = UnityObjectToClipPos(v.vertex);
// Transform the normal from object space to world space
o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
return o;
}
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
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
fixed3 color = ambient + diffuse;
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack "Diffuse"
}
4、半兰伯特光照模型的实现
// Compute diffuse term
fixed halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5;
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;
单纯的把上面的 Compute diffuse term 的部分改成那个公式上的部分就好了。
5、新手实现的注意事项
Unity和OpenGL一样,使用的是列向量,所以是矩阵 X 向量
。顺序是从左到右。而DirectX中,这点是不一样的,它是行向量,是通过 向量 X 矩阵
来进行运算的。