浮動小数点数テクスチャ

実行結果

今回のサンプルの実行結果

テクスチャの限界を超えて

前回は、深度値の扱いと座標系に関する予備知識について解説しました。

基本的に数学的な話がメインだったので、非常に難解な内容になってしまったように思います。数年前の、まだ 3D プログラミングを勉強し始めたばかりの自分が見たら、うわあ……と思わず言いたくなるだろうなと、自分でテキストを書いていても思いました。

3D プログラミングでは、前回のテキストのような数学的な知識の有無によって、製作できるプログラムの完成度のみならず、そのプログラムの柔軟性や効率、ひいては実装できる処理の幅まで劇的に変化します。自分もけして得意な分野ではないのですが、これからも勉強しつつ理解できたことはテキストにし、少しでも WebGL を扱うプログラマの役に立てればなと思います。

さて、今回ですが、前回のような文章ベースの難解なものではなく、もっと実用的なテクニックとして浮動小数点数テクスチャ(float texture)を扱ってみたいと思います。

前回のテキストで紹介したサンプルのような、シャドウマッピングに代表される高い精度を求められる処理では、通常のテクスチャでは期待した結果を得られないケースが考えられます。通常のテクスチャフォーマットでは RGBA の各要素に格納できる数値は 0 ~ 255 諧調、つまり実質 8 bit 精度です。もちろん、現在一般に出回っているディスプレイで表現できる色の範囲としては、この精度は十分なものです。しかし、そこに色以外の情報を入れるとなると、やはり 8 bit はけして十分に実用的とは言いがたいというのが現実です。

事実、以前解説した、一番最初のシャドウマッピングのサンプルでは、テクスチャになんとか精度の高い値を格納しようとして RGBA の各要素に分割して深度値を描き込む……なんていう荒業も試してみました。結果的には、この方法はフラグメント間の補間が行われることが原因で、精度の向上には役立っていないことが後に判明します。しかしこのような悪足掻きをしてしまうのも、ひとえに 8 bit という壁を越えたいが故にです。

今回ご紹介する浮動小数点数テクスチャは、この 8 bit の壁を越えるための確実な手段となりうるものです。ぜひ頑張って理解していただければと思います。

precision 宣言

さて浮動小数点数テクスチャの話をする前に、少しだけ寄り道しましょう。

掲題のとおり precision 宣言についてです。

この precision というキーワード、聞きなれないと感じる方も少なくないかもしれません。しかし、このキーワードは毎回のように当サイトのテキストに実は登場しています。

シェーダを記述する GLSL には、数値演算の精度を表すためにこの precision キーワードが使われています。たとえば、フラグメントシェーダには必ず冒頭にこんな一文が毎回ついていますよね。

precision mediump float;

この precision 宣言には、三種類の精度修飾子が利用できます。この修飾子の指定に応じて、シェーダ内で扱われる数値の精度が変化します。正確には、修飾子に続けて指定される型に対して適用される感じですね。上記の場合は float を対象とした宣言の例です。

精度修飾子

highp

midiump

lowp

上記の三種類の修飾子は、ほとんど読んで字の如くそのままの意味です。最も高い精度を誇るのが highp ですね。当サイトでは midiump を用いてフラグメントシェーダを記述しています。※ちなみに頂点シェーダは無条件に highp 精度が使われます

しかし、せっかく highp という高精度指定ができるのに、どうして midiump を使っているの? 精度が高いほうが何かといいのでは――と考えるのが普通だと思います。なにゆえ当サイトのサンプルでは midiump を使っているのか。それには、ちゃんと理由があるのです。

実は GLSL が動作する場所、つまり GPU が大きく関係しています。

この精度修飾子、ハードの実装によって精度がマチマチになってしまうという問題があります。仕様上では highp が 24 bit ~ 32 bit 精度を持つことになっています。同様に midiump は 16 bit で lowp は 8 bit ということになっているんですが、必ずしもこの結果通りにはならないケースが少なからずあるようです。※実際にいろんな環境で自分でテストしてみたわけではないです

また、PC やタブレット、スマートフォンなどあらゆるデバイス上で動作する可能性がある WebGL の場合、この精度の問題以前にハードがその修飾子指定に対応できるのかが問題になります。

