UnityShader入門精要筆記2——使用Cubemap實現鏡面反射和折射

先上效果圖

在這裏插入圖片描述
(拖到最下面直接看代碼)

立方體紋理

立方體紋理(Cubemap)由6張圖像組成,對這個紋理進行採樣時,需要提供一個向量,它會從立方體中間出發,當它向外延伸時就會與立方體的6個紋理之一相交,而採樣結果就是由該交點(藍點)計算而來【candycat】

在這裏插入圖片描述

讓着色器使用立方體紋理

(當然,unity自帶的着色器中,也有一些能夠直接使用立方體紋理,例如Legacy Shaders - Reflective裏面的那幾個)
在這裏插入圖片描述

  1. 首先新建一個Unity Shader,把原有代碼全部刪除,然後給shader起個名字。爲Shader添加一個Properties 語義塊,聲明我們需要的屬性。
Shader "RefShader" {
	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1) //漫反射底色
		_ReflectColor ("Reflection Color", Color) = (1, 1, 1, 1) //反射顏色
		_ReflectAmount ("Reflect Amount", Range(0, 1)) = 1 //反射光大小
		_Cubemap ("Reflection Cubemap", Cube) = "_Skybox" {} //立方體紋理
		//對於2D,3D,Cube這3鍾紋理類型,
		//它們的默認值是通過一個字符串後跟一個花括號來指定的。
		//其中,字符串要麼是空的,要麼是內置的紋理名稱。
	}
}

這個 _Cubemap就是稍後我們要進行採樣的立方體紋理。

  1. 爲Shader添加SubShader和Pass,並設置標籤“光照類型”(LightMode)爲“向前渲染”(ForwardBase)
Shader "RefShader" {
	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1) //漫反射底色
		_ReflectColor ("Reflection Color", Color) = (1, 1, 1, 1) //反射顏色
		_ReflectAmount ("Reflect Amount", Range(0, 1)) = 1 //反射光大小
		_Cubemap ("Reflection Cubemap", Cube) = "_Skybox" {} //立方體紋理
	}
	SubShader {
		Pass {
			//設置正確的標籤
			Tags { "LightMode"="ForwardBase" }
		}
	}
}
  1. 使用CG/HLSL語言來編寫頂點/片元着色器
Shader "RefShader" {
	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1) //漫反射底色
		_ReflectColor ("Reflection Color", Color) = (1, 1, 1, 1) //反射顏色
		_ReflectAmount ("Reflect Amount", Range(0, 1)) = 1 //反射光大小
		_Cubemap ("Reflection Cubemap", Cube) = "_Skybox" {} //立方體紋理
	}
	SubShader {
		Pass {
			Tags { "LightMode"="ForwardBase" }
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			struct a2v {
				float4 vertex : POSITION; 
				float3 normal : NORMAL;
			};
			
			struct v2f {
				//SV_POSITION語義告訴Unity,pos裏包含了頂點在裁剪空間中的位置信息
				//這也是頂點着色器最重要的一個工作:將頂點座標從模型空間轉換到裁剪空間
				float4 pos : SV_POSITION;
				//TEXCOORD0語義表示worldNormal變量佔用了TEXCOORD0插值寄存器
				//每個插值寄存器可以存儲4個浮點值(float)
				float3 worldNormal : TEXCOORD0; //世界空間下的頂點法線向量
				float3 worldPos : TEXCOORD1; //世界空間下的頂點座標
				float3 worldViewDir : TEXCOORD2; //世界空間下的觀察方向,也就是從頂點到攝像機的方向
				float3 worldRef1 : TEXCOORD3; //鏡面反射光的方向
				SHADOW_COORDS(4) //用於處理陰影的宏
			};
			
			v2f vert(a2v v) {
			}
			
			fixed4 frag(v2f i) : SV_Target {
			}

			ENDCG
		}
	}
}

