知識0からのUnityShader勉強

知識0からのUnityShader勉強

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

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

前回の成果

視線によってuv値を変化させた。

soramamenatan.hatenablog.com


今回やること

前回の続きから学んでいきます。

coposuke.hateblo.jp


ソースコード

Shader "Unlit/OcclusionMap_2" {
    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;
                float4 objectPos : TEXCOORD2;
            };

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

            float4 frag(Vertex2Fragment i) : SV_TARGET {
                float2 uv = i.uv;
                float2 uvStart = i.uv;
                float3 rayDir = normalize(i.objectViewDir);
                float3 rayPos = i.objectPos;
                float3 rayPosStart = i.objectPos;
                float rayHeight = 0.0;
                float objHeight = -_HeightScale;
                const int HeightSamples = 32;
                const float HeightPerSample = 1.0 / HeightSamples;
                float rayScale = (-_HeightScale / rayDir.y);
                float3 rayStep = rayDir * rayScale * HeightPerSample;
                for (int i = 0; i < HeightSamples && objHeight < rayHeight; ++i) {
                    rayPos += rayStep;
                    uv = uvStart + rayPos.xz - rayPosStart.xz;
                    objHeight = tex2D(_HeightMap, uv).r;
                    objHeight = objHeight * _HeightScale - _HeightScale;
                    rayHeight = rayPos.y;
                }

                return tex2D(_MainTex, uv);
            }
            ENDCG
        }
    }
}

今回からハイトマップを使用していきます。


レイを進める

前回は_HeightScaleを擬似的にハイトマップの値(0~1)としていたのですが、今回はハイトマップの値を利用してレイを進めます。


rayStep

まずは1回でどれだけ進めるかを求めます。

// 前回のrayStep
float rayScale = (-_HeightScale / rayDir.y);
float3 rayStep = rayDir * rayScale;

// 今回のrayStep
// HeightPerSample ... レイを進める回数の逆数
float rayScale = (-_HeightScale / rayDir.y);
float3 rayStep = rayDir * rayScale * HeightPerSample;

今回のrayStepには必ず底面が存在するので、レイを進める回数の逆数を乗算します。
そうすることにより、各頂点での高低差が表現できます。

今回のrayStepのイメージ

f:id:soramamenatan:20200229145146j:plain

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


レイとハイトマップとの衝突処理

1回のstepで進める距離を求めたので、次に衝突を行います。
以下で衝突検知をして、uvを返しています。

for (int i = 0; i < HeightSamples && objHeight < rayHeight; ++i) {
    rayPos += rayStep;
    uv = uvStart + rayPos.xz - rayPosStart.xz;
    objHeight = tex2D(_HeightMap, uv).r;
    objHeight = objHeight * _HeightScale - _HeightScale;
    rayHeight = rayPos.y;
}
return tex2D(_MainTex, uv);


for (int i = 0; i < HeightSamples && objHeight < rayHeight; ++i)

先ほど、HeightSamplesの逆数でrayStepを求めました。
HeightSamplesの最大数まで進めると最底面となるのでfor分を抜けます。
また、objHeight < rayHeightになるとレイとハイトマップが衝突することになるのでfor分を抜けます。


uv = uvStart + rayPos.xz - rayPosStart.xz;

オブジェクトの実際のuv値にrayStepでズラした分のuvを加算することによりハイトマップを考慮したuv値を出しています。


objHeight = tex2D(_HeightMap, uv).r

ハイトマップをtex2Dすることによってハイトマップの値(0~1)を取得しています。
rgbの全ての値が同じなのでrを代入しています。


objHeight = objHeight * _HeightScale - _HeightScale

ハイトマップの値を0~1から-_HeightScale~0に変換しています。
この変換により、rayHeightとの衝突を検知できます。


衝突のイメージ

f:id:soramamenatan:20200229155003g:plain

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


結果

立体的になりました。

f:id:soramamenatan:20200229155346g:plain


ただし、斜めにした時にジャギってしまいます。

f:id:soramamenatan:20200229160144p:plain


ジャギの修正

以下のソースを追加します。

// 省略
for (int i = 0; i < HeightSamples && objHeight < rayHeight; ++i) {
    rayPos += rayStep;
    uv = uvStart + rayPos.xz - rayPosStart.xz;
    objHeight = tex2D(_HeightMap, uv).r;
    objHeight = objHeight * _HeightScale - _HeightScale;
    rayHeight = rayPos.y;
}

// 追加
float2 nextObjPoint = uv;
float2 prevObjPoint = uv - rayStep.xz;
float nextHeight = objHeight;
float prevHeight = tex2D(_HeightMap, prevObjPoint).r * _HeightScale - _HeightScale;
nextHeight -= rayHeight;
prevHeight -= rayHeight - rayStep.y;

float weight = nextHeight / (nextHeight - prevHeight);
uv = lerp(nextObjPoint, prevObjPoint, weight);
return tex2D(_MainTex, uv);

衝突検知をしたuv値とその前1つ前のstepでのuv値を補間します。

補間のイメージ

f:id:soramamenatan:20200301211426j:plain

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


結果

見た目はあまり変わっていません。

f:id:soramamenatan:20200301211705g:plain

ですが、斜めから見た時のジャギはだいぶ軽減されています。

右が補間したもの

f:id:soramamenatan:20200301212053p:plain


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

Shader "Unlit/OcclusionMap_2" {
    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;
                float4 objectPos : TEXCOORD2;
            };

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

            float4 frag(Vertex2Fragment i) : SV_TARGET {
                float2 uv = i.uv;
                float2 uvStart = i.uv;
                float3 rayDir = normalize(i.objectViewDir);
                float3 rayPos = i.objectPos;
                float3 rayPosStart = i.objectPos;
                float rayHeight = 0.0;
                float objHeight = -_HeightScale;
                const int HeightSamples = 32;
                const float HeightPerSample = 1.0 / HeightSamples;
                // 最底面の位置
                float rayScale = (-_HeightScale / rayDir.y);
                // 1回のstep
                float3 rayStep = rayDir * rayScale * HeightPerSample;
                // HeightSamples以上だと最底面
                // objHeight < rayHeightだと衝突
                for (int i = 0; i < HeightSamples && objHeight < rayHeight; ++i) {
                    // rayを進める
                    rayPos += rayStep;
                    // 進めた分のuv値のずれを修正
                    uv = uvStart + rayPos.xz - rayPosStart.xz;
                    // ハイトマップの高さ情報(0~1)
                    objHeight = tex2D(_HeightMap, uv).r;
                    // ハイトマップの高さ情報を-_HeightScale~0に変換
                    objHeight = objHeight * _HeightScale - _HeightScale;
                    rayHeight = rayPos.y;
                }
                // 衝突したstepと、その1つ前のstepで補間する
                float2 nextObjPoint = uv;
                float2 prevObjPoint = uv - rayStep.xz;
                float nextHeight = objHeight;
                float prevHeight = tex2D(_HeightMap, prevObjPoint).r * _HeightScale - _HeightScale;
                nextHeight -= rayHeight;
                prevHeight -= rayHeight - rayStep.y;
                float weight = nextHeight / (nextHeight - prevHeight);
                uv = lerp(nextObjPoint, prevObjPoint, weight);
                return tex2D(_MainTex, uv);
            }
            ENDCG
        }
    }
}

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