現状、このテキスト執筆時(2014 年 3 月)の時点では highp を指定してまともに動くのは高スペックなグラフィックカードを搭載している PC に限定されるのが現実でしょう。自分の触れる範囲でいろいろ試してみましたが、まずオンボード GPU 以下のスペックでは highp は期待どおりの動作をしないと思います。※この辺は時を経るごとに徐々に改善されてくると思います

そういった事情から、比較的広範囲な環境で動作することを念頭に置いて、当サイトのサンプルはすべて midiump 修飾子を指定してあるわけです。

float texture での精度

さて、前述のとおりフラグメントシェーダでは多くの場合 midiump 精度で数値を扱うのが基本になります。となると、最大でも 16 bit 精度までが実用範囲ということになりますね。恐らく、浮動小数点数テクスチャを利用する場合の最大精度は、単精度浮動小数点数、すなわち 32 bit になりますがしばらくは 16 bit で我慢……ということになりそうです。

しかしそれでも、8 bit と 16 bit では雲泥の差です。それを目に見える形で実感できるように、まったく同じ条件下で、浮動小数点数テクスチャを用いた場合と、そうでない場合のシャドウマッピングの様子を見てみます。

※ここではあえて通常のテクスチャでは制御しきれない程度のぎりぎりのラインで比較してみました

まずは、今まで利用していた通常の 8 bit 精度のテクスチャでシャドウマッピングを行った場合です。

通常のテクスチャフォーマット

通常のテクスチャフォーマット

マッハバンドと呼ばれる縞模様は、シャドウマッピングにおける難しい問題の一つですが上の画像ではマッハバンド以外にも、へんなところにポツポツとドットが打たれたようになってしまっています。

これは、深度値の比較がうまくいかず、ノイズのように変なところに誤差が生じ影として判定されてしまっていることを表しています。

続いて、浮動小数点数テクスチャを利用した場合を見てみます。

浮動小数点数テクスチャフォーマット

浮動小数点数テクスチャフォーマット

相変わらずマッハバンドが露骨に出ていますが、これは精度の差をわかりやすくするためにぎりぎりの調整にしたためです。しかし、先ほどのノイズのようなドットはずいぶんと少なくなっているのが見て取れるのではないでしょうか。

浮動小数点数テクスチャの有効化

それでは、実際に浮動小数点数テクスチャを利用する手順を見ていきましょう。

浮動小数点数テクスチャは、2014 年 3 月現在の WebGL の規格 である ver 1.0 では、既定の状態では使えません。その代わり拡張機能、つまり Extensions を利用することによって利用することが可能になります。

参考:WebGL Extension Registry

ちなみに、IE 11 では WebGL が正式に利用できるようになりましたが、WebGL のバージョンは WebGL 0.92 です。各種拡張機能が利用できるかどうかは IE に限らずブラウザの実装によりまちまちですが、どうやら IE 11 でも浮動小数点数テクスチャは利用できるようです。※例によって実際にテストはしてません

もちろん、ブラウザが仮に対応していてもハードウェア側の制約によって、有効化しても効果が表れない場合などが考えられます。このあたりは、3D 特有の難しい問題ですね。概ね、float texture については数年前の XP マシンなどでも動いている感じですので、PC に限ってはほとんどの環境で動作すると考えてもよさそうです。

さて、では実際に浮動小数点数テクスチャを有効化する方法を見ていきましょう。

以下が、拡張機能を有効化するためのコードです。

浮動小数点数テクスチャを利用するために拡張機能を有効化

// float texture を有効化
var ext;
ext = gl.getExtension('OES_texture_float');
if(ext == null){
	alert('float texture not supported');
	return;
}

ここで登場した getExtension メソッドが、float texture に限らず各種の拡張機能を有効化するために利用されるメソッドになります。引数には有効化したい拡張機能を文字列で指定します。

注意すべき点としては、浮動小数点数テクスチャを利用するフレームバッファを初期化する前に、必ず上記の有効化処理を行っておかなくてはならないという点です。一度、拡張機能を有効化すれば、以降普通にフレームバッファの初期化処理で float texture が利用できるようになります。

実際に float texture を利用したフレームバッファの初期化処理を行うには以下のようにします。

浮動小数点数テクスチャを利用したフレームバッファの初期化

