映り込みと床と影と

・映り込み第二弾

 「映り込み・床編」から結構経ちましたが、このホームページの正式公開を前にして「これはいい加減やっておかないと」ということで、今回は再び床への映り込みです。

 

・鏡面反射と拡散反射

 前回、「影を作ると新たな問題が出てくる」と書きました。それは何なのか?

 その前に、影について扱うのでちょっと表現の変更を。床へ視線が反射する割合反射しない割合と書きましたが、これは鏡面反射率拡散反射率と言い替える事ができます。ちなみに英語の場合、鏡面反射はSpecular、拡散反射はDiffuseと言います。スペキュラーというのは鏡面反射ということですね。

拡散反射は、正確には光を反射しないのではなくあっちこっちに反射しています。光がそのまま反射されるのではなく拡散されるんですね。物体が見えるのは光が目に反射してきているためですが、拡散反射は光を拡散するので鏡面反射と違ってどこから見ても物体が見えます。

鏡面反射は単純にそのまま反射します。鏡は鏡面反射率が100%に近いです。
鏡面反射率は反射率と同じ意味です。3Dのレンダラなどは鏡面反射率と反射率を別々に設定しますが、あれは光源を反射する率と物体を反射する率の設定です。レンダラで言う鏡面反射率を上げても反射率が0なら周りの物は映り込まないし、反射率を上げても鏡面反射率が0なら光源は映り込まないです。

 物体が暗く見える時、その理由は拡散反射と鏡面反射で違ってきます。鏡面反射の場合は光源から来た光が目に当らなければ暗く見えます。拡散反射の場合はどの方向から見ても光が反射してくるので、光源から来る光の行く手を何かで遮らない限り暗く見えません。遮った時に、その光が遮られた部分だけが暗くなります。その、光が遮られて暗くなった部分がです。影は拡散反射だけに影響します。鏡面反射に影は関係しません(=鏡に影はできない)。

 

・映り込みと影の共存共栄(?)

 「新たな問題」の話に戻りますが、「影は拡散反射だけにできる」と書きました。そのため、影によって床だけではなく映り込んだ物体までが暗くなる、ことは無いということになります。
では、それをふまえた上で実際に映り込んだ物体・床・影・物体を書いてみましょう。


まず映り込んだ物体
暗くてよくわからん

  


そして床
映り込みが
幽霊みたいな

  


そして影
黒い紙のようだ

  
そして物体
一応人型

 うまくいってますね。影が真っ黒なのは、環境光0で光源が1つだけだからです。今回の話では真っ黒だろうと半透明の黒だろうと考え方は変わりません。
が、これは影と映り込みが重なっていないためにうまくいってるのであって、実際の映り込みを正しく表現したい場合は違う合成の順番をする必要があります。

 この例だと映り込み→床加算→影→物体という描画順ですが、床→影→映り込み加算→物体というのが正しい表現をするための描画の順番です。つまり、映り込みを書いて床を加算合成ではなく、床を書いてから物体を加算合成するのが正しいです。影と映り込みが重なるように、物体を2つ並べてみると、


正しい描画順。
この物体はとある
物の使いまわし

  


拡大。
見難いが、影にも
映り込みが見える
これが正しい表現

 


今までの描画順。
どこが誤ってる?
影が真っ黒だから
あまり目立ちません

  
拡大。
影で映り込みを
上書きして消して
しまっている

 今までのだと不都合がありますね。影が半透明の場合でも、映り込みまで暗くなるという問題があります。前回は影を作らない前提で、簡単な描画順を紹介しました。この画像のように、それほど気にならない場合もありますし。「最初から正しい描画順を教えろ!」という意見もあるでしょうが、正しい方法だと違う問題が出るんです。この画像はその問題をクリアしてますが。

 前述の描画順の中の「床加算」と「映り込み加算」は、加算合成でそれを書くという意味で書きました。床は四角ポリゴン一枚(とは限らないが)を描画するだけなので何も考えずに加算合成を使えるんですが、「映り込み加算」、つまり映り込んだ物体を加算合成で書くというのはちょっとそのままではちゃんとした描画にならない時があります。理由は「Zバッファ法は半透明に弱く、描画順に依存する」ということです(Zバッファの時に書きましたね)。

 「加算合成では描画順は関係無い」と書きましたが、あれはZバッファにZ値を書き込まないで、参照と比較だけという使い方だからです。Zバッファに書き込むような使い方の場合は、半透明だろうと加算合成だろうと描画順によって結果が変わります。普通半透明や加算合成はZバッファに書き込まないと思うんですが、今回は光などに使う訳ではなく物体を書くために使うので、書き込まないと問題が出ます。今回の問題は、「Zバッファに書き込まず、Zソートを使う」という手段も使えません。Zバッファに書き込まないなら、加算合成だから描画順は関係無いですし、問題は解消されないし。

 

 で、どんな問題が出るのか?
