iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 16
0
自我挑戰組

初見Unity Shader系列 第 16

那張藍藍的圖,法線貼圖(下)

文章內使用Unity 2019 LTS

本篇延續上一篇

目標

  • 實作一個法線貼圖效果

一些轉換

昨天Day15的時候提到,現在我們的向量是儲存在一張貼圖裡面,還知道法向向量是怎麼轉換成顏色倒貼圖上的。

接下來,如果要使用這張圖的話,我們還需要做一些轉換。先前的範例中,在處裡頂點的時候,有利用線性轉換經過需許多「空間」,在Unity解釋甚麼是空間的話,就是transform.localPositiontransform.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
        }
    }
}

挖個坑?!

在研究切線空間的時候,我看了老半天,還是無法完整的理解。或許是累了腦袋轉不過來,所以決定將「切線空間」的詳細部分,放到明天再說,先這樣。

另外,祝大家假期愉快^_^

Reference


上一篇
那張藍藍的圖,法線貼圖(上)
下一篇
切線空間
系列文
初見Unity Shader30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言