概要
今回はレイマーチングでHeight Mapを使って凹凸のある平面を描いてみます。
実際にレンダリングした結果は以下みたいな感じになります。
アニメーションさせたやつ↓
Shadertoyで実際に動くデモもあります。
見てもらうと分かりますが、とても「平面」には見えませんねw
が、距離関数自体は平面のものを使い、距離に多少細工をしてこの描画を行っています。
今回の実装については「こんな感じかなー」という想像で行いました。
ただShadertoyでは以下のようにコメントもらったので、基本的なアプローチは合っていると思います。
Most heightmap distance field formula will look like this yeah.
実装解説
距離関数
今回の実装の距離関数をまずコードで示します。
// テクセル(高さ)の倍率
const float heightFactor = 3.0;
// 平面との距離関数
float distPlane(in vec3 p, vec4 n)
{
return dot(p, n.xyz) * n.w;
}
// Height Mapを使った距離関数
float distFunc(in vec3 p)
{
float d = distPlane(p, plane);
vec4 tex = texture(iChannel0, mod(p.xz * 0.2, 1.0));
tex *= heightFactor;
return d - tex.x;
}
distFunc
が距離関数です。
内部では平面との距離関数であるdistPlane
を呼び出し結果を少し細工しています。
細工は、
vec4 tex = texture(iChannel0, mod(p.xz * 0.2, 1.0));
return d - tex.x;
の部分ですね。(heightFactor
は単にテクセルの値を増減させる目的で用意してます)
ちなみにこのtexture
関数ですが、これはShadertoy上で定義されている関数です。
第一引数に渡しているiChannel0
は、これもShadertoy上で用意されているテクスチャへの参照です。
今回の例では普通のテクスチャサンプリングと考えてもらって大丈夫です。
XZ平面上のテクスチャをサンプリング
さてここでやっていることですが、p.xz
というパラメータを使っています。
これは平面に対しての距離関数を求めたあと、その時点でのレイの位置をUV座標として利用し、平面のテクスチャを(概念的に)サンプリングします。
そしてサンプリングしたテクセルの値(つまり高さ)を、求めておいた平面との距離から減算することで平面との距離を操作している、というわけです。
と、言葉にすると分かりづらいので図にすると以下のような感じになります。
元々は平らな平面から、HeightMapを使うことで擬似的に平面に凹凸を生み出している、というわけですね。
距離についての計算ができてしまえば、あとは通常のレイマーチングの仕組みに則って法線も求められるし、ライティングやシェーディングを行うことができる、というわけです。
今回はHeightMapの実装を確かめることが目的だったのでShadertoyに用意されているテクスチャを使いましたが、実際には「フラクタルブラウン運動(Fractal Brownian Motion(FBM))」と呼ばれるパーリンノイズに似たノイズ関数を利用してランタイムで地面を生成してレンダリングしているのが一般的なようです。(Shadertoyの中で)
ちなみに、フラクタルブラウン運動についてはこちらの記事が実装込みで解説してくれています。(ただ、画像のリンクが切れてしまっていますが・・・)
以上がHeightMapを使った地形のレンダリングです。
それ以外のレイマーチングの実装は一般的な実装だと思うので解説は割愛します。
レイマーチング自体の仕組みなどについては以前、記事を書いたのでそちらを参照ください。
(vol.1とか言いながらvol.2書いてない・・・;)