半透明を使わない半透明

・新たなる混合要素

 2回続いて3Dの技術解説な話じゃなかったので、今回こそは3D。でも2Dにも使えますけど。まぁ別にこのホームページは3Dが専門じゃないから、3Dの話ばっかりじゃなくてもいいんですけどね。

 「ブレンドファクター121」が好評だったので(2人しか確認ないけど)、今回は新たに思い付いた混合要素(ブレンドファクター)の組み合わせについてです。前回は「役に立たない」と書きましたが、意外と役に立ちそうです。

 そうそう、「加算合成vs半透明」についてですが、書くのをやめようと思ってます。それで書こうと思ってた事が他の話で(今回も含めて)ちょくちょく出てて、あと書けるのは「グローは加算合成よりも半透明の方がいいけど、一番良いのはその中間な使い方だ」ということぐらいになってしまっていたので。

 

・RGBA

 Direct3Dは、テクスチャにαビットを使うのが面倒です(それ以前にテクスチャのロードも面倒だけど)。αビットを使わないならビットマップからテクスチャサーフェイスにBitBltするだけで済むんですが、αビットがあるとテクスチャサーフェイスをLockしてαビットを直に書き込む必要があって、ちょっと面倒です。

 αビットを使うと、RGBのビット(色)の精度がαビットの精度の分だけ奪われます。16bitでピクセルフォーマットが565(5bit6bit5bit)なら、αビットに4ビット使えば4444となり、1、2ビット精度が落ちます。まぁそれほど汚くなる訳ではないですが、相性の悪い画像というのはあります。

 32bitの8888を使うという方法もありますが、食うテクスチャメモリは2倍……。1555というピクセルフォーマットもありますが、これはカラーキーの代わりにαテストを使う時に使うようなフォーマットですね。


 そこで、今回のテーマは半透明を使わないで半透明をやってしまおう。つまり、αビットを使わずにビット毎に透明度を指定する、という混合要素の組み合わせを使ってみよう、ということです。αビット付きテクスチャを使えるようにするのが面倒くさい、という方は是非!

 しかし、制限はあります。

 

・混合要素のおさらい

 ここで混合要素のRGBA値をおさらいしてみましょう。

D3DBLEND_ZERO
rgba( 0, 0, 0, 0 )

D3DBLEND_ONE
rgba( 1, 1, 1, 1 )

D3DBLEND_SRCCOLOR
rgba( Rs, Gs, Bs, As )

D3DBLEND_INVSRCCOLOR
rgba( 1-Rs, 1-Gs, 1-Bs, 1-As )

D3DBLEND_SRCALPHA
rgba( As, As, As, As )

D3DBLEND_INVSRCALPHA
rgba( 1-As, 1-As, 1-As, 1-As )

D3DBLEND_DESTALPHA
rgba( Ad, Ad, Ad, Ad )

D3DBLEND_INVDESTALPHA
rgba( 1-Ad, 1-Ad, 1-Ad, 1-Ad )

D3DBLEND_DESTCOLOR
rgba( Rd, Gd, Bd, Ad )

D3DBLEND_INVDESTCOLOR
rgba( 1-Rd, 1-Gd, 1-Bd, 1-Ad )

D3DBLEND_SRCALPHASAT
rgba( f, f, f, 1) f = min( As, 1-Ad )

 

 う、この表も久しぶりだ……。前回同様、説明ではD3DBLEND_という部分を省略させていただきます。
Adを使う混合要素は抜き、ということでしたね。未だDestのα要素の使い方がわからない……。

 計算式は、

rgba( Rs, Gs, Bs, As ) * Srcの混合要素 + rgba( Rd, Gd, Bd, Ad ) * Destの混合要素

こうなっとります。黄色の字も久々……。実際に計算するとこんな感じでした。

