知識0からのUnityShader勉強

知識0からのUnityShader勉強

UnityのShaderをメインとして、0から学んでいくブログです。

【UnityShader】視差オクルージョンマッピング【1】 #41

前回の成果

法線マップを理解した。

soramamenatan.hatenablog.com


今回やること

視差オクルージョンマッピングを勉強します。

coposuke.hateblo.jp


事前準備

Scene上にQuadを配置します。

f:id:soramamenatan:20200222100715p:plain

以下は今回の制作で使用するテクスチャです。

Main Texture

f:id:soramamenatan:20200222101232j:plain

Height Map

f:id:soramamenatan:20200222101241p:plain

視差オクルージョンマッピング(parallax occlution mapping) - コポうぇぶろぐ:より引用


ソースコード

Shader "Unlit/OcclusionMap_1" {
    Properties {
        _MainTex ("Main Texture", 2D) = "white" {}
        _HeightMap("Height Map", 2D) = "white" {}
        _HeightScale("Height", Float) = 0.5
    }

    SubShader {
        Tags {
            "RenderType"="Opaque"
        }

        LOD 200

        Pass {
            CGPROGRAM
            #include "UnityCG.cginc"
            #pragma vertex vert
            #pragma fragment frag

            sampler2D _MainTex;
            sampler2D _HeightMap;
            float _HeightScale;

            struct Vertex {
                float4 position : POSITION;
                float3 normal : NORMAL;
                float2 uv : TEXCOORD0;
            };

            struct Vertex2Fragment {
                float4 position : SV_POSITION;
                float3 normal : NORMAL;
                float2 uv : TEXCOORD0;
                float3 objectViewDir : TEXCOORD1;
                float3 objectPos : TEXCOORD2;
            };

            Vertex2Fragment vert(Vertex i) {
                Vertex2Fragment o;
                o.position = mul(unity_ObjectToWorld, i.position);
                o.normal = i.normal;
                o.uv = i.uv;
                o.objectViewDir = o.position - _WorldSpaceCameraPos.xyz;
                o.objectPos = o.position;
                o.position = mul(UNITY_MATRIX_VP, o.position);
                return o;
            }

            float4 frag(Vertex2Fragment i) : SV_TARGET {
                float3 rayDir = normalize(i.objectViewDir);
                float2 uv = i.uv;
                float rayScale = (-_HeightScale / rayDir.y);
                float3 rayStep = rayDir * rayScale;
                uv += rayStep.xz;
                return tex2D(_MainTex, uv);
            }
            ENDCG
        }
    }
}

一気にやると情報が多いので小分けにしていきます。


視差マッピング

視線や高さを考慮したバンプマッピングのことです。
前回行った、法線マップと比較してみます。
左が視差マッピングで右が法線マップです。
左の方がorgの文字に立体感が出ています。

f:id:soramamenatan:20200222105123j:plain

wgld.org | WebGL: 視差マッピング |:より引用

視差マッピングと法線マップのイメージは以下です。
f:id:soramamenatan:20200222105119j:plain

wgld.org | WebGL: 視差マッピング |:より引用


視差オクルージョンマッピング

視線の向きにあった遮蔽物の前後関係を計算して色を決める手法です。


ハイトマップ

ハイトマップは高さの情報のみを持っているテクスチャです。
黒の箇所は元ポリゴンの高さで、白くなる程高くなっていきます。

f:id:soramamenatan:20200222110236p:plain

その3 波:ハイトマップから法線マップを作る方法:より引用


視線に応じてuv値を変更させる

まずは視点の位置によって、uv値を変化させます。

最底面との衝突

ハイトマップが黒であれば、視線の先(オレンジ色の矢印)の最底面に衝突することになり、その位置のテクスチャの色を取得します。
今回はハイトマップを使用せずに最低麺位衝突させてuvを変化させます。

f:id:soramamenatan:20200222112218j:plain

視差オクルージョンマッピング(parallax occlution mapping) - コポうぇぶろぐ:より引用


以下のソースで最底面を求めています。
_HeightScaleが最底面の深さとなります。

float rayScale = (-_HeightScale / rayDir.y);
float3 rayStep = rayDir * rayScale;
uv += rayStep.xz;
rayScale

何回rayDir.yを乗算すると最底面と衝突するかを求めています。

rayStep

正規化したrayDirとrayScaleを乗算することにより、Rayのベクトルを出します。
それをuvに加算することにより最底面におけるテクスチャの色を求めることができます。
今回の場合、uvはxz平面なのでxzを加算しています。

以下の画像は最底面を求める計算のイメージです。

f:id:soramamenatan:20200222114720j:plain

視差オクルージョンマッピング(parallax occlution mapping) - コポうぇぶろぐ:より引用


結果

以下のgifのようになれば成功です。
視線によって見え方が異なっているのがわかります。

f:id:soramamenatan:20200222120318g:plain


ソースコードにコメントを付与

Shader "Unlit/OcclusionMap_1" {
    Properties {
        _MainTex ("Main Texture", 2D) = "white" {}
        _HeightMap("Height Map", 2D) = "white" {}
        _HeightScale("Height", Float) = 0.5
    }

    SubShader {
        Tags {
            "RenderType"="Opaque"
        }

        LOD 200

        Pass {
            CGPROGRAM
            #include "UnityCG.cginc"
            #pragma vertex vert
            #pragma fragment frag

            sampler2D _MainTex;
            sampler2D _HeightMap;
            float _HeightScale;

            struct Vertex {
                float4 position : POSITION;
                float3 normal : NORMAL;
                float2 uv : TEXCOORD0;
            };

            struct Vertex2Fragment {
                float4 position : SV_POSITION;
                float3 normal : NORMAL;
                float2 uv : TEXCOORD0;
                float3 objectViewDir : TEXCOORD1;
                float3 objectPos : TEXCOORD2;
            };

            Vertex2Fragment vert(Vertex i) {
                Vertex2Fragment o;
                o.position = mul(unity_ObjectToWorld, i.position);
                o.normal = i.normal;
                o.uv = i.uv;
                // 視線ベクトル
                o.objectViewDir = o.position - _WorldSpaceCameraPos.xyz;
                o.objectPos = o.position;
                o.position = mul(UNITY_MATRIX_VP, o.position);
                return o;
            }

            float4 frag(Vertex2Fragment i) : SV_TARGET {
                float3 rayDir = normalize(i.objectViewDir);
                float2 uv = i.uv;
                // 何回乗算すれば最底面(_HeightScale)と衝突するか
                float rayScale = (-_HeightScale / rayDir.y);
                // 視線ベクトルと乗算してベクトルの大きさを求める
                float3 rayStep = rayDir * rayScale;
                // xz平面
                uv += rayStep.xz;
                return tex2D(_MainTex, uv);
            }
            ENDCG
        }
    }
}

今回は以上となります。
ここまでご視聴ありがとうございました。