【Unity】ノーマルマッピング

f:id:halya_11:20180214122557p:plain

ノーマルマッピングをUnityで実装してみます。 他のサイトでもよく解説されている内容なので、説明は最低限に留めます。

ノーマルマッピング

ノーマルマッピングとは、法線の向きを書き込んだテクスチャをモデル上にマッピングすることで、法線を細かく調整するための手法です。

この法線を使ってライティングをすることで、メッシュの形状では表現しづらい細かい凹凸などが作れます。

シェーダ

Shader "NormalMapping" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" {}
        [Normal] _NormalMap ("Normal map", 2D) = "bump" {}
        _Shininess ("Shininess", Range(0.0, 1.0)) = 0.078125
    }
    SubShader {

        Tags { "Queue"="Geometry" "RenderType"="Opaque"}

        Pass {
            Tags { "LightMode"="ForwardBase" }

            CGPROGRAM
           #include "UnityCG.cginc"

           #pragma vertex vert
           #pragma fragment frag

            float4 _LightColor0;
            sampler2D _MainTex;
            sampler2D _NormalMap;
            half _Shininess;

            struct appdata {
                float4 vertex : POSITION;
                float2 texcoord : TEXCOORD0;
                                 float3 normal : NORMAL;
                float4 tangent : TANGENT;
            };

            struct v2f {
                float4 pos : SV_POSITION;
                float2 uv : TEXCOORD0;
                half3 lightDir : TEXCOORD1;
                half3 viewDir : TEXCOORD2;
            };

            v2f vert(appdata v) {
                v2f o;

                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv  = v.texcoord.xy;

                // UnityCG.cginc に定義されているマクロ
                TANGENT_SPACE_ROTATION;
                o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex));
                o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex));
                
                return o;
            }

            fixed4 frag(v2f i) : COLOR {
                i.lightDir = normalize(i.lightDir);
                i.viewDir = normalize(i.viewDir);
                half3 halfDir = normalize(i.lightDir + i.viewDir);

                fixed4 tex = tex2D(_MainTex, i.uv);
                // ノーマルマップから法線を取得
                half3 normal = UnpackNormal(tex2D(_NormalMap, i.uv));
                // ノーマルマップの法線が確実に正規化されているならなくてもいい
                normal = normalize(normal);

                half3 diffuse = max(0, dot(normal, i.lightDir)) * _LightColor0.rgb;
                half3 specular = pow(max(0, dot(normal, halfDir)), _Shininess * 128.0) * _LightColor0.rgb;

                fixed4 color;
                color.rgb  = tex.rgb * diffuse + specular;
                return color;
            }

            ENDCG
        }

    }
    FallBack "Diffuse"
}

普通のノーマルマッピングです。

一点普通じゃないのは、フラグメントシェーダでノーマルマップから取得した法線を正規化している点です。
これはノーマルマップに描かれている法線が正規化されていない可能性に備えた処理です。

DXT5nm環境のみ、変換の過程でこれを書かなくても正規化されるため、これを書かない場合はプラットフォームに依存して見え方が変わってきてしまいます。
Windowsで開発していて、iOSにビルドしたときに反射が全然うまくいかないなんてことも・・・
そもそも、ちゃんと正規化されてるべきなんですけどね。

あといつも忘れるんですが、 ObjSpaceLightDirとか ObjSpaceViewDirは正規化されていません。

docs.unity3d.com

今回はどっちにしろfragmentシェーダで正規化するので頂点シェーダでは正規化する必要がなかったのですが、一応メモ。

結果

f:id:halya_11:20180214122557p:plain

こんな感じになりました。

ちなみにノーマルマップはこのようなものを使ってます。

f:id:halya_11:20180214123707p:plain

関連

視差マッピング light11.hatenadiary.com