实践篇:简单镜面反射

前言:本篇博客只是一个简单的实现镜面反射功能的例子,主要是当做笔记使用。

通过反射光向量实现流程:如下所示:
核心要点:如下所示:
1.顶点镜面反射颜色值等于反射光颜色乘以反射强度来获取。而入射光向量,法向量,视角向量,反射光向量的关系图如下所示:
在这里插入图片描述
2.入射光向量实际上就是光源指向顶点的向量。要获取该向量,可以使用顶点指向光源向量的反操作获取(也就是-WorldSpaceLightDir处理)。

3.反射光向量可以通过将法向量和入射光向量作为参数来调用reflect函数进行获取。当然也可以通过法向量和入射光向量来推导出反射光向量,具体推导流程如下图所示:
在这里插入图片描述
1>.这里的L就是入射光向量的反向向量(也就是光照向量),N就是法向量,且L和N都是单位向量(模长为1)。
2>.将L平移至L’,将R平移至R’。此时由于L和R都是模长为1的单位向量。所以由此组成的平行四边形的对角线互相平分且互相垂直。
3>.L+R = R+L’=S,所以R = S - L。因此只需要计算出S向量,就可以推导出反射光R向量。
4>.S向量模长就是等于顶点到S点对角线的长度。由第2条得知当前平行四边形对角线互相平分且垂直,此时假设顶点到对角线交叉点的距离为d,那么S向量的模长就是2d。而cos(a) = d / L模长,又因为dot(N,L) / (L模长 * N模长) = cos(a),所以dot(N,L) / (L模长 * N模长) = d / L模长。由于L和N都是单位向量,对应模长都是1,所以dot(N,L) = d。因此S向量的长度为2 * dot(N,L) 。
5>.由于S向量和N向量在相同方向上,只是模长不同,所以 S模长 / N模长 = S向量 / N向量。由于N向量是单位向量,模长为1,所以就可以推导出S向量 = S模长 * N向量,也就是S = 2 * dot(N,L) * N。
6>.结合第3点和第5点可以得出:反射光向量R = 2 * dot(N,L) * N。

4.视角向量实际上就是顶点指向摄像机的向量。要获取该向量,可以使用摄像机向量减去顶点向量来获取(也就是调用WorldSpaceViewDir处理)。

5.获取反射光向量和视角向量的点积值。当点积值为负数时表示反射光在平面背面,可以不做反射光照处理。因此可以将点积值限制在0~1之间,并且点积值越大,反射强度越大,夹角越小。

6.反射强度并不是像第5点中那样描述的线性变化,而是存在平滑系数来控制反射强度衰减大小。平滑系数越大,反射强度乘以平滑系数平方值趋于0的顶点就会相对变多,从而高光范围就越小,反射光照越集中。平滑系数越小,反射强度乘以平滑系数平方值趋于0的顶点就会相对变少,从而高光范围就越大,反射光照越分散。

核心代码:如下所示:

Shader "Custom/demo12" {
	Properties {
		// 镜面反射光颜色
		_SpecularColor("Specular Color", COLOR) = (1, 0, 0, 1)
		// 镜面光平滑度
		_SpecularShiness("Specular Shiness", Range(1, 64)) = 8
	}

	SubShader {
		pass {
			Tags { "LightMode" = "ForwardBase" }

			CGPROGRAM
			#pragma vertex vert 
			#pragma fragment frag 
			#include "unitycg.cginc"
			#include "lighting.cginc"

			struct v2f {
				float4 pos:POSITION;
				fixed4 col:COLOR;
			};

			fixed4 _SpecularColor;
			float _SpecularShiness;

			v2f vert(appdata_base i)
			{
				v2f o;
				o.pos = mul(UNITY_MATRIX_MVP, i.vertex);
				
				// 获取世界空间中不受缩放影响的归一化法向量:使用模型到世界矩阵的逆矩阵的转置矩阵进行变换模型空间中的法向量
				float3 N = normalize(mul(float4(normalize(i.normal), 0), unity_WorldToObject).xyz);
				// 获取世界空间中的归一化光照向量
				float3 L = normalize(_WorldSpaceLightPos0.xyz);
				// 获取世界空间中法向量和光照向量的点积值:值越大,漫反射强度越大,角度越小。
				// 将点积值为负数时表示光照在平面背面,此时可以不做光照处理。因此将点积值要限制在0~1之间
				float ndotl = saturate(dot(N, L));
				// 使用光照强度乘以世界空间中的光照颜色,获取顶点的漫反射颜色
				fixed4 myDiffuse = _LightColor0 * ndotl;

				// 获取世界空间中入射光向量:光源指向顶点的向量,可以使用顶点指向光源向量的反操作获取。
				float3 I = -WorldSpaceLightDir(i.vertex);
				// 获取世界空间中归一化反射光向量
				float3 R = normalize(reflect(I, N)); //等价于normalize(2 * dot(N, L) * N - L);
				// 获取世界空间中归一化视角向量:顶点指向摄像机的向量
				float3 V = normalize(WorldSpaceViewDir(i.vertex));
				// 获取世界空间中反射光向量和视角向量的点积值:值越大,反射强度越大,夹角越小。
				// 将点积值为负数时表示反射光在平面背面,此时可以不做光照处理。因此将点积值要限制在0~1之间
				float3 rdotv = saturate(dot(R, V));
				// 使用平滑系数来控制反射强度衰减大小:
				// 平滑系数越大,反射强度乘以平滑系数平方值趋于0的顶点就会相对变多,从而高光范围就越小,反射光照越集中。
				// 平滑系数越小,反射强度乘以平滑系数平方值趋于0的顶点就会相对变少,从而高光范围就越大,反射光照越分散。
				float specularScale = pow(rdotv, _SpecularShiness);
				// 使用反射强度乘以反射光颜色,获取顶点最终反射颜色
				fixed4 mySpecular =  _SpecularColor * specularScale;

				// 获取漫反射 + 高光反射 + 环境光混合颜色
				o.col = myDiffuse + mySpecular + UNITY_LIGHTMODEL_AMBIENT;

				return o;
			}

			fixed4 frag(v2f i):COLOR
			{
				return i.col;
			}

			ENDCG
		}
	}
}