現在,我們可以在頂點着色器中計算我們需要的信息了

	v2f vert(a2v v)
	{
		//申明返回值v2f
		v2f o;
		
		//這是頂點着色器最重要的一個任務,將頂點座標從模型空間轉換到裁剪空間
		//UnityObjectToClipPos函數接受一個模型空間的座標,返回該座標在裁剪空間的座標
		o.pos = UnityObjectToClipPos(v.vertex);
		
		//UnityObjectToWorldNormal函數接受一個模型空間的法線向量,將其轉換到世界空間中並返回
		o.worldNormal = UnityObjectToWorldNormal(v.normal);
		
		//unity_ObjectToWorld是unity模型空間到世界空間的變換矩陣
		//對v.vertex進行左乘,使其變換到世界空間下
		o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
		
		//UnityWorldSpaceViewDir函數接受一個世界空間中的頂點位置
		//返回世界空間中從該點到攝像機的觀察方向
		o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);
	
		//反射光
		o.worldRef1 = reflect(-o.worldViewDir, o.worldNormal);
		
		//用於處理陰影的宏
		TRANSFER_SHADOW(o);
	
		return o;
	}

其中,反射光的計算是這樣子來的:
reflect函數需要輸入兩個值,第一個參數是入射向量l,第二個是法向量n,經過計算 [r^ = l^ - 2 ( n^ · l^ ) n^ ] ( v^表示單位向量)reflect函數返回反射方向r

在這裏插入圖片描述
在這裏插入圖片描述

shader:

Shader "RefShader" {
	Properties {
		_Color ("Color Tint", Color) = (1, 1, 1, 1)
		_ReflectColor ("Reflection Color", Color) = (1, 1, 1, 1)
		_ReflectAmount ("Reflect Amount", Range(0, 1)) = 1
		_Cubemap ("Reflection Cubemap", Cube) = "_Skybox" {}
	}
	
	SubShader {
		Pass {
			Tags { "LightMode"="ForwardBase" }

			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			#include "Lighting.cginc"
			#include "AutoLight.cginc"

			fixed4 _Color;
			fixed4 _ReflectColor;
			float _ReflectAmount;
			samplerCUBE _Cubemap;

			struct a2v {
				float4 vertex : POSITION;
				float3 normal : NORMAL;
			};

			struct v2f {
				float4 pos : SV_POSITION;
				float3 worldNormal : TEXCOORD0;
				float3 worldPos : TEXCOORD1;
				float3 worldViewDir : TEXCOORD2;
				float3 worldRef1 : TEXCOORD3;
				SHADOW_COORDS(4)
			};

			v2f vert(a2v v)
			{
				v2f o;

				o.pos = UnityObjectToClipPos(v.vertex);

				o.worldNormal = UnityObjectToWorldNormal(v.normal);

				o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;

				o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);

				//反射光
				o.worldRef1 = reflect(-o.worldViewDir, o.worldNormal);

				TRANSFER_SHADOW(o);

				return o;
			}

			fixed4 frag(v2f i) : SV_Target {
				fixed3 worldNormal = normalize(i.worldNormal);
				fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
				fixed3 worldViewDir = normalize(i.worldViewDir);
				
				fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

				fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir));
				
				fixed3 reflection = texCUBE(_Cubemap, i.worldRef1).rgb * _ReflectColor.rgb;
				//texCUBE(samplerCUBE, float3|half3|min10float3|min16float3)
				//texCUBE(samplerCUBE, float3|half3|min10float3|min16float3, float3|half3|min10float3|min16float3, float3|half3|min10float3|min16float3)
				UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);

				fixed3 color = ambient + lerp(diffuse, reflection, _ReflectAmount) * atten;
				return fixed4(color, 1.0);
			}
			
			ENDCG
		}
	}

	Fallback "Specular"
}

C#腳本:

    RenderTexture cubemap;
    Camera refCamera;
    // Use this for initialization
    void Start () {
        gameObject.AddComponent<Camera>();
        cubemap = new RenderTexture(128, 128, 16)
        {
            dimension = UnityEngine.Rendering.TextureDimension.Cube
        };
        GetComponent<Renderer>().material.SetTexture("_Cubemap", cubemap);
        refCamera = GetComponent<Camera>();
    }

    // Update is called once per frame
    void Update () {
        refCamera.RenderToCubemap(cubemap);
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章