3DS のレンダーバッファにはブロック 8 モードとブロック 32 モードの 2 通りの設定が存在します。後述するアーリーデプステストを使用する場合はブロック 32 モードでなければなりませんが、使用しない場合は初期状態で設定されているブロック 8 モードのままにしてください。
ブロックモードの設定は glRenderBlockModeDMP()
で行います。
void glRenderBlockModeDMP(GLenum mode);
mode
は以下の設定値から選択します。
設定値 | ブロックモード |
---|---|
GL_RENDER_BLOCK8_MODE_DMP
|
ブロック 8 モード(デフォルト) |
GL_RENDER_BLOCK32_MODE_DMP
|
ブロック 32 モード |
ブロック 32 モードはブロック 8 モードと異なり、以下のような制限があります。
- ブロックサイズが 32 に変更されるため、レンダーバッファおよびディスプレイバッファの幅と高さのピクセル数が 32 の倍数でなければなりません。この制限のため、LCD とぴったり同じサイズのレンダーバッファ、ディスプレイバッファを確保することができなくなります。
-
glCopyTexImage2D()
、glCopyTexSubImage2D()
でのテクスチャへのコピーは正しく行われません。 - フレームバッファにテクスチャを指定(render to texture)した場合の描画結果は正しくありません。この制限のため、シャドウやガスのようにテクスチャにレンダリングする必要のある処理を行うことができなくなります。
ブロックモードを変更しながらレンダリングを行った場合の描画結果は保証されません。同じレンダーバッファへの glDrawElements()
、glDrawArrays()
、glReadPixels()
は同じブロックモードで実行してください。現在のブロックモードを取得するには glGetIntegerv()
に GL_RENDER_BLOCKMODE_DMP
を渡してください。
2.1. アーリーデプステスト
アーリーデプステストとはラスタライズと同時に行われるデプステストのことで、通常のデプステストのみで行うよりも早い段階で不要なフラグメントを棄却することができます。この機能を使用するには、レンダーバッファのブロックモードがブロック 32 モードでなければなりません。
アーリーデプステストはアーリーデプスバッファというバッファを使用して処理が行われます。アーリーデプスバッファは、32x32 ピクセルを 1 つのブロックとして扱い、ブロックの代表値としてデプス値を 1 つ保持しています。代表値は 12 bit 精度で記憶され、デプス情報が標準のデプスバッファに書き込まれるときに、対応するブロックのアーリーデプス値が更新されます。アーリーデプスバッファはメモリ上に領域を確保する必要がなく、1024x1024 ピクセルのデプスバッファまで対応することができます。比較はラスタライズ時に計算された描画中のポリゴンのデプス代表値(最小値または最大値)との間で行われ、アーリーデプステストにパスした場合はブロック内すべてのフラグメントが次のプロセスに送られ、フェイルした場合はブロック内すべてのフラグメントが棄却されます。
アーリーデプステストを行うことでフラグメント処理のパイプラインに送られるフラグメントの量を抑えることができます。しかし、標準のデプステストよりも判定の精度が低いために標準のデプステストとは異なる判定をすることがあり、思ったようにパフォーマンスが改善されなかったり、本来は描画されるべきフラグメントが棄却されたりする可能性があることに注意しなければなりません。判定で棄却されるべきフラグメントが誤ってパスしたとしても通常のデプステストで棄却されるため、パフォーマンス向上には繋がりませんが特に問題は発生しません。しかし、逆にパスされるべきフラグメントが棄却された場合は描画されるべきフラグメントが描画されず、表示上の問題が発生します。
アーリーデプステストは早期に実行するデプステストという位置付けにあるため、デプステストが無効である場合はアーリーデプステストも無効にしなければなりません。アーリーデプステストのみを実行した場合は、描画されるべきフラグメントが描画されない可能性があります。
2.1.1. 実行の制御
通常のデプステストと同様に、アーリーデプステストの実行と停止は glEnable()
と glDisable()
で行います。アーリーデプステストが実行されるかどうかは glIsEnabled()
で取得することができます。それぞれの関数への引数には GL_EARLY_DEPTH_TEST_DMP
を渡します。
glEnable(GL_EARLY_DEPTH_TEST_DMP); glDisable(GL_EARLY_DEPTH_TEST_DMP); glIsEnabled(GL_EARLY_DEPTH_TEST_DMP);
2.1.2. バッファのクリア
アーリーデプスバッファは通常のデプスバッファとは独立しています。クリアは個別に行うことができますが、通常のデプスバッファをクリアした場合は必ずアーリーデプスバッファもクリアしてください。
アーリーデプスバッファをクリアするには、mask
に GL_EARLY_DEPTH_BUFFER_BIT_DMP
を含むビットマスクを渡して glClear()
を呼び出してください。バッファのクリアに使用される値は、glClearEarlyDepthDMP()
で設定します。
void glClearEarlyDepthDMP(GLuint depth);
アーリーデプスバッファのクリア値には通常のデプスバッファのクリア値とほぼ同じ値を使用しますが、通常のデプスクリア値が GLfloat
型で指定するのに対し、アーリーデプステストでは正の整数で指定しなければなりません。指定する値の目安は、以下の式で計算することができます。
アーリーデプステストが通常のデプステストよりも低い精度で行われることを考慮して、offset
には GL_LESS
または GL_LEQUAL
モードならば 0x1000 以上の正の値、GL_GREATER
または GL_GEQUAL
モードならば -0x1000 以下の値を採用することを推奨しています。アーリーデプスバッファのクリア値がとりうる値の範囲は 0x000000 ~ 0xFFFFFF ですので、offset
を加えた結果がこの範囲を超えた場合は 0x000000 ~ 0xFFFFFF にクランプしてください。
設定されているクリア値を取得するには、pname
に GL_EARLY_DEPTH_CLEAR_VALUE_DMP
を渡して glGetIntegerv()
を呼び出してください。
2.1.3. デプスバッファ更新フラグ
アーリーデプスバッファでは、アーリーデプス値を 32x32 ピクセル単位のブロックで保持するほかに、通常のデプスバッファの更新フラグ(デプスバッファ更新フラグ)を 4x4 ピクセル単位で保持しています。このフラグには、通常のデプステストの結果によって(通常の)デプスバッファの 4x4 の範囲にあるピクセルが 1 ピクセルでも更新されると 1 がセットされ、そのピクセルが含まれているアーリーデプスバッファのブロックで保持されているアーリーデプス値が更新されます。フラグが 1 にセットされると、そのあとにその範囲にあるピクセルでデプスバッファが更新されたとしてもフラグは変化せず、アーリーデプス値も更新されません。ただし、同じアーリーデプスバッファのブロックに含まれている、まだフラグが 0 の範囲のピクセルでデプスバッファが更新されたときは、フラグが 1 にセットされると同時にアーリーデプス値が更新されます。なお、デプスバッファ更新フラグが 0 にクリアされるのは、アーリーデプスバッファをクリアしたときのみです。
デプスバッファ更新フラグによって制御されるため、アーリーデプステストの効果はモデルの描画順序に強く依存しています。アーリーデプステストの効果を大きくするには、比較関数が GL_LESS
または GL_LEQUAL
の場合は手前のモデルから奥のモデルの順に描画し、比較関数が GL_GREATER
または GL_GEQUAL
の場合は奥のモデルから手前のモデルの順に描画します。
例えば、比較関数に GL_LESS
を設定しているときに奥側にあるモデルを先に描画してしまうと、そのあとに手前側にあるモデルを描画しても、アーリーデプスバッファは更新されないため、アーリーデプステストにパスするフラグメントが増え、アーリーデプステストの効果が小さくなってしまいます。逆に、手前側にあるモデルを先に描画すれば、アーリーデプステストで破棄されるフラグメントが増えることになり、アーリーデプステストの効果が上がります。
また、上記の設定で奥側にある背景を最初に描画する場合でもアーリーデプステストの効果が得られません。このような場合は、背景を描画するときだけアーリーデプステストを無効にし、ほかのモデルを描画するときにアーリーデプステストを有効にすることで、限定的にアーリーデプステストの効果を得ることができます。
更新フラグによって 1 度描画されたピクセルに対応するアーリーデプス値が更新されませんので、偶数フレームと奇数フレームで比較関数を変えてデプスバッファのクリアを回避していても、更新フラグを 0 にするためにフレームごとにアーリーデプスバッファをクリアしなければなりません。
2.1.4. 比較関数の設定
アーリーデプステストで使用する比較関数の設定は通常のデプステストとは別に設定しなければなりません。比較関数は glEarlyDepthFuncDMP()
で設定することができます。
void glEarlyDepthFuncDMP(GLenum func);
func
には比較関数を指定します。設定可能な比較関数は GL_LESS
、GL_LEQUAL
、GL_GREATER
、GL_GEQUAL
の 4 種類です。そのほかの比較関数は設定することができません。通常のデプステストと異なる設定でのアーリーデプステストは、判定に食い違いが生じる可能性があります。
アーリーデプステストの比較関数には制約があり、容易に変更することができません。1 つのシーンでデプステストの比較関数を変更しながら描画する場合にはアーリーデプステストは使用できなくなってしまいますが、アーリーデプスバッファだけをクリアすることで制約から解放されることがあります。
現在設定されている比較関数を取得するには、pname
に GL_EARLY_DEPTH_FUNC_DMP
を渡して glGetIntegerv()
を呼び出してください。初期状態では GL_LESS
が設定されています。
アーリーデプステストが有効である場合、標準のデプスバッファに値が書き込まれるときに、対応するアーリーデプスバッファの値をブロック内のフラグメントが持つデプス値の最大値または最小値で更新します。アーリーデプステストの比較関数が GL_LESS
モードまたは GL_LEQUAL
モードの場合は最大値で、GL_GREATER
モードまたは GL_GEQUAL
モードの場合は最小値でアーリーデプスバッファは更新されます。アーリーデプスバッファの内容を外部に直接取り出すことや、外部から直接値を設定することはできません。
2.1.4.1. 比較関数の変更について
1 フレームを描画する間に比較関数を変更するかどうかに関わらず、アーリーデプステストで比較関数を変更するにはいくつかの制約があります。
- アーリーデプステストの比較関数は標準のデプステストと同じでなければなりません。
- アーリーデプステストの比較関数を一度設定したら、アーリーデプスバッファをクリアするまでアーリーデプステストの比較関数を変更することができません。
- 1 つのレンダーバッファに対して通常のデプステストの比較関数が変更されて描画が行われたら、アーリーデプスバッファをクリアするまでアーリーデプステストを有効にすることができません。
- 通常のデプステストの比較関数がアーリーデプステストで使用できない比較関数に変更された場合は、アーリーデプステストを無効(停止)にしなければなりません。
これらの制約に従わない場合、間違ったフラグメントの棄却が行われて正しい描画結果が得られない可能性があります。
制約を回避する方法としては、パフォーマンスを向上させたい箇所のみアーリーデプステストを有効にする方法や、比較関数を変更するたびにアーリーデプスバッファをクリアする方法などがあります。ただし、後者の方法でアーリーデプスバッファのクリアが頻繁に行われると、間違ったパス判定が増えてパフォーマンスが向上しない可能性があります。
1 フレーム中で比較関数を変更するなど、通常のデプスバッファのクリアと異なるタイミングでアーリーデプスバッファをクリアするときは、誤判定を防ぐためにも、クリア値に最小値(0x000000)または最大値(0xFFFFFF)を設定するようにしてください。
2.1.5. オフセットのあるビューポートを使用したときの問題
アーリーデプステストでは、アーリーデプスバッファを読み出す際の座標に、ビューポートのオフセットが適用されないというハードウェアの不具合があります。そのため、ビューポートのオフセット(glViewport()
の x
、y
)が (0, 0) ではない場合に、アーリーデプステストが正しく行われなくなります。
アーリーデプスバッファの更新は、ビューポートのオフセットが適用された座標に対して行われるため、ビューポートのオフセットが適用されるピクセルのデプス値と、オフセットが適用されないアーリーデプスバッファのデプス値が比較されてしまいます。これにより、アーリーデプステストにパスするはずのピクセルがフェイルと判定されてしまい、描画されるべきピクセルが描画されない問題が発生します。フェイルと判定された場合、そのピクセルを含む 8×8 ピクセルが誤って捨てられてしまいます。
この問題を回避するには、以下の 2 つの方法が考えられます。
- ビューポートのオフセットに (0, 0) 以外の値を設定し、アーリーデプステストを無効にします。1 つのシーンでアーリーデプステストの有効・無効を切り替えながら描画すると、アーリーデプステストの効果は落ちますが、通常のデプステストで捨てられるべきピクセルは捨てられるため、描画結果は同じになります。
- ビューポートのオフセットに (0, 0) を設定し、代わりにモデルビュー変換を使って描画領域をずらして、不要な領域をシザリングでカットします。