运行效果:如下所示:
在这里插入图片描述
通过半角向量实现流程:如下所示:
核心要点
1.顶点镜面反射颜色值等于反射光颜色乘以反射强度来获取。而光照向量,法向量,视角向量和半角向量的关系如图所示:
在这里插入图片描述
2.光照向量实际上就是顶点指向光源的向量。要获取该向量,可以使用光源向量减去顶点向量来获取(也就是调用WorldSpaceLightDir处理)。

3.视向量实际上就是顶点指向摄像机的向量。要获取该向量,可以使用摄像机向量减去顶点向量来获取(也就是调用WorldSpaceViewDir处理)。

4.半角向量可以使用光照向量与视角向量相加来获取。

5.获取半角向量和法向量的点积值。当点积值为负数时表示反射光在平面背面,可以不做反射光照处理。因此可以将点积值限制在0~1之间,并且点积值越大,反射强度越大,夹角越小。

6.反射强度并不是像第5点中那样描述的线性变化,而是存在平滑系数来控制反射强度衰减大小。平滑系数越大,反射强度乘以平滑系数平方值趋于0的顶点就会相对变多,从而高光范围就越小,反射光照越集中。平滑系数越小,反射强度乘以平滑系数平方值趋于0的顶点就会相对变少,从而高光范围就越大,反射光照越分散。

核心代码:如下所示:

Shader "Custom/demo13" {
	Properties {
		// 镜面反射光颜色
		_SpecularColor("Specular Color", COLOR) = (1, 0, 0, 1)
		// 镜面光平滑度
		_SpecularShiness("Specular Shiness", Range(1, 64)) = 8
	}

	SubShader {
		pass {
			Tags { "LightMode" = "ForwardBase" }

			CGPROGRAM
			#pragma vertex vert 
			#pragma fragment frag 
			#include "unitycg.cginc"
			#include "lighting.cginc"

			struct v2f {
				float4 pos:POSITION;
				fixed4 col:COLOR;
			};

			fixed4 _SpecularColor;
			float _SpecularShiness;

			v2f vert(appdata_base i)
			{
				v2f o;
				o.pos = mul(UNITY_MATRIX_MVP, i.vertex);
				
				// 获取世界空间中不受缩放影响的归一化法向量:使用模型到世界矩阵的逆矩阵的转置矩阵进行变换模型空间中的法向量
				float3 N = normalize(mul(float4(normalize(i.normal), 0), unity_WorldToObject).xyz);
				// 获取世界空间中的归一化光照向量
				float3 L = normalize(_WorldSpaceLightPos0.xyz);
				// 获取世界空间中法向量和光照向量的点积值:值越大,漫反射强度越大,角度越小。
				// 将点积值为负数时表示光照在平面背面,此时可以不做光照处理。因此将点积值要限制在0~1之间
				float ndotl = saturate(dot(N, L));
				// 使用光照强度乘以世界空间中的光照颜色,获取顶点的漫反射颜色
				fixed4 myDiffuse = _LightColor0 * ndotl;

				// 获取世界空间中归一化视角向量:顶点指向摄像机的向量
				float3 V = normalize(WorldSpaceViewDir(i.vertex));
				// 获取世界空间中归一化的半角向量
				float3 H = normalize(L + V);
				// 获取世界空间中半角向量和法向量的点积值:值越大,反射强度越大,夹角越小。
				// 将点积值为负数时表示反射光在平面背面,此时可以不做光照处理。因此将点积值要限制在0~1之间
				float3 hdotv = saturate(dot(H, N));
				// 使用平滑系数来控制反射强度衰减大小:
				// 平滑系数越大,反射强度乘以平滑系数平方值趋于0的顶点就会相对变多,从而高光范围就越小,反射光照越集中。
				// 平滑系数越小,反射强度乘以平滑系数平方值趋于0的顶点就会相对变少,从而高光范围就越大,反射光照越分散。
				float specularScale = pow(hdotv, _SpecularShiness);
				// 使用反射强度乘以反射光颜色,获取顶点最终反射颜色
				fixed4 mySpecular =  _SpecularColor * specularScale;

				// 获取漫反射 + 高光反射 + 环境光混合颜色
				o.col = myDiffuse + mySpecular + UNITY_LIGHTMODEL_AMBIENT;

				return o;
			}

			fixed4 frag(v2f i):COLOR
			{
				return i.col;
			}

			ENDCG
		}
	}
}

运行结果:如下图所示:
在这里插入图片描述
知识小点
1.兰伯特模型就是包含环境光和漫反射光照处理的模型。
2.Phong模型和Bill-Phong模型就是在兰伯特模型的基础上增加了一个镜面高光处理的模型。其中Phong模型是通过反射光向量(法向量和入射光向量点积获取)实现镜面高光处理,而Bill-Phong模型是通过半角向量(光照向量和视角向量相加获取)实现镜面高光处理。

发布了81 篇原创文章 · 获赞 39 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/zjz520yy/article/details/88770575