半透明と半透明の合成

ずいぶん前に半透明の合成式に関してあれこれ考えていたことがある。
http://d.hatena.ne.jp/mirichi/20091207/p2
現在DXRubyのImage#drawはこの式を採用していて、半透明画像に対して半透明画像を描画した場合の結果は見た感じ自然になる。
ところが先日、DXRubyサイトの掲示板に「RenderTargetで描画した場合におかしい」という指摘があって、アップされた検証コードを見ると確かに直感的な結果ではない。
DirectX9のRenderStateの設定がそうなっているからそうなる。そのような設定にした記憶があって、そのように動いているので、まあ、そうなんだろうと思って仕様ですと回答してしまったのだが、よく考えてみるとやっぱりおかしい。
半透明の合成についてハードウェアでの描画で今まで気にならなかったのは、スクリーンにα値を持っていないからだ。RenderTargetはα値を持つ画像であり、そこにハードウェアで描画するのはそれまで無かった動きなのだ。


DirectX9の描画は(a)レンダリング対象に置く色を作る部分と、(b)作った色をレンダリング対象に置く部分という2段階に分かれている。
固定機能パイプラインという古い手法では、(a)の部分はハードウェアにパラメータを指定してやって、色を合成してもらう。いわゆるテクスチャステージというもので、どこから色をとってくるか、どのように合成するかを選んで設定する。DXRubyはWindow.draw_alphaの半透明指定に頂点色を使っていて、テクスチャステージでテクスチャと頂点色を合成することで半透明化している。
また(a)の部分は固定機能パイプラインのかわりにピクセルシェーダで置き換えることができる。DXRuby1.3系ではShaderクラスを使ってHLSLで記述することで、テクスチャステージをユーザー指定のプログラムに差し替えることができる。よって、Window.draw_exでalphaとshaderを同時に指定するとalphaが無視される。残念ながら現在の仕掛けではalphaを自動的に適用する手段は無い。
(b)はレンダリング対象の色と合成する処理で、レンダリング処理そのものだ。RenderTargetに半透明色が置いてある場合に、そこに半透明を描画しようとすると、合成式は(b)の処理が適用されることになる。この指定はDirectX9のRenderStateというもので行うわけだが、これが詳細で複雑かつ自由度が無い。完全にハードウェアでやっている部分だからしょうがない。


で、話はようやく最初につながって、DXRubyのImage#drawでやっているような計算式を(b)の合成式として適用する方法を調べていたのだ。
あれこれ実験してみてもなかなか思うようにならないのでWebで検索したらピンポイントに出てきた。
ゲームつくろ〜質問箱
http://marupeke296.com/cgi-bin/cbbs/cbbs.cgi?mode=al2&namber=1423&rev=&no=0&P=R&KLOG=2
そういえば前にも見たような記憶があるようなないような。
つまり、(b)の部分をImage#drawと同じ計算式にする手段は無いということだ。残念なことに。ピクセルシェーダを使ってゴリ押しで回避することは可能だ。
となると、現在のRenderTargetの描画をどうするか、というのが問題になる。できないまでも近い感じにするか、そのままにしておくか。
これについては少し考えてから結論を出す。