隠面消去とZバッファ — 手前が奥を隠すしくみ
三角形を画素に塗れるようになっても、物体が重なったとき「どちらが見えるか」を決めなければ絵になりません。描く順番に頼る方法が破綻する様子と、画素ごとに深度を競わせて必ず正しい前後関係を作るZバッファ法を、バッファの中身ごと観察します。
1. 描く順番では解決できない — ペインターズアルゴリズム
油絵のように「奥のものから順に描けば、手前が自然に上書きして正しくなる」— これがペインターズアルゴリズムです。ところが三角形単位で奥行き順を決めようとすると、交差する2枚の板のように場所によって前後が入れ替わる配置では、どちらを先に描いても必ずどこかが間違います。
交差する2枚の板 — どの順で描いても破綻する
左=上から見た配置。2枚は交差していて、左側では B(赤)が、右側では A(青)が手前。右=カメラから見た絵。①②はポリゴンをまるごと上書きするので、後に描いた色が交差線を無視して覆ってしまう(白い市松模様=正解と違う画素)。③は画素単位で深度を比べるので破綻しない。
注意 — 並べ替えでは救えない配置がある
交差だけでなく、3枚の板が互いに一部ずつ前に出る循環遮蔽も、三角形を切断しない限りどんな描画順でも正解にならない。「ポリゴン単位の順序づけ」ではなく「画素単位の判定」が必要になる理由がここにある。
2. Zバッファ法 — 画素ごとの「最前面メモ」
Zバッファ法は、色を覚えるカラーバッファと並べて、画素ごとに「今までで一番手前だった深度」を覚える Zバッファ(深度バッファ)をもう1枚持ちます。三角形が画素にかぶるたびに生まれる色候補(フラグメント)が来るたび、深度を比較して勝ったときだけ両方を上書きします。
zfrag < z_buf[x, y] のとき color[x, y] ← Cfrag, z_buf[x, y] ← zfrag
比較1回と(勝ったときだけ)書き込み2回。これを全画素・全三角形で繰り返すだけ — 描く順番は自由
カラーバッファとZバッファの中身(シーンを回転)
右のヒートマップがZバッファ: 明るい=手前、暗い=奥、黒=何も描かれていない(∞で初期化)。交差する2枚の板の前後関係が、交差線を境に画素単位で正しく入れ替わっているのが深度からも読み取れる。
POINT — 「順序不要」こそZバッファの本質
ソートが要らないから三角形を届いた順に流し込めて、画素ごとに独立な比較だから並列化できる。1970年代には「メモリを贅沢に使う力業」と呼ばれたこの方法が、メモリが安くなった今では全GPUの標準装備になった。
3. 1画素の中の「深度競争」
バッファ全体で起きていることを、たった1画素に注目してスローで見てみます。z_buf は無限遠で初期化され、フラグメントが届くたびに「比較 → 勝者だけ書き込み」。全部処理し終えると、最小の z を持つ色が自動的に残っています。
1画素の深度テストをステップ実行
届く順に注目 — 手前の三角形が先に来ても(D)、あとから来た奥の三角形(E)は比較で負けて破棄されるだけ。結果は到着順に依存しない。やっていることは min(z) の逐次計算と同じ。
4. 精度が尽きるとき — Zファイティング
Zバッファに記録できる深度は有限ビットです(多くは24〜32bit、しかも遠近で密度が偏る)。2つの面の深度差 Δz が記録の刻みより小さくなると、丸めた値が同じになり、どちらが勝つかが画素ごと・フレームごとに揺らいでちらつきます。壁にぴったり重ねたポスターがギザギザに明滅する、あの現象です。
Zファイティング — Δz を深度の刻みに近づける(擬似再現)
右の目盛りが深度の記録の刻み q。ポスターと壁の差 Δz が q に近づくと、2つの深度が同じ目盛りに丸められ、勝者は補間の計算誤差まかせに — 画面は緑とオレンジがまだらに明滅する。スライダーを 0 に近づけてみてください。
注意 — Zファイティングの定石対策
① near / far 平面を欲張らない(特に near を離す。深度精度は near 付近の逆数分布に支配される)② 重ねて描く面には polygon offset などで人為的に微小な差をつける ③ 同一平面の重ね描きはデカール専用の描き方やステンシルで逃がす。
5. まとめ
- ポリゴン単位の描画順(ペインターズ)は、交差や循環遮蔽で必ず破綻する配置がある。
- Zバッファは画素ごとに最前面の深度を記録し、比較1回で隠面消去する。ソート不要・並列向き。
- 結果は画素ごとの min(z) — フラグメントの到着順に依存しない。
- 深度差が記録の刻みを下回るとZファイティング。near/far の設定とオフセットで守る。
- 例外は半透明。Zバッファは「奥を透かして混ぜる」ことができないため、透明な物体は今も奥から手前へのソート(や順序非依存透明 OIT)が必要。
zbuf = a − b / zview
透視投影後に記録される深度は視点からの距離の逆数に比例 — 精度は near 付近に集中し、遠方ほど粗くなる
一歩先へ — Reversed-Z と early-Z
浮動小数点は 0 付近ほど目盛りが細かい。そこで深度を「手前=1、遠く=0」と逆向きに割り当てる Reversed-Z にすると、1/z の偏りと浮動小数点の偏りが打ち消し合って精度がほぼ均一になる(現代エンジンの定番設定)。さらにGPUは、色の計算より先に深度テストを行う early-Z で、負けると分かった画素のシェーディングを丸ごと省略している — Zバッファは正しさだけでなく速さの要でもある。