文章內使用Unity 2019 LTS
本篇延續上一篇
昨天Day15的時候提到,現在我們的向量是儲存在一張貼圖裡面,還知道法向向量是怎麼轉換成顏色倒貼圖上的。
接下來,如果要使用這張圖的話,我們還需要做一些轉換。先前的範例中,在處裡頂點的時候,有利用線性轉換經過需許多「空間」,在Unity解釋甚麼是空間的話,就是transform.localPosition
跟transform.position
的關係,transform.localPosition
就是存在於模型空間(Model Space);transform.position
則是是存在於世界空間(World Space),而這張藍藍的圖則存在於切線空間(Tangent Space)。
貼圖紋理(Texture)存在於模型空間(Model Space),法線紋理則儲存在切線空間。想要將這兩種紋理進行使用,有兩種方法,把貼圖紋理轉換到切線空間,或是將法線紋理轉換到模型空間。下面使用的是轉換到切線空間的方法。
// Blinn-Phong + Normal mapping
Shader "Learning/NormalMappingTangentSpace"
{
Properties
{
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Texture", 2D) = "white" {} // 貼圖紋理
_BumpMap ("Bump Map", 2D) = "bump" {} // 法線紋理
_BumpScale ("Bump Scale", Float) = 1.0 // 用於控制凹凸效果的程度
_Specular ("Specular Color", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8, 256)) = 20
}
SubShader
{
Tags { "LightMode" = "ForwardBase" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct appdata
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT; // 新屬性!切線座標
float4 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float3 lightDir : TEXCOORD1;
float3 viewDir : TEXCOORD2;
};
// 在貼圖紋理及法線紋理的下面有兩個`float4`
// 後面的ST代表位移(T)與縮放(S),xy是存放位移,zw是縮放
// 可以在Unity內的編輯器看到Tiling, Offset兩個屬性,分別為縮放, 位移
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _BumpScale;
fixed4 _Specular;
float _Gloss;
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
// 利用_ST來調整紋理座標
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
// 利用切線、副切線、法向量形成一個3x3的矩陣
// 將視角向量與光源向量轉換至切線空間
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldBitangent = cross(worldNormal, worldTangent) * v.tangent.w;
float3x3 worldToTangent = float3x3(worldTangent, worldBitangent, worldNormal);
o.lightDir = mul(worldToTangent, ObjSpaceLightDir(v.vertex)).xyz;
o.viewDir = mul(worldToTangent, ObjSpaceViewDir(v.vertex)).xyz;
// 或是可以利用內建的Macro,直接利用內建的rotation矩陣
// TANGENT_SPACE_ROTATION;
// o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
// o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed3 tangentLightDir = normalize(i.lightDir);
fixed3 tangentViewDir = normalize(i.viewDir);
// 請注意! 利用法線紋理時,請確認貼圖的type為"Normal map"
fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw);
fixed3 tangentNormal = UnpackNormal(packedNormal);
tangentNormal.xy *= _BumpScale;
// 利用xy計算出z軸的分量
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
// 以下為Blinn-Phong shading
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));
fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
}
在研究切線空間的時候,我看了老半天,還是無法完整的理解。或許是累了腦袋轉不過來,所以決定將「切線空間」的詳細部分,放到明天再說,先這樣。
另外,祝大家假期愉快^_^