浮動小数点数VTF
今回のサンプルの実行結果
float texture と VTF
前回は頂点テクスチャフェッチ、別名 VTF を扱いました。
近年の大量のデータを必要とする 3D プログラミングの世界では、非常に重要なテクニックの一つである頂点テクスチャフェッチは、簡潔に言うなら「頂点シェーダでテクスチャを参照する」という仕組みなのでしたね。
今回は、前回に引き続き、この頂点テクスチャフェッチを活用した例として、浮動小数点数テクスチャ(フロートテクスチャ)との合わせ技をやってみたいと思います。実際に頂点シェーダでテクスチャを参照することができたとしても、その精度が使い物にならないようでは意味がありません。浮動小数点数テクスチャを用いれば、精度の高い VTF が実現できます。先に言ってしまうと、今回のサンプルは実用性はほとんどありません。通常の VTF と比べ、浮動小数点数テクスチャを用いたことで精度が上がっていることを実感していただければと思います。
シェーダへの修正
とは言え、前回のサンプルからの修正点は、実はあまり多くありません。
前々回、浮動小数点数テクスチャを扱った際に指定した拡張機能を有効化しておき、その上で VTF を行うための処理を記述していきます。浮動小数点数テクスチャに関する詳細な説明は、以前のテキストを参照してみてください。
参考:浮動小数点数テクスチャ
拡張機能を有効化し、フレームバッファに対して正しくテクスチャフォーマットなどが指定できたら、あとは前回のときと同様に、テクスチャへの描き込みを行うシェーダとそれを読み出すシェーダの二組のシェーダを記述します。
基本的な部分は、前回のサンプルとほとんど同様です。ちょっと駆け足ですが、まずは頂点の座標位置をフレームバッファ(にアタッチされたテクスチャ)へレンダリングするためのシェーダから見ていきましょう。
座標位置描き込み用頂点シェーダ
attribute vec3 position;
attribute float index;
varying vec3 vColor;
const float frag = 1.0 / 16.0;
const float texShift = 0.5 * frag;
const float rCoef = 1.0;
const float gCoef = 1.0 / 255.0;
const float bCoef = 1.0 / (255.0 * 255.0);
void main(void){
float r = position.x * rCoef;
float g = position.y * gCoef;
float b = position.z * bCoef;
vColor = (vec3(r, g, b) + 1.0) * 0.5 ;
float pu = fract(index * frag) * 2.0 - 1.0;
float pv = floor(index * frag) * frag * 2.0 - 1.0;
gl_Position = vec4(pu + texShift, pv + texShift, 0.0, 1.0);
gl_PointSize = 1.0;
}
さて、前回のサンプルと比較して、どのあたりが変わっているのかを見ていきます。
前回のサンプルでは、テクスチャへ描き込む際に RGB の各要素には XYZ の値をそのまま割り当てていました。この方法では、頂点テクスチャフェッチがそもそも正しく実行できているかどうか――という点では、実際にサンプルを動作させてみれば確かめることができましたが、どの程度の精度が出ているのかはいまいちわかりにくかったと思います。
そこで、今回は先述したとおり浮動小数点テクスチャを利用しつつ、さらに XYZ の描き込みをちょっと変えています。
まず、先ほどの頂点シェーダの内で使われている定数群に注目してみます。
頂点シェーダ内の定数
const float frag = 1.0 / 16.0;
const float texShift = 0.5 * frag;
const float rCoef = 1.0;
const float gCoef = 1.0 / 255.0;
const float bCoef = 1.0 / (255.0 * 255.0);
上の二つは、前回のサンプルでも登場したものですね。今回追加したのは下に並んでいる三つです。
なんとなくその名前から連想できるかと思いますが、RGB の各成分に値を格納する際に利用する係数です。
R 成分には、特に修正を加えずそのまま格納します。G 成分には、値を本来の 255 分の 1 にした値を、B 成分にはさらにその 255 分の 1 です。
ここまで値を小さくしてしまうと、通常の 8 bit テクスチャでは Z 値に相当する B 成分が処理できなくなります。かろうじて R と G の成分は正常な範囲で描き込めたとしても B 成分(つまり Z の座標値)は値が小さすぎて端数が丸め処理されてしまいます。しかし浮動小数点数テクスチャであれば問題ありません。しっかりと B 成分に関しても描き込み、読み込みが可能です。
前回のサンプルでは RGB のそれぞれに XYZ をそのまま当てはめていたので、フレームバッファのテクスチャの色合いが非常にカラフルでした。しかし、今回は G 成分と B 成分は非常に小さな値になってしまっているので、色の分布で見るとかなり地味な色合いになっています。R 成分に相当する赤い色は、係数が 1.0 なので色の濃淡の変化が目視できるレベルになっていますよね。
フレームバッファに描き込まれた色としての座標情報
座標位置の読み出し
さて続いては読み出し側のシェーダのほうを見てみます。
こちらに関しても描き込んだ際と同じく、計数を使って値を復元します。それ以外の基本的なロジックとしては前回のサンプルとほぼ同様です。
頂点座標位置読み出し用頂点シェーダ
attribute float index;
uniform mat4 mvpMatrix;
uniform sampler2D texture;
const float frag = 1.0 / 16.0;
const float texShift = 0.5 * frag;
const float rCoef = 1.0;
const float gCoef = 255.0;
const float bCoef = 255.0 * 255.0;
void main(void){
float pu = fract(index * frag + texShift);
float pv = floor(index * frag) * frag + texShift;
vec3 tPosition = texture2D(texture, vec2(pu, pv)).rgb * 2.0 - 1.0;
tPosition *= vec3(rCoef, gCoef, bCoef);
gl_Position = mvpMatrix * vec4(tPosition, 1.0);
gl_PointSize = 16.0;
}
描き込んだ際とは反対に、読み出した値に対して一定倍になるように係数を掛けてやります。そして、係数によって修正された値をそのまま頂点座標位置として利用するわけですね。前回のサンプル同様、この頂点シェーダは直接 VBO を介して頂点の座標を受け取っているわけではありません。あくまでも、テクスチャから読み出した値だけで、頂点の座標位置を決定しています。
javascript 側のプログラムには、実は浮動小数点数テクスチャを利用するための設定を行う以外に、修正を行った箇所はありません。実際のところ、今回のサンプルの内容は前回のテキストとひとまとめにしてもよかったんですが、前回が結構大盛りな内容だったので別枠にしました。修正としては軽微なものですので、前回の内容が理解できていれば、そんなに難しい部分はないのではないでしょうか。
まとめ
ちょっと簡単すぎる内容だったかもしれませんが、浮動小数点テクスチャと頂点テクスチャフェッチの合わせ技が実現できました。
このサンプルを作り始めた当初は、R 成分だけで XYZ のすべての情報が格納できないだろうか――などとも考えてはみたのですが、頂点座標にはマイナスの値が入る可能性もあることがネックでした。結局、プログラマ独自の実装でうまく負の数値を処理するようなロジックを組んで、そのローカルルールに則って処理する分には十分に有用なテクニックにもなり得るとは思います。ですがそのような極度の最適化を行っているサンプルというのも、解説テキストとしてはかなり逸脱した内容になってしまいそうでしたので、結局このような簡易な内容に落ち着きました。
そもそも、浮動小数点数というのはその名が示すとおり小数点の位置を汎用的に表すことができるような仕組みで表わされます。符号や桁数が個別のビットに割り当てられ管理されているため、GLSL から一つの float 型データ領域全体を分割して個別にビット単位で処理するようなことは基本的にできません。※自分も完全に理解できているわけではないので浮動小数点数について詳しく知りたい人は各自調べてみてください。
ですが、工夫次第では float texture を利用して必要なデータを最適化することはできます。これに関してはプログラマの実装次第です。
今回も実際に動作するサンプルを以下のリンクより確認できます。自分が使っているマシンで、正しくレンダリングされるかどうかチェックしてみてください。