// フレームバッファをオブジェクトとして生成する関数
function create_framebuffer(width, height){
	// フレームバッファの生成
	var frameBuffer = gl.createFramebuffer();
	
	// フレームバッファをWebGLにバインド
	gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
	
	// 深度バッファ用レンダーバッファの生成とバインド
	var depthRenderBuffer = gl.createRenderbuffer();
	gl.bindRenderbuffer(gl.RENDERBUFFER, depthRenderBuffer);
	
	// レンダーバッファを深度バッファとして設定
	gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height);
	
	// フレームバッファにレンダーバッファを関連付ける
	gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthRenderBuffer);
	
	// フレームバッファ用テクスチャの生成
	var fTexture = gl.createTexture();
	
	// フレームバッファ用のテクスチャをバインド
	gl.bindTexture(gl.TEXTURE_2D, fTexture);
	
	// フレームバッファ用のテクスチャにカラー用のメモリ領域を確保
	gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT, null);
	
	// テクスチャパラメータ
	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
	gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
	
	// フレームバッファにテクスチャを関連付ける
	gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, fTexture, 0);
	
	// 各種オブジェクトのバインドを解除
	gl.bindTexture(gl.TEXTURE_2D, null);
	gl.bindRenderbuffer(gl.RENDERBUFFER, null);
	gl.bindFramebuffer(gl.FRAMEBUFFER, null);
	
	// オブジェクトを返して終了
	return {f : frameBuffer, d : depthRenderBuffer, t : fTexture};
}

ここでのポイントは texImage2D メソッドに与える引数です。

このメソッドは非常に引数が多いのでわかりにくいのですが、第八引数に gl.FLOAT を指定しているのが見てとれると思います。これにより、フレームバッファにアタッチされるテクスチャで浮動小数点数を利用することが可能になります。

また、注意しなければならない点はテクスチャを生成したあとのパラメータ指定に関する部分にもあります。ここでは従来、当サイトのサンプルではフィルタリングに関する定数として texParameteri の第三引数に gl.LINEAR を与えていました。

しかし拡張機能 OES_texture_float では gl.NEAREST を指定する必要があるので注意しましょう。この指定を間違えると、実際にレンダリングを行うタイミングでエラーが出てしまいます。

このように、浮動小数点数テクスチャを利用する際には、まず拡張機能を有効化し、続いてフレームバッファの初期化処理でいくつかの定数を浮動小数点数テクスチャ用のものを指定するようにします。あとは、通常どおりレンダリングを行うだけです。

浮動小数点テクスチャとフレームバッファ

本記事の本質とはちょっと外れる部分もあるので詳細は割愛しますが、浮動小数点テクスチャをフレームバッファにアタッチするレンダーターゲットとして利用する場合には、注意しないといけない点があるようです。

これについては、[WebGL] 浮動小数点テクスチャの扱いでハマった件 - Qiita で詳しく言及されていますので、ありがたく参考にさせてもらいましょう。

いずれ、トピックが出揃ってきて、WebGL 2.0 の登場や拡張機能の安定した稼動が確認できたりしたら、あらためて記事にするかもしれません。

まとめ

さて、浮動小数点数テクスチャ、つまり float texture について見てきましたがいかがでしたでしょうか。意外と、実は簡単に利用できるものなんだということがわかったのではないでしょうか。

一番の問題はむしろハードウェア側の対応状況なのかなと思います。環境によって動作に差が出るというのは、プログラマ目線では非常に困った問題です。なにせ、干渉することができない領域の話になってしまうからです。

ただ、拡張機能の有効化ができているのかどうかを正しくチェックすれば、フレームバッファの初期化処理の時点で分岐するなど、ある程度は対応が可能です。どうしても精度の高いテクスチャの利用が絶対条件であるという場面はまだまだ少ないと思います。今後に期待、ということになりますね。

以下に、シャドウマッピングのサンプルを浮動小数点数テクスチャで実装したものへのリンクがあります。見た目にはそれほど劇的な変化がないので非常に地味ですが、動作確認の意味も含めつつ、興味のある方は参考にしていただければと思います。※あまりマッハバンドの対策などをしていないので、見た目はあんまりキレイじゃないですが参考程度に……

entry

PR

press Z key