文章目录

  • 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)**公式:
unity shader graph实现描边_d3
  最后,相加环境光得到最后的结果。

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 矩阵 来进行运算的。