ホーム < ゲームつくろー! < DirectX技術編 < Zバッファとアルファブレンドの嫌な関係


その17 Zバッファとアルファブレンドの嫌な関係


 アルファブレンドは本当にポピュラーに使われるようになりました。2DでMMXを用いて極限まで合成を最適化していた時代が懐かしく思えます。正にDirect3Dさまさまです。さてアルファブレンドが簡単に使えるようになると、ちょっとした部分でもアルファブレンドを使いたくなるのですが、一つ考慮すべき問題があります。それはZバッファとの関係です。アルファブレンドとZバッファを同時に用いると、思わぬレンダリングのミスが引き起こされてしまうのです。
 この章ではZバッファとアルファブレンドが引き起こすレンダリングの問題点を整理し、その解決方法を模索していきたいと思います。


@ Zバッファ

 まずはZバッファについてのおさらいです。Zバッファというのは皆さんがご覧になっている2Dスクリーンのすべての点に設けられた、カメラとオブジェクトまでの距離を記憶するメモリ領域の事です。イメージは下図のような感じです。

 Zバッファは、カメラを通して見た画であるスクリーン上のピクセル(x, y)について一番手前にあるオブジェクトまでの距離を記憶します。例えば、上の図でワールド空間に赤い球が置かれると、Zバッファは赤い球が映るピクセルすべてについてその距離を自動的に計算して記憶します(例えばZ(x,y)=5000など)。次に青い球を手前に置いたときは、同じように青い球が映るピクセルについて距離を計算します(Z(x,y)=2300)。もしピクセル(x,y)についてZバッファに保持されている最短距離よりも青い球までの距離が短いという結果になったら、そのピクセルは青い球の色で塗りつぶされます。逆に赤い球が手前の場合は、塗りつぶされることはありません。

 これにより、Zバッファを用いるとオブジェクトの前後関係が正されたレンダリングしてくれるわけです。これは、昨今の3Dレンダリングの必須機能になっています。

 Zバッファは通常16bitの数値(0〜65535)で記録されます。カメラには切り取る空間があり、その最も遠い距離から最近距離が65536分割され、前後関係がテストされます。



A アルファブレンド

 続いてDirect3Dのアルファブレンドのおさらいです。アルファ値付きのテクスチャ画像を板ポリゴンに貼り付けたとします。その時のポリゴンの色(テクスチャ)情報は次のようになります。

 オレンジ色のテクスチャの各ピクセルにあるアルファ情報をポリゴンに適用すると、左の図のようにガラス板のような状態になります。この段階でポリゴンの表面にはアルファ情報があると判断されます。実際にレンダリングをするとき、通常はポリゴン(ソース)のアルファ値Asを用いて、次のように画面に合成します。

 REScolor = As * TEXcolor + (1-As) * BACKcolor

 REScolorはスクリーンの色、TEXcolorは合成しようとするテクスチャ(ソース)の色、BACKcolorは塗りつぶし先にある色です。このアルファブレンド法は昔から本当に広く使われているブレンドの王道です。



B Zバッファのいたずら

 さて、Zバッファとアルファブレンドのおさらいが済んだところで、これらを合わせた時に起こる問題を示しましょう。アルファブレンドはスクリーンに合成する時に行われ、上で示したように機械的に計算されます。一方Zバッファの計算もこの時に行われます。最初に板ポリゴンがレンダリングされたとしましょう。自動的にZバッファに板ポリゴンまでの距離が記憶されます。次に、その板ポリゴンの後ろに上の図のような球を置いたとします。この時アルファブレンドの計算が行われると、球のテクスチャのアルファ値を元にスクリーンの色が決められます。上の例で球は透過しない(As=1)とするなら、

 REScolor = (1) * TEXcolor + (0) * BACKcolor = Blue

となり、スクリーンには手前に板ポリゴンがあるにもかかわらず球の青色が映し出される事になります。「おいおい、それじゃ前後関係がおかしくなっちゃうよ」となりますよね。そこでZバッファが頑張ってくれます。 Zバッファは塗りつぶそうとしている青い球までの距離とビルボードの距離を比較します。上の場合だと、板ポリゴンの方が手前ですから、青色を塗りつぶすという結果は破棄されます。

 ところが!ここでZバッファは思わぬ副作用を起こします。カメラから見て板ポリゴンが手前にあると判断された場合、その部分の青色は破棄されて、それまでレンダリングされてきた色が継続されて使われることになります。最初に板ポリゴンを描画したときには透明部分の背景には何も描画されていませんでしたね。ということは、板ポリゴンの後ろに透過して映ってほしいはずの青い球の色は、そこだけぽっかりと切り取られてしまうのです。図にすると次のようになってしまいます。

 まるで手前の板に色が付いているかのように切り取られてしまい、思い描くレンダリングにはなってくれません。つまり、透過処理をしたポリゴンと言うのは透明情報を持っているだけであって「ガラスのようなものでは決してない」のです。