例として、「箱を手前から奥へ3つ描画」と「箱を奥から手前へ3つ描画」を比べてみましょう。加算合成でZバッファへの書き込みは有効にしておきます。まず前者(違いをわかりやすくするため、明度は下げてません)。


1個目。
黒に加算合成しても
普通に描画するのと同じ

  


2個目。
問題無し

  
3個目。
異常無しであります

 何とも無いですね。次に後者の「箱を奥から手前へ3つ描画」。


1個目。

  


2個目。
この時点ですでに
変なんですけど

  
3個目。
異常有りであります

 物体同士で加算されてしまってます。今回は床とだけ加算させるためにZバッファを使っているのに、描画順が奥から手前だと意味が無くなっています。これではこの後床と物体を書いた時に、物体と映り込んだ物体に違いが出てしまいます。何故描画順で結果が変わるかはわかりますよね。手前から奥の順番で書く場合は、奥の物を書く時に手前の物と重なった部分(手前の物に隠れて見えない部分)は描画しないからです。

 これがポリゴン単位で問題になってくるので、厄介になってきます。先の例で使った物体でも、足が先か手が先か胴体が先か……で、いろいろ結果が違ってきます。とりあえず、解決策でも考えてみますか。

(1)Zバッファ+Zソート

 まず最初に思い付く手段。描画順に依存するなら、ちゃんとした描画順にしたらいいだろうという素直な考え方。映り込む物体を書くためだけに、Zソートで時間を食うのもちょっと嫌かもしれませんが。

 難しいことはしません(面倒だけど)。映り込む全てのポリゴンの描画順を、手前から奥になるようにソートするだけです。

 物体(オブジェクト)毎にZソート→それぞれの物体のポリゴンをZソート(その前にCullingして、除去したポリゴンはソート対象に含めない)という感じでしょうか。まぁ普通ですね。物体毎にZソートだと物体がくっついていた時に問題になるし、Zソートでは大きさの違うポリゴンやポリゴンに刺さってるポリゴンで問題も出ると思いますが、まぁ許容範囲……だったらいいなぁ。
ちなみに、物体が凸型ならCulling(裏面除去)するだけで十分です。その物体のポリゴンをZソートする必要はなくなります。

(2)ポリゴン2度書き

 Zソートで問題になる事(刺さったポリゴンとか)も、この方法なら問題になりません。手前か奥かということもポリゴンの描画順も関係無くなります。そのためZソートも一切必要無くなります。Zバッファ+Zソートよりも時間食いそうですが……。特に物体が大きければ大きいほど。HALのポリゴン描画速度次第ですね。HELなら絶対Zソートの方が早いです。

 この方法は、映り込んだ物体を2度描画する事になります。しかも位置や大きさなど、頂点データは全く同じにします。そのため透視変換が一度で済むという利点もあります。それぞれの違いは、

・1度目:Zバッファ書き込みあり・Zテストあり・色書き込みなし(つまり見た目には変化無し。Zバッファには変化あり)
・2度目:Zバッファ書き込みなし(すでに同じの書かれてるし)・Zテストあり・色書き込みあり(この時やっと見える)

 こんな感じ。1度目にZバッファにだけポリゴンを書いて、2度目で画面にポリゴンを書きます。
こうすると何が違うか?では解説。次のような2つのポリゴンがあるとします。


青いポリゴンに緑の
ポリゴンが刺さってる
 
横から見た場合

 この画像のように書けるのが理想です。
この場合だと、Zバッファ+Zソートが使えません。緑を先に書こうが青を先に書こうが問題が出てしまうためです。


青が先。青と緑の
重なった部分が
加算されて水色に
 
