1. 程式人生 > 其它 >【Unity Shader學習筆記】Unity光照基礎-高光反射

【Unity Shader學習筆記】Unity光照基礎-高光反射

1、原理

1.1、Phong模型

計算高光反射需要表面法線、視角方向、光源方向、反射方向等。

在這四個向量中,我們實際上只需要知道其中3個向量即可,而第4個向量(反射方向r)可以通過其他資訊計算得到:

這樣,我們就可以利用Phong模型來計算高光反射的部分:

m(gloss)是材質的光澤度,也被反稱為反光度。它用於控制高光區域的“亮點”有多寬,m(gloss)越大,亮點就越小。
m(spscular)是材質的高光反射顏色,它用於控制該材質對於高光反射的強度和顏色。
c(light)則是光源的顏色和強度。
使用max防止結果為負。

1.2、Blinn模型

它的基本思想是,避免計算反射方向r。為此,Blinn模型引入了一個新的向量h,如下:

然後,使用n和h之間的夾角進行計算,而非v和r之間的夾角,如下圖所示:

總結一下,Blinn模型的公式如下:

在硬體實現時,如果攝像機和光源距離模型足夠遠的話,Blinn模型會快於Phong模型。
當V或者I不是定值時,Phong模型可能反而更快一些。

2、程式碼實現

2.1、逐頂點光照

先計算漫反射部分,再計算高光反射部分。最後把漫反射、高光反射與環境光加到一起形成最後顏色。
因為需要計算漫反射與高光反射,Properties中含有三個變數:

Properties
    {
        _Diffuse ("Diffuse", Color) = (1, 1, 1, 1)
        _Specular("Specular", Color) = (1, 1, 1, 1)
        _Gloss("Gloss", Range(8.0, 256)) = 20
    }

在Pass程式碼塊中寫Tags、#include、定義結構體:

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;
            fixed3 normal : NORMAL;
            };
            struct v2f {
            float4 pos : SV_POSITION;
            fixed3 color : COLOR;
    };

主要計算集中在頂點著色器。
頂點著色器中,首先把座標從模型空間變換到裁剪空間。
隨後獲得環境光向量、worldNormal法線向量、worldLightDir光線向量。
計算漫反射項diffuse。

v2f vert(a2v v)
{
	v2f o;
	o.pos = UnityObjectToClipPos(v.vertex);
	//獲得環境光
	fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
	//世界座標下法線方向
	fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
	//世界座標下光線方向
	fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
	//計算漫反射
	fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));

使用reflect()函式並歸一化,求得反射方向。
視角方向的計算,需要先_WorldSpaceCameraPos.xyz求得攝像機位置向量,再與mul(unity_ObjectToWorld, v.vertex).xyz頂點位置向量相減,即可得到視角方向的向量。使用normalize歸一化。

	//計算反射方向
	fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
	//計算視角方向
	fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);

最後便可以計算高光反射。環境光、漫反射、高光反射三者相加即可得到最後的顏色。

pow(x, y)即x的y次方。

	//計算高光反射
	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);
}

最終程式碼如下:

Shader "Practice/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"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"

            // 變數
            fixed4 _Diffuse;
            fixed4 _Specular;
            float _Gloss;
            struct a2v {
            float4 vertex : POSITION;
            fixed3 normal : NORMAL;
            };
            struct v2f {
            float4 pos : SV_POSITION;
            fixed3 color : COLOR;
            };
            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                //獲得環境光
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                //世界座標下法線方向
                fixed3 worldNormal = normalize(mul(v.normal, (float3x3)unity_WorldToObject));
                //世界座標下光線方向
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                //計算漫反射
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal, worldLightDir));

                //計算反射方向
                fixed3 reflectDir = normalize(reflect(-worldLightDir, worldNormal));
                //計算視角方向
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - mul(unity_ObjectToWorld, v.vertex).xyz);
                //計算高光反射
                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"
}

逐頂點的高光反射會出現視覺問題,因為高光反射部分的計算是非線性的,而頂點著色器中計算光照再進行插值的過程是線性的。
效果如下,可以看到過度不太平滑。

2.2、逐畫素光照

逐畫素光照只要在頂點著色器中把變數的座標轉換做好,計算部分集中放在片元著色器即可。
具體程式碼如下:

Shader "Unity Shaders Book/Chapter 6/Specular Pixel-Level"
{
	Properties{
		//宣告屬性,使用之前需要在Pass中定義
		_Diffuse("Diffuse", Color) = (1, 1, 1, 1)
		//用於控制材質的高光反射顏色
		_Specular("Specular", Color) = (1, 1, 1, 1)
		//用於控制高光區域的大小 
		_Gloss("Gloss", Range(8.0, 256)) = 20
	}
	SubShader{
		Pass {
			//LightMode用於定義該Pass在Unity的光照流水線上的角色。
			Tags {"LightMode" = "ForwardBase"}
			CGPROGRAM
			//告訴Unity,我們定義的頂點與片元著色器函式叫什麼名字
			#pragma vertex vert
			#pragma fragment frag
			#include "Lighting.cginc"
			//定義與屬性型別相匹配的變數
			fixed4 _Diffuse;
			fixed4 _Specular;
			float _Gloss;
			//定義頂點著色器的輸入與輸出結構體
			struct a2v {
				float4 vertex : POSITION;
				fixed3 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 world space
				o.pos = UnityObjectToClipPos(v.vertex);
				o.worldNormal = mul(v.normal, (float3x3)unity_WorldToObject);
				o.worldPos = mul(unity_ObjectToWorld, 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);
			fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir, viewDir)), _Gloss);

			return fixed4(ambient + diffuse + specular, 1.0);

			}


			ENDCG

		}
	}
		Fallback "Specular"
}