よっしーとの合わせワザで一歩先を目指せ!~Parallax-Corrected Cubemap~
はじめに
はい皆さんこんにちは。ゲーム創ってますか?
今回は、よっしーが紹介してくれた技術と併用する事により、表現力が今よりグッと高まる技術、
Parallax-Corrected Cubemapと呼ばれる手法を紹介します!
尚、詳細な説明や、数学過ぎる話は省き、せめて理屈だけでも覚えてくれるような内容になるように努力します!
Cubemap?キューブマップって...はて、どこかで...?
さて、今回の題材はキューブマップを扱うワケですが、ちゃんと理解出来てますか?
出来てない or 怪しい...ならまずはよっしーの記事DirectX11でリアルタイムキューブマップテクスチャ作成(Mipmap付き)&DDSファイル保存を見てくるんだ今直ぐに!!!
ちゃんと読んだ or 理解出来たなら次に進みましょう。
この方法は、常に立方体の中心にいるとみなしているので、フィールド上のどこにいてもヘルメットに映り込む風景は変わりません。では例えば、何かしらの建物に入った場合はどうでしょう?
「常に立方体の中心にいるとみなしている」と書きましたよね?
単純なCubemapだと、映り込む設定がそのままなので、キャラクターのヘルメットにはフィールド上の風景が映り込んだままになっているのです!
だだっ広いフィールド上にて単体の風景を写り込ませるだけ[1]だとそれほどの違和感は持ちませんが、フィールド上に存在する建物等に入れる作りのゲームの場合、入った途端にかなり強い違和感を持つ事でしょう。
だって建物の中にいるのに、ヘルメットに青空が写ってたらおかしいでしょ?
では、「Cubemap」をフィールド用と建物用の2バージョン用意すれば良くね?って事になると思いますが、それだと以下のような問題が発生してしまいます。
単純なCubemapを利用した場合の問題点
下の画像は室内風景を撮影したキューブマップを用意し[2]、適応/表示させたものです。
一件問題無さそうに見えるが...キャラの位置やカメラを回転させると...
なんか反射位置おかしくない...?
何かがおかしい・・・映り込む内容は合っているものの、キャラクターの位置やカメラの角度によっては映り込みが不自然に見えてしまってますね。
簡単ではありますが、何故こうなるのかを図で説明します。
本来、反射先で衝突した点Pのテクセルが欲しいですよね?
しかし、CubeMapは上記の通り、常に立方体の中心にいるとみなしているので、
キャラクターがどこに居ようが反射ベクトルが同じ位置を指す事でこのような事が起こるのです。
Parallax-Corrected Cubemapとは
そこで!これらの問題点を解消する(完全ではないですが)方法が今回ご紹介する「Parallax-Corrected Cubemap」です!
キャラクターやカメラの位置によって生じる「視差を正しく計算し、キューブマップをサンプリングする手法」となります。
いざ実装!その1
それでは実際に実装してみましょう!
実現するにあたって必要なデータは、上記よっしーの記事を参考にして実装した、
リアルタイムに生成した室内のキューブマップ画像のみです。
// IBLテクスチャ(リアルタイムに生成した室内のキューブマップ画像だよ!)
TextureCube g_IBLTex : register(t100);
// Parallax-Corrected Cubemap
float3 calcParallaxCorrectedCubemapReflect(float3 worldPos, float3 worldReflect,
float3 cubemapPos, float3 boxMin, float3 boxMax)
{
// AABB と 反射ベクトル の交差点 worldIntersectPos を計算します
float3 firstPlaneIntersect = (boxMax - worldPos) / worldReflect;
float3 secondPlaneIntersect = (boxMin - worldPos) / worldReflect;
float3 furthestPlane = max(firstPlaneIntersect, secondPlaneIntersect);
float dist = min(min(furthestPlane.x, furthestPlane.y),
furthestPlane.z);
float3 worldIntersectPos = worldPos + worldReflect * dist;
// Cubemapから交差点に向かうベクトルが ParallaxCorrected された反射ベクトルになります
return worldIntersectPos - cubemapPos;
}
上記コードはHLSLによるParallax-Corrected Cubemapの反射ベクトル...えぇい小難しい話は抜きにして、ベクトルとAABBの交差判定のアルゴリズム[3]と覚えて頂ければOK!
このコードの働きのイメージをParallax Corrected CubeMaps from Siggraph 2012にあるスライドの一部画像を拝借して見てみましょう。
究極にざっくり説明すると、上のコードは図にあるポイントCから右下に伸びるベクトル(赤線)を求めているだけなのです。
この求めたベクトルを利用してキューブマップ画像のテクセルを取得し、あまり違和感のない反射に見える様にしましょうね、と言う事です。
いざ実装!その2
では上記で用意したアルゴリズムを利用してピクセルの色を決めて行きましょう。
//================================
// ピクセルシェーダ
//================================
float4 main(VSOutput In) : SV_Target0
{
// 最終的な色
float3 outColor = 0;
...基本的なライティング計算等々を省略...
//------------------
// IBL(Parallax-Corrected Cubemap)
//------------------
{
// 拡散光
float3 envDiff = g_IBLTex.SampleLevel(g_ss, wN, 8).rgb;
outColor += envDiff * baseDiffuse.rgb / 3.141592;
// 反射光
float3 CubeCenterPos = float3(0, 2, 0);
// ↓要調整個所!↓
float3 MinBox = float3(-4.5f, 0, -15.0f);
float3 MaxBox = float3(3.3f, 100.0f, 13.0f);
// ↑要調整個所!↑
// 反射ベクトルを求める
float3 vRef = reflect(-vCam, wN);
vRef = calcParallaxCorrectedCubemapReflect(In.wPos, vRef, CubeCenterPos,
MinBox, MaxBox);
// 粗いほど低解像度の画像を使用する
float3 envSpec = g_IBLTex.SampleLevel(g_ss, vRef, roughness * 8).rgb;
outColor += envSpec * baseSpecular;
}
//------------------------------------------
// 出力
//------------------------------------------
return float4(outColor, 1.0f);
}
「// ↓要調整個所!」 と書いてあるコメント部分は、部屋をぴったり覆うようなAABBの設定部分です。
本来は定数バッファ(Constant Buffer)を用いてゲーム側から引っ張ってくるようなデータなので誤解しないように!!!
必要なコードは概ねコレだけ!準備出来たら実行してみましょう!
かなりそれっぽく反射してますね!
カメラをグリグリ動かしたり、キャラクターをゴリゴリ動かしたりして色々な角度から見ても床への反射が自然に見えていれば成功です!
おわりに
如何でしたか?
アルゴリズムや計算部分が非常にややこしく、どうにもとっつきにくいのがシェーダーですが、
それを乗り越えた先には素晴らしい世界が広がっています。
綺麗に見せる為の技術はまだまだありますので是非色々な技術にチャレンジし、もう一歩、二歩先を行く作品に仕上げましょう!
Discussion