C いたずら回避

 アルファ合成もZバッファも、お互い非常にすばらしい機能を提供してくれるのですが、それを合わせるととんでもないいたずらが発生してしまうことがわかりました。では、それを回避する方法は無いものでしょうか?実は、これは一筋縄ではいかないのです。

 例えば、青い球をレンダリングする時にZバッファを切るとうまく行きそうな気がしますが、前後関係を無視してしまい、また青い球のアルファ値は1なのでレンダリング結果は次のようになります。

「ちが〜〜う!」っと悶絶してしまいます。Zバッファを切るのはやっぱりだめなんです。ではZバッファを切らないとして、正しいレンダリング結果にするにはどうすればよいか?答え自体は実は非常に簡単です。「板ポリゴンを後から描画する」というのが正解。

 先に青い球を描画すると、そのZ値がバッファに格納されます。次に手前に板ポリゴンを置くとZ値が更新されて、またアルファブレンドによる色情報も正しく計算され、Aで示したような期待する色をスクリーンに塗ってくれます。つまり、透過情報のあるオブジェクトをレンダリングするときには、後ろのオブジェクトをすべてレンダリングしなければならないわけです。これがどれほど面倒なことかは想像にたやすいでしょう。普段Zバッファにやってもらっている前後関係の話を自分で行う。しかもカメラはめまぐるしく変化するので、ある瞬間で前後関係が逆転する事などいくらでもあります。ここまではさすがのDirect3Dもやってくれないのです(当たり前ですね(^-^;)。


D 多段階レンダリングで大分解決

 オブジェクトの前後関係を正しくすればレンダリングは正常に行われます。しかし、毎回それを検証するのは非常にコストがかかりそうです第一面倒でしかたありません。状況にもよりますが、非透過オブジェクトを先にレンダリングした後に透過オブジェクトをレンダリングすると決めてしまえば、それなりなレンダリング結果が期待できる事があります。それを実現するには「多段階レンダリング」の機構を確立させます。

 多段階レンダリングとは、レンダリングの順番をある程度制御する仕組みです。実装は簡単で、単に幾つかのリストにオブジェクトを登録するだけです。

 レンダリングはこのリストに従って行わせます。非透過のオブジェクトを先にレンダリングし、最後に透過オブジェクトをレンダリングするとそこそこうまくいきます。この仕組みはぜひとも実装するべきです。



E 透過オブジェクトの前後関係

 多段階レンダリングの弱点は透過オブジェクトの前後関係によって結果が変わってしまう事です。透過オブジェクトのアルファブレンドは貼り付けようとするオブジェクトのアルファ情報を元にするため、計算の結果が変わります。例えば背景を白(255)、貼り付ける2つのテクスチャTex1(128)およびTex2(64)、そのアルファ情報をAs1=0.7、As2=0.3として、前後を変えて貼り付けた時の色を計算して見ます。

Tex1→Tex2
REScolor1 = As1 * 128 + (1-As1) * 255 = 166.1
REScolor2 = As2 * 64 + (1-As2) * 166.1 = 135.47
Tex2→Tex1
REScolor1 = As2 * 64 + (1-As2) * 255 = 216.9
REScolor2 = As1 * 128 + (1-As1) * 216.9 = 109.87

実際こう違ってしまいます。最も正しい結果を得るには、最遠の透過オブジェクトからレンダリングしていくしかありません。

Dで紹介した多段階レンダリングの最後のリストに透過オブジェクトを登録すると決めておいて、そのリストだけを距離でソートすれば、Zバッファとアルファブレンドの問題は相当しっかりと解決する事になります(完璧ではないのですが・・・)。透過オブジェクトまでの距離は、その制御点をどこかに決めておいてカメラの位置とのベクトルの大きさから簡単に求められます。ただ、実際のオブジェクトはボリューム(体積)を持っていますから、その分前後関係を間違ってしまう事があります。完璧ではないというのはそういう理由です。しかし、以上のことをきちっと行えば、見た目に目立たないほどレンダリングは成功します。



 実際の実装は簡単ではありませんが可能です。後は・・・やる気ですよね(^-^;