UnityShader學習之旅(2)初識光照

 

以下內容轉至https://blog.csdn.net/puppet_master/article/details/53074789

簡介

學了一段時間shader,然而一直在玩後處理,現在終於下定決心鑽研一下真正的帶光照的shader。從Diffuse到Specular。一個遊戲的畫面好壞,很大程度上取決於光照和貼圖。現實世界中,我們之所以能看見東西,是因爲他們要麼反射了光源發出的光,要麼是自身能夠發光。而在遊戲世界中,如果沒有了光,我們雖然可以直接根據貼圖顯示物體的材質,但是少了很多細節光影效果,遊戲顯得不真實。但是,真實的光照計算是一個非常複雜的過程,對於遊戲這種至少30FPS的程序來說是完全不可能的,所以我們必須要使用一種近似的光照算法,來模擬光照效果。本篇文章就來學習一下基本的光照模型以及其在unity下的shader實現。

漫反射和鏡面反射

我們觀察世界是因爲有光進入我們的眼睛,光在世界中主要有反射和折射兩種屬性,當光照在某種介質表面時,一部分光發生反射,另一部分光進入介質,發生折射,也有轉化爲其他能量的光。本篇文章只討論反射,折射等其他現象以後再學習。光的反射分爲兩種,漫反射和鏡面反射。

漫反射,是投射在粗糙表面上的光向各個方向反射的現象。當一束平行的入射光線射到粗糙的表面時,表面會把光線向着四面八方反射,所以入射線雖然互相平行,由於各點的法線方向不一致,造成反射光線向不同的方向無規則地反射,這種反射稱之爲“漫反射”或“漫射”。這種反射的光稱爲漫射光。很多物體,如植物、牆壁、衣服等,其表面粗看起來似乎是平滑,但用放大鏡仔細觀察,就會看到其表面是凹凸不平的,所以本來是平行的太陽光被這些表面反射後,瀰漫地射向不同方向。

鏡面反射,是指若反射面比較光滑,當平行入射的光線射到這個反射面時,仍會平行地向一個方向反射出來,這種反射就屬於鏡面反射,其反射波的方向與反射平面的法線夾角(反射角),與入射波方向與該反射平面法線的夾角(入射角)相等,且入射波、反射波,及平面法線同處於一個平面內。

蘭伯特光照模型

先來學習一個最簡單的光照模型,蘭伯特光照模型。蘭伯特光照模型是目前最簡單通用的模擬漫反射的光照模型,定義如下:模型表面的明亮度直接取決於光線向量(light vector)和表面法線(normal)兩個向量將夾角的餘弦值。光線向量是指這個點到光從哪個方向射入,表面法線則定義了這個表面的朝向。


如果漫反射光強設置爲Diffuse,入射光光強爲I,光方向和法線夾角爲θ,那麼蘭伯特光照模型可以用下面的公式表示:Diffuse = I * cosθ
進一步地,我們可以通過點乘來求得兩個方向向量之間的夾角,入射光方向設置爲L,法線方向設置爲N,如果光方向向量和法線方向向量都爲單位向量(這就是爲什麼我們在寫shader的時候需要normalize操作的原因),那麼它們之間的夾角餘弦值就可以表示爲:cosθ = dot(L,N),最終漫反射光強公式,也就是蘭伯特光照模型可以表示爲:Diffuse = I  * dot(L,N)


逐頂點計算着色shader

我們在shader中需要計算輸出的顏色,逐頂點着色也就是說我們的計算主要放在了vertex shader中,根據頂點來計算,每個頂點中計算出了該點的顏色,直接作爲vertex shader的輸出,pixel(fragment) shader的輸入,當到達pixel階段時,直接輸出頂點shader的結果。比如一個三角形面片,在vertex階段,分別計算了每個頂點的顏色值,在pixel階段時,這個面片經過投影,最終顯示在屏幕上的像素,會根據該像素周圍的頂點來插值計算像素的最終顏色,這種着色方式也叫做高洛德着色。

下面看一下unity shader實現的逐頂點着色:
 


Shader "ApcShader/DiffusePerVetex"
{
	//屬性
	Properties{
		_Diffuse("Diffuse", Color) = (1,1,1,1)
	}
 
	//子着色器	
	SubShader
	{
		Pass
		{
			//定義Tags
			Tags{ "RenderType" = "Opaque" }
 
			CGPROGRAM
			//引入頭文件
	        #include "Lighting.cginc"
			//定義Properties中的變量
			fixed4 _Diffuse;
			//定義結構體:應用階段到vertex shader階段的數據,如果定義了
			struct a2v
			{
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};
			//定義結構體:vertex shader階段輸出的內容
			struct v2f
			{
				float4 pos : SV_POSITION;
				fixed4 color : COLOR;
			};
 
			//定義頂點shader
			v2f vert(a2v v)
			{
				v2f o;
				o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
				//把法線轉化到世界空間
				float3 worldNormal = mul(v.normal, (float3x3)_World2Object);
				//歸一化法線
				worldNormal = normalize(worldNormal);
				//把光照方向歸一化
				fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
				//根據蘭伯特模型計算頂點的光照信息,dot可能有負值,小於0的部分可以理解爲看不見,直接取0
				fixed3 lambert = max(0.0, dot(worldNormal, worldLightDir));
				//最終輸出顏色爲lambert光強*材質diffuse顏色*光顏色
				o.color = fixed4(lambert * _Diffuse.xyz * _LightColor0.xyz, 1.0);
				return o;
			}
 
			//定義片元shader
			fixed4 frag(v2f i) : SV_Target
			{
				return i.color;
			}
 
			//使用vert函數和frag函數
			#pragma vertex vert
			#pragma fragment frag	
 
			ENDCG
		}
 
	}
	//前面的Shader失效的話,使用默認的Diffuse
	FallBack "Diffuse"
}
Phong光照模型的Shader實現


Shader "lijia/Phong"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Specular("Specular", Range(1, 20)) = 1
        _SpecColor("SpecColor", Color) = (1,1,1,1)
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 normal : TEXCOORD1;
                float3 lightDir : TEXCOORD2;
                float4 objPos : TEXCOORD3;
            };

            sampler2D _MainTex;
            float4 _MainTex_ST;

            float4 _LightColor0;

            float _Specular;
            float4 _SpecColor;

            v2f vert (appdata_full v)
            {
                v2f o;
                o.objPos = v.vertex;
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
                o.normal = v.normal;
                o.lightDir = ObjSpaceLightDir(v.vertex);//把光向量從世界空間轉成模型空間
                return o;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float3 L = normalize(i.lightDir);
                float3 N = normalize(i.normal);
                float3 viewDir = normalize(ObjSpaceViewDir(i.objPos));//計算出視線

                float diff = saturate(dot(L, N));
                float3 reflection = normalize(2.0 * N * diff - L);//反射向量
                float spec = pow(max(0, dot(reflection, viewDir)), _Specular);
                float3 finalSpec = _SpecColor.rgb * spec;
                //漫反射+鏡面高光+環境光
                float3 finalLight = diff * _LightColor0 + finalSpec + UNITY_LIGHTMODEL_AMBIENT;

                fixed4 col = tex2D(_MainTex, i.uv);
                return col * float4(finalLight, 1);
            }
            ENDCG
        }
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章