OpenGLプログラミング/Glescraft 3
はじめに
[編集]前の2つのチュートリアルでは、ボクセルはとても退屈な外見で、ボクセルを区別しづらくなっていましたが、それは単一の色しか持たず、照明効果にまったく気を使ってこなかったからです。 このチュートリアルでは、1つのテクスチャ座標だけを使用して、ボクセルに別個のテクスチャを与えることができる方法について説明していきます。 さらに、フラグメントシェーダを微調整して、シーンの照明において小さな方法で非常に大きな改善を達成していきます。
テクスチャ
[編集]ボクセルにテクスチャを入れたいとき、チャンク内のすべてのボクセルを同じテクスチャにしたくはありません。 しかし、一度にチャンク内のすべての三角形をレンダリングする場合、ボクセル間のテクスチャを切り替えることはできません。 そこで、 texture atlasを使用して、ひとつのOpenGLテクスチャのなかにボクセルにペイントしたいすべての画像を保存する必要があります。 利用できるテクスチャ座標はひとつだけで、可能な値は256あります。 テクスチャアトラス内の可能なサブイメージ256のうちひとつを指すように使用するとよいでしょう。
16のサブイメージをもつテクスチャアトラスがあると仮定します。 すべてのサブイメージは同じサイズ(SW x SH)を持っていますが、正確な大きさは気にしなくてかまいません。 テクスチャアトラスはひとつの行にあるすべてのサブイメージを持つことになるので、テクスチャアトラスは(SW * 16)×SHピクセルを持つことになります。 blk[x][y][z]配列は今0〜15の範囲の値を含む必要があります。 フラグメントシェーダでは、頂点シェーダから受け取る varying vec4 texcoordから実際のテクスチャ座標を作成する必要があります。 明らかに、0〜15の整数値だけでは不十分です。 しかし、ともに役目を果たしてくれるx、y、z座標を持っています。 これらは"varying"なので、VBOからの整数値は含まれていませんが、フラグメントが頂点の間でどれだけ離れているかに応じて、整数値の間の任意の値を持つことができます。 特に、(0, 0, 0)から(1, 1, 0)まで四角形を描画する場合、z座標はどこも0になりますが、yとzの座標は間の任意の値を取ることができる。 この四角形にテクスチャをつけるために、次のフラグメントシェーダを使用することができます:
#version 120
varying vec4 texcoord;
uniform sampler2D texture;
void main(void) {
gl_FragColor = texture2D((texcoord.x + texcoord.w) / 16.0, texcoord.y);
}
16で除算するのは、テクスチャの行で16のサブイメージを持っているという事実からのものです。 また、wの座標はクワッド内のすべての頂点に対して同じなので、フラグメントシェーダの定数値を保持します。 MVP行列が適用される前は、 texcoordは単なる uniform coordのコピーなので、テクスチャはMVPの変化によって影響を受けないことを覚えておきましょう。
もちろん、このシェーダは、他にありうる座標の四角形のほとんどで機能しないことに、すでにお気づきでしょう。 まず、(X, Y, *)から(X + 1, Y + 1, *)までの角部をもつ四角形を処理するとき、xおよびyは整数座標で、*は任意のとりうるzの意味で、0〜1の範囲に戻してx座標をマッピングするために fract()関数を使用することができます:
gl_FragColor = texture2D((fract(texcoord.x) + texcoord.w) / 16.0, texcoord.y);
texcoord.yでfract()を使用する必要がないのは、私たちのテクスチャアトラスが単一の行の画像だけを持ち、そしてOpenGLが垂直方向のテクスチャラッピングを取り仕切ってくれるからです。 このシェーダがうまく機能するのは、任意のボクセルの面が正または負のz方向を指しているときです。 しかし、これらの面では、面のすべての頂点がz座標の同じ/整数/値を持ちます。 したがって、 fract()関数を適用する前に、安全にx座標にそれを追加することができます:
gl_FragColor = texture2D((fract(texcoord.x + texcoord.z) + texcoord.w) / 16.0, texcoord.y);
同じ引数が正または負のy方向をさすボクセル面を保持するので、これらの面に同じシェーダーを使用することができます! しかし、そこにもy座標を配置することはできません。 また、正または負のy方向に向いた面を正しくレンダリングするための唯一の方法は、2つのフラグメントシェーダを持たせて、xとzに向いている面とは別にyに向いた面をレンダリングするか、または追加的な情報を加えて、頂点が2つの場合の間でフラグメントシェーダでの区別をできるようにするかのどちらかです。 私たちのテクスチャでは16のサブイメージしか使用しないで、W座標で使用するのは4ビットですみます。 実際には非常に基本的な "normal"ベクトルとして別のビットを使用することもできます。 y方向に向いた面を示すときには、負のw座標を使用します:
if(texcoord.w < 0)
gl_FragColor = texture2D((fract(texcoord.x) + texcoord.w) / 16.0, texcoord.z);
else
gl_FragColor = texture2D((fract(texcoord.x + texcoord.z) + texcoord.w) / 16.0, texcoord.y);
w座標の負の値を心配する必要はなく、VBOを作成するときに限れば、 blk[x][y][z]の値から16の倍数を引けば、それを負にすることができます。 その方法だと、テクスチャ座標の範囲は0 .. 1の外になりますが、OpenGLによって正しくラップされてテクスチャアトラスの正しい位置になります。
エクササイズ:
- 前のチュートリアルでは、隣接する面をマージすることで、描画する必要がある三角形の数を減らせることを見てきました。 上記のシェーダーは、そのような場合でもまだ機能するでしょうか?
照明
[編集]テクスチャがついていても、ボクセルを区別するのが難しいこともあります。 シーンをさらに自然な外観にし、ボクセルのどちら側を見ているのか区別しやすくなるように、 前のセクションで紹介した "法線" をちょっぴり使用してボクセルの側面を少し暗くします。 これは現実の世界でいう正午の、太陽が真上にあるときのシェーディングをシミュレートします。
if(texcoord.w < 0)
gl_FragColor = texture2D((fract(texcoord.x) + texcoord.w) / 16.0, texcoord.z);
else
gl_FragColor = texture2D((fract(texcoord.x + texcoord.z) + texcoord.w) / 16.0, texcoord.y) * 0.85;
霧
[編集]現実の世界では、遠くのオブジェクトは、すぐ目の前にあるオブジェクトよりもかすみがかり、色も不鮮明になって見えます。 これは、大気による光の散乱に起因します。 それはほとんど霧と同じ効果ですが、大きな違いは強さです。 これをフラグメントシェーダで次のように実装することができます:
#version 120
varying vec4 texcoord;
uniform sampler2D texture;
const vec4 fogcolor = vec4(0.6, 0.8, 1.0, 1.0);
const float fogdensity = .00003;
void main(void) {
vec4 color;
if(texcoord.w < 0)
color = texture2D((fract(texcoord.x) + texcoord.w) / 16.0, texcoord.z);
else
color = texture2D((fract(texcoord.x + texcoord.z) + texcoord.w) / 16.0, texcoord.y) * 0.85;
float z = gl_FragCoord.z / gl_FragCoord.w;
float fog = clamp(exp(-fogdensity * z * z), 0.2, 1);
gl_FragColor = mix(fogcolor, color, fog);
}
フラグメントシェーダの上部で、2つの定数を定義します。 fog colorは、非常に遠くにあるときにオブジェクトが持つ色です。 fog densityは霧効果の強さを制御します。 とても小さい方の値は大気の散乱や光のぼやけを表し、大きいほうの値は霧の濃さを表しています。
フラグメントからカメラまでの距離は、 gl_FragCoord.z by gl_FragCoord.wによって割ることで算出できます。 霧の影響は距離の指数関数になります。 変数 fogは霧が適用された後に"left"実際の色の割合を表します。 最終的なフラグメントの色は元の色とフォグの色のミックスです。
透明性
[編集]最後に、透明なピクセルを持つテクスチャを作ることができます。 透明テクスチャの適用にブレンドを使用することもできますが、フラグメントシェーダで完全な透明ピクセルをシミュレートすることも可能です。 colorを計算したすぐ後に、アルファ値に基づいてフラグメントを破棄することができます:
if(color.a < 0.5)
discard;
discardキーワードは、フラグメントプログラムを起動させて、以降の処理を停止させるためのものです (C言語でいう "return"文のようなものです )。 このover blendingの利点は、Zバッファの値が透明ピクセルで更新されないということです。 blendするときで、カメラの近くにある透明な三角形をレンダリングする場合、かつその背後にある不透明なものがある場合、深度テストに失敗するので、不透明なものが描画されません。
エクササイズ:
- サブイメージのいくつかに透明度を追加し、その結果を見てみましょう。
- 前のチュートリアルから chunk::update()関数を変更し、部分的に透明な面を適切に処理しましょう。
- blendingの代わりにdiscardキーワードを使用してみましょう。