この記事は,WebGL Advent Calendar 2016 20日目の記事です。
doxasさんやgam0022さんのレイマーチングの作品を見て面白そうだったので挑戦してみようと思います。
#目的
ところでこの記事を見ている方はエースコンバットというゲームをご存知でしょうか。
超遠距離のオブジェクトや、雲や煙のような形が定まっていない物ばかりが描画されるゲームです。
プログラミングを学び始めた頃は、こういったものがどうやったら表示出来るのか全くわかりませんでしたが、今なら少し再現できそうです。
そこで、今回はその第一歩としてレイマーチングの力を借りて、リアルな雲を描画してみたいと思います。
#方法
「よし、レイマーチングでボリュームレンダリングをしよう!」と思っても、よく考えたらなにをしたら良いのかわかりません。
というかボリュームレンダリングって何?
https://ja.wikipedia.org/wiki/ボリュームレンダリング
コンピュータグラフィックスにおいて、ボリュームレンダリングとは、3次元的な広がりのあるデータを直接2次元画面に表示することである。半透明な物体や発光体などを光学的に正しくレンダリングしたり(レイキャスティング)、CTなどで3次元的に撮影された画像を目的に沿って見やすく提示したりする(最大値投影処理など。)
説明が全然わからない。
レイキャスティングによるボリュームレンダリングをする方法の模式図。
なるほど。
とりあえず一定距離ずつレイを飛ばしてその地点の色の情報を取得して合成していく雰囲気だと思います。
#作成
考え方は想像出来たのでSharder Toy上で作ってみました。
https://www.shadertoy.com/view/ll3SWl
vec4 sum = vec4(0, 0, 0, 0);
for (float depth = 0.0; depth < 100000.0; depth += 100.0)
{
vec3 ray = campos + fragAt * depth;
if (cloudrange.x < ray.y && ray.y < cloudrange.y)
{
float alpha = smoothstep(0.5, 1.0, fbm(ray * 0.00025));
vec3 cloudColor = mix(vec3(1.1, 1.05, 1.0), vec3(0.3, 0.3, 0.2), alpha);
alpha = (1.0 - sum.a) * alpha;
sum += vec4(cloudColor * alpha, alpha);
}
}
雲を描画している部分がここです。
簡単に解説すると
- カメラの位置からfragAtの方向に100.0ずつベクトルを伸ばし、伸ばした座標に依存するfbmを取得
- 取得した値を元に透明度と雲の色(濃さ)を計算
- 雲の色を「余っている透明度」に対してその雲の透明度分、合成
「余っている透明度」の部分がすこしわかりづらいですが
例えば1ループ目で透明度50%の雲が発生したら
alpha = (1.0 - 0.0) * 0.5
なのでalphaは0.5になります。
2ループ目でもう一度透明度50%の雲が発生したら
alpha = (1.0 - 0.5) * 0.5
なので0.75になります。
このように画面奥の雲の色は、それより手前でついた雲の色より影響されにくくなります。
#参考
fbmというのはフラクタルブラウン運動というパーリンノイズの仲間みたいなものです。
http://qiita.com/y_li/items/290754b9c3ba18e9fb2b
潜れる雲を作るにあたって非常に参考になった作品です。
この作品自体はカメラを雲の中に移動させると破綻してしまうんですが、wikipediaの参考画像と合わせてボリュームレンダリングについて理解出来るようになりました。
https://www.shadertoy.com/view/4sXGRM
ところでボリュームレンダリングでこれあってるんでしょうか。
「君のやってるそれ全然違うものだよ」とかありましたらご指摘ください。