Unity Shader - 對Cubemap進行環境對映(世界空間和切線空間下的對比)
取樣圖效果:
上面3幅圖的效果分別是:原始Cubemap、世界空間下的取樣、切線空間下的取樣;
由以上對比圖可知,在需要使用Cubemap 進行環境對映等情況下,我們就需要在世界空間下對Cubemap進行取樣。
切線空間轉換到世界空間的方法:
//世界空間下的法線、切線、副法線 float3 worldPos = mul(_Object2World, v.vertex).xyz; fixed3 worldNormal = UnityObjectToWorldNormal(v.normal); fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz); fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; //構造切線空間到世界空間的旋轉矩陣 o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x); o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y); o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
模型空間轉換到切線空間的方法:
//世界空間光源方向(指向光源) float4 lightDirWorld = float4(normalize(_WorldSpaceLightPos0.xyz), 1); //從世界空間到觀察空間 float4 lightDirObj = mul(lightDirView, UNITY_MATRIX_IT_MV); float4 lightDirView = mul(UNITY_MATRIX_V, lightDirWorld); //從觀察空間到模型空間 //rotation為模型空間到切線空間的旋轉矩陣 TANGENT_SPACE_ROTATION; //o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)); o.lightDir = mul(rotation, lightDirObj); //ObjectSpaceLightDir可以把光線方向轉化到模型空間,然後通過rotation再轉化到切線空間 //等同於上面的語句 //o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)); //方式二
完整Shader程式碼如下:
Shader "Unity Shaders Book/Chapter 15/Water Wave" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} _WaveMap ("Wave Map", 2D) = "bump" {} _Cubemap ("Environment Cubemap", Cube) = "_Skybox" {} _WaveXSpeed ("Wave Horizontal Speed", Range(-0.1, 0.1)) = 0.01 _WaveYSpeed ("Wave Vertical Speed", Range(-0.1, 0.1)) = 0.01 _Color ("Main Color", Color) = (0, 0.15, 0.115, 1) } SubShader { // We must be transparent, so other objects are drawn before this one. Tags { "Queue"="Transparent" "RenderType"="Opaque" } Pass { Tags { "LightMode"="ForwardBase" } CGPROGRAM #include "UnityCG.cginc" #include "Lighting.cginc" #pragma vertex vert #pragma fragment frag sampler2D _MainTex; float4 _MainTex_ST; sampler2D _WaveMap; float4 _WaveMap_ST; samplerCUBE _Cubemap; fixed _WaveXSpeed; fixed _WaveYSpeed; fixed4 _Color; struct a2v { float4 vertex : POSITION; float3 normal : NORMAL; float4 tangent : TANGENT; float4 texcoord : TEXCOORD0; }; struct v2f { float4 pos : SV_POSITION; float4 uv : TEXCOORD1; float4 TtoW0 : TEXCOORD2; float4 TtoW1 : TEXCOORD3; float4 TtoW2 : TEXCOORD4; float3 lightDir : TEXCOORD5; }; v2f vert(a2v v) { v2f o; o.pos = mul(UNITY_MATRIX_MVP, v.vertex); o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex); o.uv.zw = TRANSFORM_TEX(v.texcoord, _WaveMap); //世界空間下的法線、切線、副法線 float3 worldPos = mul(_Object2World, v.vertex).xyz; fixed3 worldNormal = UnityObjectToWorldNormal(v.normal); fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz); fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w; //構造切線空間到世界空間的旋轉矩陣 o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x); o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y); o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z); //世界空間光源方向(指向光源) float4 lightDirWorld = float4(normalize(_WorldSpaceLightPos0.xyz), 1); //從世界空間到觀察空間 float4 lightDirView = mul(UNITY_MATRIX_V, lightDirWorld); //從觀察空間到模型空間 float4 lightDirObj = mul(lightDirView, UNITY_MATRIX_IT_MV); //rotation為模型空間到切線空間的旋轉矩陣 TANGENT_SPACE_ROTATION; o.lightDir = mul(rotation, lightDirObj); //方式一 //ObjectSpaceLightDir可以把光線方向轉化到模型空間,然後通過rotation再轉化到切線空間 //等同於上面的語句 //o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)); //方式二 return o; } fixed4 frag(v2f i) : SV_Target { float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w); fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos)); // Get the normal in tangent space fixed3 bump = UnpackNormal(tex2D(_WaveMap, i.uv.zw)).rgb; float2 speed = _Time.y * float2(_WaveXSpeed, _WaveYSpeed); fixed4 texColor = tex2D(_MainTex, i.uv.xy + speed); // Convert the normal to world space //世界空間下的反射光 bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump))); fixed3 reflDir = reflect(-viewDir, bump); fixed3 reflCol = texCUBE(_Cubemap, reflDir).rgb * _Color.rgb; //切線空間下的反射光 //fixed3 reflDir = reflect(-i.lightDir, bump); //fixed3 reflCol = texCUBE(_Cubemap, reflDir).rgb * _Color.rgb; fixed3 finalColor = reflCol; return fixed4(finalColor, 1); } ENDCG } } // Do not cast shadow FallBack Off }
世界空間下和切線空間下的效果分別如下面上、下兩幅圖:
(世界空間)(切線空間)
結論:在需要使用Cubemap 進行環境對映等情況下,我們就需要在世界空間下對Cubemap進行取樣。
UnityShader之空間變換解析
我們都知道要渲染出一個物體,需要經歷以下空間變換,
模型座標(通常由美術決定)----->世界座標----->觀察空間(相機位置決定)------------>裁剪空間(相機型別和引數決定)------->投影
unity提供了一系列的矩陣來幫助使用者進行座標空間變換,這裡列個表記錄下:
1:模型空間 2:世界空間 3:觀察空間 4:裁剪空間
UNITY_MATRIX_MVP:將座標或者方向從1到4
UNITY_MATRIX_MV:將座標或者方向從1到3
UNITY_MATRIX_VP:將座標或者方向從2到4
UNITY_MATRIX_V:將座標或者方向從2到3
UNITY_MATRIX_P:將座標或者方向從3到4
unity_ObjectToWorld:將頂點或者方向從1到2
unity_WorldToObject:unity_ObjectToWorld的逆矩陣,用於將頂點方向從2到1
重點講下
UNITY_MATRIX_IT_MV:專門用於將法線從模型空間變換到觀察空間,為UNITY_MATRIX_MV的逆轉置矩陣,如果UNITY_MATRIX_MV為正交矩陣(意味著只有旋轉操作),那麼UNITY_MATRIX_MV就等於UNITY_MATRIX_IT_MV,如果有統一縮放操作,UNITY_MATRIX_IT_MV=(1/k)*UNITY_MATRIX_MV ,k為縮放係數((為什麼不用UNITY_MATRIX_MV來處理法線,因為如果模型有不統一縮放的時候,用UNITY_MATRIX_MV會造成方向失真),我也可以把這個矩陣轉置一下或者UNITY_MATRIX_MV的逆矩陣,就可以把頂點或者方向向量從觀察空間變換到模型空間,例如
float4 modelPos = mul(transpose(UNITY_MATRIX_IT_MV),viewPos)=mul(viewPos,UNITY_MATRIX_IT_MV);
UNITY_MATRIX_T_MV:UNITY_MATRIX_MV的轉置矩陣,因為求一個矩陣的逆矩陣需要巨大的運算量,但是求一個矩陣的轉置矩陣就很簡單了,所以當一個矩陣為正交矩陣的時候,通過轉置矩陣或者逆矩陣是一個很有用的方法,我們在計算一些方向向量,不考慮平移操作,縮放係數設為1,這樣就可以用這個矩陣來計算從觀察空間到模型空間的方向向量變換;