【UnityShader】視差オクルージョンマッピング【2】 #42
前回の成果
視線によってuv値を変化させた。
今回やること
前回の続きから学んでいきます。
ソースコード
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のイメージ
レイとハイトマップとの衝突処理
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との衝突を検知できます。
衝突のイメージ
結果
立体的になりました。
ただし、斜めにした時にジャギってしまいます。
ジャギの修正
以下のソースを追加します。
// 省略 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値を補間します。
補間のイメージ
結果
見た目はあまり変わっていません。
ですが、斜めから見た時のジャギはだいぶ軽減されています。
右が補間したもの
ソースコードにコメントを付与
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 } } }
今回は以上となります。
ここまでご視聴ありがとうございました。