例) Src:SRCALPHA Dest:INVSRCALPHA  Srcの色:rgba( 1.0, 1.0, 0.0, 0.8 ) Destの色:rgb( 0.0, 0.5, 1.0 )
rgba( Rs, Gs, Bs, As ) * rgba( As, As, As, As ) + rgba( Rd, Gd, Bd, Ad ) * rgba( 1-As, 1-As, 1-As, 1-As )
= rgba( 1.0, 1.0, 0.0, 0.8 ) * rgba( 0.8, 0.8, 0.8, 0.8 ) + rgba( 0.0, 0.5, 1.0, 1.0 ) * rgba( 0.2, 0.2, 0.2, 0.2 )
= rgba( 0.8, 0.8, 0.0, 0.64 ) + rgba( 0.0, 0.1, 0.2, 0.04 )   この時点でα要素の役目は終わっている
= rgba( 0.8, 0.9, 0.2, 0.68 )  α要素は無視して描画(描画色はrgb( 0.8, 0.9, 0.2 ))

 


 では早速……の前に、先ほど書いた制限について。なに、簡単な事ですよ。色は白か黒しか使えません。

 なんだかこれだけで「駄目じゃん」という感じもしますが、気にせず行きます。

 

・白の半透明


 雲を書きたい。白の半透明で書くと、


Dest
いつもの。
 
SrcのRGBのビット
真っ白
 
SrcのRGBのビット
前に使った雲
 
描画結果
 
2つ重ねた場合

 こんな感じ。でもテクスチャにαビット使えるようにするのが面倒くさい……。よし、じゃあ代わりに加算合成で書いてみよう!


Dest
いつもの。
 
SrcのRGBのビット
前に使った雲
 
描画結果。
水色っぽくなってる
 
2つ重ねた場合。
重なる部分は真っ白

 加算合成だから、2つ重ねただけで真っ白になってしまいます。加えて、なんだか雲が水色になってますね。

これは、Destのグラデーションにrgb( 1.0, 0.0, 0.0 )からrgb( 1.0, 0.75, 0.0 )の色を使っているためです。rgb( 1.0, 0.75, 0.0 )の水色に、薄い白の部分、例えばrgb( 0.25, 0.25, 0.25 )の部分が加算された所はrgb( 1.0, 1.0, 0.25 )の水色になってしまいます。rgb( 1.0, 0.0, 0.0 )の部分は半透明と変わらないんですけどね。

 例として、rgb( 0.75, 0.5, 0.25 )に雲のテクスチャを加算合成してみると……。


Dest
海の色?
 
SrcのRGBのビット
 
描画結果。
見るからに水色

  水色ですね。加算合成は、Src側の色がそのまま出ない事がよくあります。これの例の他に、前にもやりましたが緑の背景に赤い炎を加算合成したら黄色になってしまった、とか。

 rgb( 1.0, 0.5, 0.0 )の色に不透明度50%の白rgba( 1.0, 1.0, 1.0, 0.5 )を描画すると、色はrgb( 1.0, 0.75, 0.5 )になります。前にも使ったグラフで、rgb( 0.75, 0.5, 0.0 )に不透明度25%の白rgba( 1.0, 1.0, 1.0, 0.25 )と明度25%の白rgb( 0.25, 0.25, 0.25 )を加算合成した時のそれぞれの色の変化を表してみましょう。


加算合成。あっという間に真っ白
  
半透明。下の数字は描画回数

 加算合成では雲っぽく見えない……。そこで、まず白の半透明を代用できる混合要素の組み合わせを考えてみましょう。と言っても、実は結果的に加算合成の混合要素を使うんですが。


 まず半透明のグラフの、描画前と描画後の色の差を見てみます。描画前が0.5で後が0.75なら、その差は0.25です。

 差を求めるのは、「この順で色を加算すると、白の半透明と同じ結果になる」ということを視覚的にわかるようにして、実現するための手がかりを得るためです。


こんな感じ

 このグラフを見て何か感じた事は無いですか?

 ちょっと、このグラフの数値を(不透明度25%だったので)4倍してみましょうか。


こうなりました

 これでわかるでしょうか。先程の白の半透明のグラフとこのグラフを並べてみましょう。


加算する色×4.0
  
