OpenGLプログラミング/Transparency
はじめに
[編集]不透明なオブジェクトなら、コンピュータグラフィックスで描画するのは簡単です。 画面上の特定の画素を見る場合、シーン内にはそのピクセルにゼロ、1つ、またはそれ以上のオブジェクトが重なることもあります。 zバッファのおかげで、どのオブジェクトがいちばん手前かを心配する必要はありません。 最初はただそのピクセルの最初のオブジェクトを描画し、その深さを覚えておいて、そして次のオブジェクトを取得するときに、前のオブジェクトからにものに対しての第2のオブジェクトの深さを確認し、それ以下であれば第2のオブジェクトの色でピクセルを上書きし、そうでなければそのまま何もしません。
半透明オブジェクトだと、物事はもっと難しくなります。 GPUでは、ピクセルの色を上書きするか、それとも深度値にもとづいてそれを維持するかを選択することはできません。 その代わりに、GPUは透明なオブジェクトの色とその背後にある色を混合する必要があります。 しかし、まだそのオブジェクトが描画されていなければ、その背後にあるオブジェクトの色を知ることはできません。 zバッファは、ここでは役に立ちません。オブジェクトを描画する順序は思いがけず非常に重要になるからです。
この問題には様々な解決手段があります。 明白な解決策は、最寄りのものから遠くのものまですべてのオブジェクトをソートすることです(でしょう?)。 しかし、ソートは非常にコストがかかり、シーンが変化するたびに、毎回やり直さなければならず、そこにはカメラの動きも含まれます。 幸いにも、それを行うことを避けるための、いくつかのテクニックがありますが、それらにはすべていくつかの制限があります。
透明性をシミュレートするためにアキュムレーションバッファを使用することができます。 例えば不透明なオブジェクトのあるシーンで、50%透明な着色ガラスの板があるところを想像してみましょう。 これから使用していくトリックは、シーンを2度レンダリングするものです: 一度目は、ガラスを含むすべてのオブジェクトを、すべて完全に不透明なものとして描画します。 二度目は不透明なオブジェクトだけをレンダリングし、透明なオブジェクトのレンダリングをスキップします。 アキュムレーションバッファを使用して、は2つのフレームの平均を計算します。 その結果、ガラス板がある場所は、その背後にあるオブジェクトの光の50%が通りぬけて輝くことになります。
アキュムレーションバッファで2つ以上のフレームを平均化することにより、または別の値の各フレームを乗算することにより、容易に透明度を変化させることができます。 しかし、この手法の限界は、2つ以上の透明オブジェクトが直接お互いの背後にあるときに、正しく動作しないということです。 実生活では、2枚の50%の透明なガラス板では、その背後にある物体からの光の25%だけを透過させる結果になります。 しかし、アキュムレーションバッファのトリックだと、その背後にあるオブジェクトから見えるのは50%のままになります。
Order-independent transparency using the accumulation buffer
[編集]オブジェクトを透明にするための通常の方法は、そのテクスチャのアルファチャンネルを変更することです。 なので、テクスチャをルックアップするだけの非常に単純なフラグメントシェーダで始めて、アルファチャンネルに対するカットオフ値を設定変更できるようにつなげていきます。 アルファ値がカットオフ値よりも低い場合、そのフラグメントを描画せず、そうでなければそれを不透明なものとして描画します:
varying vec2 texcoord;
uniform sampler2D texture;
uniform float alpha_cutoff;
void main(void) {
vec4 color = texture2D(texture, texcoord);
// Don't draw pixels with an alpha value lower than the cutoff
if(color.a < alpha_cutoff)
discard;
gl_FragColor = color;
}
その後、シーンを2回描画します。 一度目は1/3のカットオフ値にします。 50%の透明度を持つオブジェクトは、この最初のパスで描画されます。 二度目は2/3のカットオフ値を使用します。 50%の透明度を持つオブジェクトは、このパスで描画されません。 その後、アキュムレーションバッファから2つのパスの平均を取得し、それを画面に送信します。
glUniform1f(uniform_alpha_cutoff, 1.0 / 3.0);
draw_scene();
glAccum(GL_LOAD, 0.5);
glUniform1f(uniform_alpha_cutoff, 2.0 / 3.0);
draw_scene();
glAccum(GL_ACCUM, 0.5);
glAccum(GL_RETURN, 1);
glSwapBuffers();
エクササイズ
[編集]- 上記のコードを修正して、シーンを描画するのは2回だけのままで、ただし透明度が80%のオブジェクトに対しても動作するようにしましょう。
- 上記のコードを修正して、2回以上のシーンを描画するようにし、25%、50%、75%のすべての透明オブジェクトに対して作用するようにしましょう。
- 前のチュートリアルのいずれかで、このテクニックを実装してみましょう。
- アンチエイリアシング、モーションブラー、および/または被写界深度といった手法を効率的に組み合わせることはできるでしょうか?
- オブジェクトのソートは完璧なアプローチのように思えます。 しかし、オブジェクトが奇妙な形状になることもあり、2つの物体の間に明確な順序がないかもしれません。 例えばからみ合った2つのリングついて考えてみましょう。 しかし、その代わりに個々の三角形すべてのソートについてだとどうでしょうか?