【图形学】18 光照模型(三、镜面反射的Shader实现)

来源:《UNITY SHADER入门精要》

1、镜面反射ShaderLab的方式

  我们回忆之前的公式:
c s p c u l a r = ( c l i g h t ⋅ m s p e c u l a r ) max ⁡ ( 0 , v ^    ⋅    r ) m g l o s s \boldsymbol{c}_{spcular}=\left( \boldsymbol{c}_{light}\cdot \boldsymbol{m}_{specular} \right) \max \left( 0, \hat{v}\,\,\cdot \,\,\boldsymbol{r} \right) ^{m_{gloss}} cspcular=(clightmspecular)max(0,v^r)mgloss
  我们计算镜面反射需要四个参数:入射光线 c l i g h t \boldsymbol{c}_{light} clight 、材质的高光反射系数 m s p e c u l a r \boldsymbol{m}_{specular} mspecular 、视角方向 v ^ \hat{v} v^ 、反射方向 r \boldsymbol{r} r 。而其中,反射方向 r \boldsymbol{r} r 又需要由表面法线 n ^ \hat{n} n^ 和 光源方向 I \boldsymbol{I} I 计算而得。

  公式不难写出,不过,CG提供了计算反射方向的函数:reflect(i, n)。其中参数:i 为入射方向;n 为法线方向。

2、逐顶点的镜面反射

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" }
			

  这里定义了三个 Properties ,其中,_Diffuse 用来控制漫反射的颜色系数, _Specular 用来控制高光反射的颜色, _Gloss 用来控制高光区域的大小。
  这里的 LightMode 设置为 ForwardBase 定义了该 Pass 在 Unity 的光照流水线中的角色。

			CGPROGRAM
			
			#pragma vertex vert
			#pragma fragment frag
			
			#include "Lighting.cginc"
			
			fixed4 _Diffuse;
			fixed4 _Specular;
			float _Gloss;

  同样,在CGPROGRAM之后,我们包含头文件"Lighting.cginc",用 #pragma 指定顶点/片元着色器的函数。
  声明 Properties 语义块中的属性:由于颜色属性的范围在0到1之间,因此对于Diffuse和Specular属性我们可以使用fixed精度的变量来存储它。而Gloss的范围很大,因此我们使用flot精度来存储。

			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
				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 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
				
				// Compute diffuse term
				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));

  前面的计算是关于漫反射的,跟之前是一致的。

				// Get the reflect direction in world space
				fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
				// Get the view direction in world space,计算的是每一个顶点在世界空间的位置。
				fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);
				
				// Compute specular term
				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"
}

  这里采用的 CG 的 reflect() 函数计算了反射光线。由于参数要求传入的方向是从光源到交点处的,所以,采用的是它的反向量。
  然后,我们用计算得到的反射向量,计算得到高光值,然后都相加起来,返回就好了。
  最后,第21行,我们磨人的FallBack设置为 内置的 Specular。

  最后,我们注意到,逐顶点光照,由于线性插值的缘故,会有明细的线性效果,不够真实。所以,我们需要逐像素来出来计算。在这里插入图片描述

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oIf2liTz-1659590622745)(assets/image-20220620162252379.png)]

3、逐像素光照计算

  

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
			
			#pragma vertex vert
			#pragma fragment frag

			#include "Lighting.cginc"
			
			fixed4 _Diffuse;
			fixed4 _Specular;
			float _Gloss;

  前面的代码和之前没有区别

			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};
			
			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
			};

  因为是逐像素进行计算。这里的输出皆使用纹理坐标的语义来接收。

			v2f vert(a2v v) {
				v2f o;
				// Transform the vertex from object space to projection space
				o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
				
				// Transform the normal from object space to world space
				o.worldNormal = mul(v.normal, (float3x3)_World2Object);
				// Transform the vertex from object spacet to world space
				o.worldPos = mul(_Object2World, v.vertex).xyz;
				
				return o;
			}

  顶点着色器所做的事情就是计算 世界空间下的法线方向和顶点坐标,并把它们传递给片元着色器。

			fixed4 frag(v2f i) : SV_Target {
				// Get ambient term
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
				
				// Compute diffuse term
				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));
				
				// Get the reflect direction in world space
				fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
				// Get the view direction in world space
				fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
				// Compute specular term
				fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);
				
				return fixed4(ambient + diffuse + specular, 1.0);
			}
			
			ENDCG
		}
	} 
	FallBack "Specular"
}

猜你喜欢

转载自blog.csdn.net/qq_40891541/article/details/126157700