【Unity】視差マッピング

f:id:halya_11:20180214202812p:plain

ノーマルマッピングに引き続き、視差マッピングもUnityで実装してみます。

ノーマルマッピングはこちら light11.hatenadiary.com

視差マッピング

物体上に凸があるとき、斜めから見ると凸の向こう側は遮られて見えません。

f:id:halya_11:20180214203747p:plain

このような現象を、メッシュの変形を使わずに擬似的に実現するのが視差マッピングです。

ノーマルマッピングは法線のみを変えるため、光の当たり方はいい感じになるものの、凸に視線が遮られないため斜めから見ると不自然にのっぺりして見えます。

f:id:halya_11:20180214203853p:plain

そのため、視差マッピングはノーマルマッピングと併用することでより立体表現をリアルにするテクニックだと言えます。

具体的には、高くなっている部分のテクスチャのサンプリング位置を視点の方向にすこしずらします。

f:id:halya_11:20180214204803p:plain

シェーダ

シェーダはこんな感じです。

Shader "ParallaxMap" {
    Properties {
        _MainTex ("Base (RGB)", 2D) = "white" {}
        [Normal] _NormalMap ("Normal map", 2D) = "bump" {}
        _HeightMap ("HeightMap map", 2D) = "white" {}
        _Shininess ("Shininess", Range(0.0, 1.0)) = 0.078125
        _HeightFactor ("Height Factor", Range(0.0, 0.1)) = 0.02
    }
    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;
            sampler2D _HeightMap;
            half _Shininess;
            half _HeightFactor;

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

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

            v2f vert(appdata v) {
                v2f o;

                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv  = v.texcoord.xy;
                TANGENT_SPACE_ROTATION;
                o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex));
                o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex));
                
                return o;
            }

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

                // ハイトマップをサンプリングしてUVをずらす
                fixed4 height = tex2D(_HeightMap, i.uv);
                i.uv += i.viewDir.xy * height.r * _HeightFactor;

                fixed4 tex = tex2D(_MainTex, i.uv);
                fixed3 normal = UnpackNormal(tex2D(_NormalMap, i.uv));
                fixed4 diff = saturate(dot(normal, i.lightDir)) * _LightColor0;
                
                half3 spec = pow(max(0, dot(normal, halfDir)), _Shininess * 128.0) * _LightColor0.rgb;

                fixed4 color;
                color.rgb  = tex.rgb * diff + spec;
                return color;
            }

            ENDCG
        }

    }
    FallBack "Diffuse"
}

ハイトマップから高さを取得し、その高さに応じてメインテクスチャ及びノーマルマップのサンプリング位置を変えています。

もちろん空間を揃える必要があるため、今回は接空間のviewDirを使って計算を行っています。

また、使ったノーマルマップはこんな感じで、

f:id:halya_11:20180214205044p:plain:w400

ハイトマップはこんな感じです。

f:id:halya_11:20180214205107p:plain:w400

結果

f:id:halya_11:20180214205346p:plain

結果はこのように描画されます。
視差マッピングを追加した方が凹凸部分がリアルに見えることがわかります。

パフォーマンス

接空間でのノーマルマッピングと併用する場合は計算量はあまり多くならなそうです。

ただし、凹凸が小さい部分に適用しても効果はあまり実感できなかったりするので、モバイルプラットフォームでは効果が実感できる部分にのみ適用する感じでいいかも。

関連

ノーマルマッピング

light11.hatenadiary.com