これが白の半透明

 そう、白の半透明のグラフの上下をひっくり返した数値(1.0-n)と同じなんです。

 ということは、Destの混合要素は元の色そのままで(ONE)、SrcはDestの色を反転した色を使えばいいという事です。Destの色の反転は……INVDESTCOLORでしたね。

 不透明度は色の要素(Direct3DのデフォルトD3DBLEND_MODULATEでは頂点の色とテクスチャの乗算結果)で指定する事になります。色がrgb( 0.5, 0.5, 0.5 )なら、描画結果が不透明度50%の色と同じになります。

 色の明度が不透明度になるので、テクスチャの色(画像)は固定でも色の要素を変えれば不透明度を変える事ができます。つまり、テクスチャの明度が50%でも、色を明度50%にすることで不透明度を0.5*0.5=25%に変えられます。これがテクスチャの色だけに固定されてると、煙を描画する時にだんだん薄くなっていくという処理ができません。

 ちなみに、rgb( 0.0, 0.5, 1.0 )という色を使った場合、赤の成分はそのまま、緑は白の半透明50%の時と同じ、青は一回目の描画で1.0になります(白の半透明100%の時と同じ)。参考までに。

 まとめ。白の半透明は、SrcにINVDESTCOLOR、DestにONEを使い、Srcの色の明度が高いほど不透明度が高い(より白に近い)ということになりますね。

 

・黒の半透明

 では次の黒の半透明を考えます。

 輪郭がぼやけてる(半影がある)影や、真っ黒じゃなくて黒の半透明の影を使いたいという事はよくあるでしょう。そこで、αビット付きの黒のテクスチャを使う事になります。色のα要素を変えれば半透明の薄さも変える事ができますしね。

 でも、やっぱりαビット付きテクスチャを使えるようにするのは面倒くさい。よし、じゃあ減算合成で書いてみよう!……ってDirect3Dじゃ使えないですね。今回は手っ取り早く答えを書いてしまいましょう。

 A.乗算合成を使ってください

 乗算合成は「ブレンドファクター121」でやりましたけど、忘れた方もいるでしょう。SrcにZERO、DestにSRCCOLOR( Src * 0.0 + Dest * Src )か、SrcにDESTCOLOR、DestにZERO( Src * Dest + Dest * 0.0 )でしたね。


Dest

 
Src
白い所はそのまま、
黒い所は黒く
 
描画結果

 α値を指定する時と同じように、白い所が不透明度が高く(より黒く)なるようにするには、反転したSrcの色が使えればいいのでSrcにZERO、DestにINVSRCCOLORを設定してください。


Dest

 
Src
白い所はそのまま、
黒い所は黒く
 
描画結果

 いやー、これで黒の半透明も大丈夫ですね。黒の半透明は、なんだか白の場合に比べて楽に終わりましたね。

 ……と言いたい所ですが、これではまだ不十分です。何か忘れてませんか?これでは不透明度がテクスチャの色固定です。だんだん薄くするという事ができません。影に使うだけならあまり必要無いですけどね。でも黒煙や文字などに使うなら必要なので、黒の半透明でも薄くする方法について考えてみましょう。

 白の半透明の時のように、明るさを落として不透明度を下げようとすると、黒くなっていきます。明度を下げて乗算合成することになるので。乗算合成を使うという事は、明度を上げれば不透明度が低くなる(薄くなる)ということになります。が、明度を上げるのにも限界があります。Srcの色はテクスチャ×色(1.0が最大)なので、色の明度を上げてもテクスチャの色よりも明るくする事はできません。

 ということで、明度を上げて不透明度を下げる(薄くする)という方法は限界があります(見えなくなるまで薄くする事ができない)。ではどうするか?ならば逆に明度を下げて不透明度も下げれば良いか、もしくはα値を不透明度指定に使えば良いということです。そういう混合要素の組み合わせを考えてみましょう。前者は本当にできるのかわからないので、なんとなくできそうな後者のα値から考えてみます。すでにこの時点でどうすればできるかがわかってる方もいるでしょうが。

 α値を使うには、SRCALPHAまたはINVSRCALPHAを使うことになります。乗算合成はSrcかDestがZEROなので、ここに使えるかもしれませんね。試しに、前出の2つの乗算合成のやり方のうち、前者の片方のZEROSRCALPHAにしてみて、SrcにSRCALPHA、DestにSRCCOLORを設定してみます。


