Unity Shader 漫反射光照模型

        在计算光照模型时,通常来讲会有两种:在顶点着色器中计算的称为逐顶点光照;在片元着色器中计算的被称为逐像素光照,我们先讲逐顶点光照。

        逐顶点光照也被称为高洛德着色,我们在每个顶点上计算光照,然后会在渲染图元内部进行线性插值,最后输出成像素颜色。由于顶点数目往往远小于像素数目,因此逐顶点光照的计算量往往要小于逐像素光照。但是由于逐顶点光照依赖于线性插值来得到像素光照,因此当光照模型中有非线性的计算(如计算高光反射时),逐顶点光照就会出现问题。且逐顶点光照会在渲染图元内部对顶点颜色进行插值,会导致渲染图源内部的颜色总是暗于顶点处的最高颜色值,这在某些情况下会产生明显的棱角现象。

        在Unity Shader中实现漫反射光照模型,计算公式:

        为了防止点积结果为负值,我们需要使用max操作,在CG中提供了saturate(x)函数能达到同样的效果。

Shader "Unlit/Diffuse Vertex-Level" {
	Properties {
        //声明一个Color类型的属性,初始值设为白色
		_Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
	}
	SubShader {
		Pass { 
            //设置光照模式,只有定义正确的光照模式才能得到Unity的内置光照变量
			Tags { "LightMode"="ForwardBase" }
		
			CGPROGRAM
			
			#pragma vertex vert
			#pragma fragment frag
			
            //需要使用Unity内置一些变量,如_LightColor0
			#include "Lighting.cginc"
			
            //定义一个和Properties语义块中类型匹配的变量,得到材质的漫反射属性
			fixed4 _Diffuse;
			
            //顶点着色器的输入结构体
			struct a2v {
				float4 vertex : POSITION;
                //模型法线信息
				float3 normal : NORMAL;
			};
			
           //顶点着色器的输出结构体,也是片元着色器的输入结构体
			struct v2f {
				float4 pos : SV_POSITION;
                //将顶点着色器中计算得到的光照颜色传递给片元着色器
				fixed3 color : COLOR;
			};
			
			v2f vert(a2v v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
				
				//通过Unity内置变量得到环境光
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
				
                //计算法线和光源方向的点积时,要在同一坐标空间下
				//将法线从模型空间转换到世界空间
				fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
				//得到光源方向
				fixed3 worldLight = normalize(_WorldSpaceLightPos0.xyz);
				//计算漫反射(_LightColor0是Unity内置变量)
				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"
}

        然后是逐像素光照,我们会以每个像素为基础,得到它的法线,然后进行光照模型的计算。

Shader "Unlit/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;
			};
			
			v2f vert(a2v v) {
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);

				//将世界空间下的法线传递给片元着色器即可
				o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);

				return o;
			}
			
			fixed4 frag(v2f i) : SV_Target {
				//通过Unity内置变量获得光照强度
				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 color = ambient + diffuse;
				
				return fixed4(color, 1.0);
			}
			
			ENDCG
		}
	} 
	FallBack "Diffuse"
}

        逐像素光照可以得到更加平滑的光照效果,但是即使使用了逐像素漫反射光照,还有一个问题仍然存在,在光照无法到达的区域,模型的外观通常是全黑的,没有任何明暗变化,会使得模型背光区域看起来就像一个平面一样,失去了模型细节表现。我们可以用一种改善技术来解决,就是半兰伯特光照模型。

			fixed4 frag(v2f i) : SV_Target {
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);

				fixed halfLambert = dot(worldNormal, worldLightDir) * 0.5 + 0.5;
				fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambert;
				
				fixed3 color = ambient + diffuse;
				
				return fixed4(color, 1.0);
			}

         直接用半兰伯特公式修改片元着色器中计算漫反射光照的部分即可。

        

猜你喜欢

转载自blog.csdn.net/weixin_45081191/article/details/129215357