高解像度シャドウマップ
今回のサンプルの実行結果
高解像度な影
前回はシャドウマッピングを行ないました。
シャドウマップと呼ばれる深度値を格納した特殊なテクスチャを用意しておくことで、レンダリングの際に深度値を比較し、影を描画することが可能になるのでしたね。
今回紹介するのは、前回行なったシャドウマッピングの高解像度バージョンです。
前回と比べると変更点自体は本当に少ないのですが、レンダリングされる影の精度はだいぶよくなります。
仔細な変更だけで簡単に実装できますので、参考程度に覚えておくといいかもしれません。
影のジャギー
前回のシャドウマッピングを行なうサンプルを実行し、レンダリングされる影をじっくり見てみると、影と、影でない部分との境界が、非常に粗いギザギザの状態になっていることに気がつきます。
前回のシャドウマッピングの拡大図
レンダリングされるトーラスが滑らかな曲線でレンダリングされているのに、影のほうはギザギザのジャギーが発生した状態になってしまっています。どうしてこのようなことが起こるのでしょうか。
シャドウマッピングの仕組みを思い返してみましょう。シャドウマッピングは深度値を格納したシャドウマップと呼ばれるテクスチャを利用して影を描くべきかどうかを判断する技術です。このとき仮に、シャドウマップテクスチャが非常に解像度の低い状態だったと仮定すると、当然ですが影の精度が下がってしまいますね。
試しに、シャドウマップをレンダリングするフレームバッファのサイズを、前回のサンプルの四分の一の解像度、つまり 128 px 幅にしてみた結果が以下の画像です。
低解像度シャドウマップ
明らかに影の精度が落ちているのがわかります。
そしてここから逆説的に考えてみると、シャドウマップを高解像度にすることができれば、影の精度が上がっていくことがわかりますね。今回の肝はまさにここです。
フレームバッファのサイズ
それではシャドウマップを高解像度にするためにはどうしたらいいか考えてみましょう。
シャドウマップは先述のとおり、フレームバッファに対してレンダリングされた深度値を格納したテクスチャです。ですから、フレームバッファのサイズを大きくできれば、必然的にシャドウマップの解像度は上がります。
サンプルの javascript コードの中では、フレームバッファの初期化を行なっていたはずです。その部分のコードを抜粋します。
フレームバッファの初期化コード抜粋
// フレームバッファオブジェクトの取得
var fBufferWidth = 2048;
var fBufferHeight = 2048;
var fBuffer = create_framebuffer(fBufferWidth, fBufferHeight);
前回のサンプルでは、ここでは 512 という数値が指定されていました。すなわち、既定の canvas と同じサイズですね。今回は canvas よりも大きなサイズのフレームバッファを用意しますので、2048 を指定しています。これで、前回のサンプルと比べて 4 倍のサイズを持つフレームバッファが生成されます。
初期化の段階で注意すべきことは、あくまでもフレームバッファは最終的にテクスチャとして再利用されるという点です。フレームバッファのサイズが 2 の累乗サイズになるように指定しなければなりませんので注意しましょう。
レンダリングする際の注意
さて、フレームバッファを既定の canvas と異なるサイズで生成した場合には、レンダリングを行う場面でも注意すべき点があります。
というのも、従来のサンプルでは一度も触れたことはありませんでしたが、WebGL にはビューポート(view port)という概念があります。名前からも想像がつくかと思いますが、すごく簡単に言ってしまうとビューポートとはレンダリングされるスクリーンの大きさと考えても差し支えないでしょう。
通常、WebGL コンテキストが初期化された時点で、暗黙のうちに canvas と同サイズのビューポートが内部的に設定されます。ですから、今までは意図的にビューポートを設定することをしなくても、canvas 上に正しくレンダリング結果が表示されていました。しかし、今回のようなケースでは、既定の canvas と同じサイズのビューポートのまま大きなサイズのフレームバッファにレンダリングしてしまうと、せっかく大きな描画領域を持つフレームバッファの、一部の領域にしかレンダリングが行われません。これでは困ってしまいますね。
つまり、異なる大きさの複数のバッファに対してレンダリングを行うような処理では、ビューポートを正しく設定してからレンダリングを行ってやる必要があるわけですね。
ビューポートの設定は、以下のように非常に簡単かつ、直観的です。
ビューポートの設定
// ビューポートをフレームバッファのサイズに調整する
gl.viewport(0.0, 0.0, fBufferWidth, fBufferHeight);
見た目でなんとなくわかると思いますが、ビューポートを設定するには viewport
メソッドを利用します。第一引数、第二引数はビューポートの原点となる座標、そして第三引数がその幅、第四引数がその高さとなります。
ここで注意しなければならないのは、第一引数と第二引数に指定する原点の部分。ここは OpenGL 系ではよくある左下隅が基準になります。非常に紛らわしいので間違えないように注意しましょう。
今回のケースのように、canvas やフレームバッファの全体に描画を行う場合には原点に関しては何も考えずに 0 を指定し、幅だけ間違えないようにすれば大丈夫ですね。
大きなサイズのフレームバッファへのレンダリングが終了したら、本番の canvas に対して描画を行う前に、ビューポートを元の領域に設定しなおすのも忘れずに行うようにしましょう。これをしないと今度は 512 px しかない canvas 上に正しくレンダリングが行われなくなってしまいます。
まとめ
今回は非常に少ない修正だけで済んでしまったので少々拍子抜けしたかもしれません。
ただし、高解像度シャドウマップを使った場合は影の精度が非常に良くなりますので、見た目に及ぼす変化は大きいと言えるでしょう。
参考までに、以下にいくつかのサイズでシャドウマップを生成した結果を載せておきます。
フレームバッファサイズ 64 px
フレームバッファサイズ 256 px
フレームバッファサイズ 1024 px
フレームバッファサイズ 2048 px
シャドウマップが高解像度になればなるほど、当然ですがプログラムの負荷は大きくなります。どの程度の精度を持った影がほしいのかによって、バランスを見て判断するようにしたほうがいいでしょう。
今回のサンプルでは、フレームバッファを 2048 px で生成した場合の高解像度シャドウマップを使っています。前回のサンプルと比較してみると、だいぶ影が綺麗に出ているのがわかると思います。