緑が先。青より奥の
緑が残っている

 そこで、2度書きを使います。まず、Zバッファの書き込みはそのまま有効で、色の描画をしないようにします。色の描画をしないと言っても、どうしましょうか。α合成でSrcをZERO、DestをONEにしますか。スペキュラーはα合成が効かないので、SetRenderStateD3DRENDERSTATE_SPECULARENABLEをFALSEにしておきましょう。
Zバッファの中身を図示するとこうなります。


奥は左、手前なら
右という図。
青と緑の位置関係は
こうなってます
 
まず、青から
描画した場合
 
次に緑を描画。
青と緑を比較して
手前(この図だと右)
の方だけが残る
 
こっちは緑から
描画した場合
 
そして青。
結果は青→緑の
時と同じ

 色が着いているのは、見やすくわかりやすくするためです。実際のZバッファの中身にはもちろん色は着きません(Z値だけです)。
Zバッファを使うので、描画順に依存してませんね。どっちから書こうと結果は一緒です。この時点ではZバッファの内容は変わっていても、見た目には変化はありません。

 次に、2度目の描画。先にZバッファに書き込んだ理由がここでわかります。
その前にZテストについて。Zテストの比較関数は、2度目はEQUALを使う必要がありますが、LESSEQUAL(デフォルトの設定)でも代用できます。GREATEREQUALは駄目ですが。1度目の描画は普通に使うならLESSEQUALでしょう。ということで、1度目も2度目もLESSEQUALを使うのが普通だと思います。デフォルトのままで良いと。

 「Zバッファの使い方」の時はEQUALが使い物になりませんでしたが、今回は違います。あの話の線の例え話で言うと今回は始点と終点が完全に同じなので、どんな精度だろうと関係ありません。例えWバッファだろうと。

 話を戻して、早速色付きで描画します。その時に、ZバッファのZ値と描画しようとしているポリゴンのZ値が一致した部分だけが描画されます。


再びZバッファの
内容の図。1度目で
こうなってましたね
 
青を描画。青い線
が青のZ値、白は
描画される部分
 
次に緑を描画。
こっちも同様に重な
った白の部分を描画
 
結果、正しく
描画される

 なんだか無駄に長くなってしまいましたが、わかりました?この方法を使うと、オーバードロー(上書き)が一切無くなります。マルチパステクスチャでステージ1やそれ以降のステージでα合成を使ってもちゃんと描画されるのもこのためです。

 実はこの手法、Masaさんが使っている手法だったりします。「Zバッファを先に書く」という記述があったんですが、多分こういうやり方の事を言っているんだと思います。それが映り込みにも使えるので、今回解説しました。このやり方を知った時はもう目からウロコがボロッと。

 あと、PowerVR系もこの手法そっくりですね。先にZバッファを処理して、見えない部分は書かないという所が。

(3)作者の都合により

 映り込みの影との重ね合わせ程度の事に、わざわざZソートや2度書きを使わなくても簡単に処理する方法があります。しかもかかるコストは従来の影に上書きしてしまう描画順と大差無し!映り込みに使うならこれが最善の手だと思う手法。
が、ちょっとこれについては書きません、諸々の事情により。一応ヒント。

ヒント:「そういう手法がある」ということが最大のヒントでしょう。「そういう手法がある」と「無いかもしれない」では大違いです。

 自分が思い付いた解答は一つだけですが、他の人が思い付いた事はそれと同じとは限りません。思い付いたらぜひ掲示板などで教えてください。その時に自分の解答もお教えします。
やっぱり人の受け売りばっかりじゃなく、自分で考えないと。そんなに難しくないですしね。

 ちなみに、映り込み限定技。他のオーバードロー解決には使えないです。

(4)気にするな

 ある意味、究極の手段。一番お勧めかも。

 まぁそれほど目立たないなら従来の方法で良いんじゃないですか?そんなに映り込みに時間を割くのも勿体無いですしね。

 気にしないという手段には「影で上書きされても気にしない」と「加算合成でおかしくなっても気にしない」の2つがありますが、前者の方が良いでしょう。

 

 とりあえず4つ(3つ?2つ?)書いてみました。ひとまずこれで影については一段落ということで。

 

・以上、影編でした。

 あぁ、またこんなにも長くなってしまった。ここで一旦終わって、「加算合成を使わない映り込み」と「光源とモデル(物体)についての反転」は次回に持ち越しということにします。映り込みは次で絶対終わらせます。……多分。

 

 

written by Y.Ohde  e-mail : oode@alles.or.jp

back