Dest

 
Srcの色
α値は0.5
 
描画結果
…何がしたいんだ?

 駄目だ、黒い部分を薄くしたいのにこれじゃあ影以外に白の明度50%を加算しただけだ。だからと言って、2つの乗算合成のやり方の後者を使ってSrcにDESTCOLOR、DestにSRCALPHAを設定しても、これでは「ブレンドファクター121」の一番最後にやった、Destを明るくする混合要素と同じになってしまいます。


Dest

 
Srcの色
α値は0.5
 
描画結果
影以外は明度1.5倍

 じゃあα値で不透明度指定はできないのだろうか?……できないんじゃないですかねぇ。できたら便利なんですけどね。α値を使った方法が見つからないので、次の明度を下げて不透明度も下げる混合要素を考えます。


 色の明度を下げて不透明度も下げる(黒を薄くする)ことができればあっさり解決ですけど、それができるんでしょうか?

 普通の乗算合成だと、色の明度を下げると不透明度が上がる(より黒くなる)……ならば、乗算合成でSrcの色を反転することで、色の明度を下げて不透明度も下げることができる?試しに、テクスチャを反転させてSrcにZERO、DestにINVSRCCOLORを使ってみようか。これが成功すれば、頂点の色を暗くする事で薄くする事ができる……。


Dest

 
Src
いつものを反転
 
描画結果
できた!!

 あら、あっさりと。 ……これ、どこかで見た事ありますね。そう、つい2画面分(サイズによる)前に見たやつじゃないですか!しかも混合要素も同じ!賢明な方なら、最初にこの画像を見た時点ですでにわかっていたかもしれませんね。

 とにかく、これなら頂点の色を暗くして、薄くする事もできます。いやーめでたしめでたし。

 ……と言いたい所ですが……って、嘘です。もう大丈夫です。これでできます。

 まとめ2。黒の半透明は、SrcにZERO、DestにINVSRCCOLORを使い、Srcの色の明度が高いほど不透明度が高い(より黒に近い)ということになりますね。

 

・余談

 余談ですけど、白の半透明はSrcにINVDESTCOLOR、DestにONEを使い、黒の半透明はSrcにZERO、DestにINVSRCCOLORを使う、つまり全く逆の混合要素を使うんですねぇ。

 余談その2。白の半透明は、”色反転”→”乗算合成”→”色反転”という方法を使ってやる事もできます。反転させて黒の半透明を使う、ということです。3度描画するという手間がかかりますが。

 余談その3。白と黒以外の半透明も考えました。白・黒以外にも赤・緑・青・黄色・紫・水色の、彩度がはっきりした(3原色の明度に1.0と0.0しか使わない)8色だけが使える混合要素の組み合わせが判明しました!それは白と黒の半透明の混合要素を合わせることでできます。SrcにINVDESTCOLOR、DestにINVSRCCOLORを使えば可能です。……最大の欠点は、不透明度100%固定という事です。それじゃあ普通の描画(SrcにONE、DestにZERO)と何ら変わらんじゃないか……。

 

 今回は、αビット付きテクスチャ対応ができないor面倒くさい方のために、新たな混合要素の組み合わせを説明致しました。なんだか、前と比べて文体が変わってしまいました。それにやたらと読み手に語り掛けてます。その上説明がまわりくどい。結論だけ書けば3行程度で終わってしまいそうな話なのに。でもそれだと淡白ですし。

 それにしても、勢いで書いたような文章になってしまってますね。

 文字に色を付けるのは少しだけ久々だったので、ちょっと楽しんでやりました。画像作りは相変わらず面倒くさいですけど、これがあると無いとでは結構違ってくるのでしょうがない。

 次は……なんでしょう。画角か、STGの作り方についてか、画面やキャラなどの振動についてか。どれも書くためには覚悟が要りそうなテーマですね……。やっぱ画角(視野角度)かな。STGは大した事書けないし、振動はサンプルデモ作らないといかんし。

 

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

back