月別アーカイブ: 2007年10月

Direct3D 10 ShaderModel 4.0 半透明ソート補足

元の記事エントリ
Direct3D 10 ShaderModel4.0 ピクセル単位の半透明ソートを行う

少々わかりにくかったかもしれませんので補足します。

実際のシーンのレンダリング時に、フレームバッファの各ピクセル単位で
描画履歴を全部保存しています。実際に書き込まれた色とZ深度、そして
そのピクセルに何回描いたか。
これを浮動少数 float4 ×2~4 に encode して詰め込み、最大 6回分
格納します。

最後に encode された情報を全部 decode して、順番にピクセルごとに
blend しなおすわけです。

これらの処理は各ピクセルごとに独立しています。回数判定もピクセル
単位なので、ポリゴンの重なりや Draw 命令の回数等とは関連しないし
影響を受けることもありません。
またこの処理は Hardware の Blend 機能だけで実現する必要があります。

blend image

32bit float には演算によって 8bit ×3個まで値が格納できます。
IEEE754 の単精度の仮数部は 23bit ですが実際は 24bit の情報量が
あるためです。
3回以上値が加算されてしまうと、デコードするときに不要な情報が混ざり
ノイズとなります。Blend 演算だけで必要なピクセルの選択も行わなければ
なりません。
これは浮動少数単精度の制限を使います。3回以上描き込まれたピクセルの
値は仮数部の精度の範囲外に押し込み、必要な値だけ残るようにします。

まとめると
・float に複数の値を正確に畳み込んで、あとから取り出す手段
・Blend 機能だけで値を encode する手段
・Blend 機能だけで履歴(回数)による値の選択と切捨てを行わせる手段
が必要となるわけです。

AlphaToCoverage との違いは次のとおりでしょうか。

・粒状感が出ない
・ブレンドアルゴリズムを任意に持てる (SrcAlpha+InvSrcAlpha 以外も可能)
・最後に Shader でブレンドするので、Hardware Blend 機能より高度な
 演算も実装できる

MRT を増やして追加パラメータを格納すれば、ソート後のブレンド時に
ピクセルごとに演算手法の選択もできるかもしれません。

またアルゴリズム的に大変応用が利くので、フレームバッファに情報を
蓄積していく技としてさまざまな手法に活用することができます。

Z剥がしの方法とは違い、シーンのレンダリングは一度で済むのでそこそこの
速度で動作しています。

欠点は
・現在最大6回までしか保持できない
・浮動少数演算の丸め対策で現状 7bit 精度になっている(6 layerのみ)
・保存する分だけメモリと 帯域を かなり 使う
・並べ替えなど、動的分岐の多い重いシェーダーが走る
などなど。

Direct3D 10 ShaderModel4.0 ピクセル単位の半透明ソートを行う

追記 2007/11/02: Direct3D 10 ShaderModel4.0 Stencil Routed A-Buffer とお詫び も見てください。

半透明描画時に、ピクセル単位でソートを行うシェーダーを考案しました。

wheelhandle_ss10 per pixel alpha sorting

とりあえずデモプログラムをご覧ください。(ソース付)
実行には DirectX10 環境 (Vista+GPU) と
DirectX SDK November 2007 の Runtime が必要です。

wheelhandle_ss10t.zip

[SPACE]   pause 一時停止
[3]       3 layer mode
[6]       6 layer mode
[S]       sort ありなし切り替え
[U]       + teapot 追加
[D]       - teapot 削除

ピクセル単位でソートしているので、順番が正しく保たれるだけでなく
半透明ポリゴンが交差しても矛盾しません。

こちらがソートありで

wheelhandle_ss10 per pixel alpha blending with sort

ソートなしにするとこうなります。

wheelhandle_ss10 per pixel alpha blending without sort

ソートしないと、teapot のオブジェクト間だけでなく、自分自身の取っ手や
ふたの重なりにも矛盾が生じていることがわかるかと思います。

●特徴

この手法の特徴は本当にソートしてブレンドしていることと、GPU だけで処理が
完結していることです。CPU は何もしていません。

またシーンのレンダリングは一回で済み、レイヤー単位でレンダリングしなおす
必要はありません。

●欠点

ピクセル単位で最大 layer 数(重なり回数)制限があります。
2種類のシェーダーを作成しています。それぞれ pixel 単位で 3枚までのものと、
6枚までです。

MultiRenderTarget かつ 128bit pixel を使っているため、それなりにメモリと
帯域を消費します。速度についてはまだ未評価です。

今の方法では格納できる値に制限があるため、ソート時に参照する Z の精度に
限界があります。14~16bit なので、交差面の境界が荒くなる可能性があります。

●ソートが必要な理由

Direct3D 10 では GeometryShader の導入によって、より自由度が高まりました。
ポリゴンの動的な追加削除が可能で、また StreamOutput を使ってジオメトリ
の更新も GPU だけでできるようになっています。

反面ジオメトリの決定や描画において CPU を介さないということは、CPU に
よる半透明ソートができなくなることも意味しています。

これを解決する手段として、ソートが不要な加算半透明だけ用いる方法があります。

また Direct3D では、MultiSample + AlphaToCoverage を使う方法が有望と
されています。これはアルファブレンドの代わりに高密度の点描を行い平均化
するものです。

今回のデモ ss10 のアルゴリズムは全く異なるアプローチで、本当にピクセルを
ソートしてしまいました。

● 3 layer シェーダー

・8bit のフルカラー画素値を用いることができます。
・半透明描画の重なりはピクセルごとに 3枚までです。
・透明度(Alpha)は各ピクセル単位で独立した値を持つことができます。
・3枚を超える重なりは、最後に描画された 3pixel が用いられます。Z 値を
 考慮した選択ではないので、重なりが多いと不定の色に見えることがあります。
・MRT が 2枚なので 6 layer より効率はよくなっています。
・ソート時の Z 精度が 16bit あるためで 6 layer より実用的です。

● 6 layer シェーダー

・R G B A 各チャンネルは 7bit カラーに制限されます。
・その代わり半透明描画の重なりはピクセルごとに 6枚まで対応可能です。
・透明度(Alpha)は各ピクセル単位で独立した値を持ちます。
・6枚を超える重なりは、最初に描画した 3pixel と最後に描画された 3pixel
 が用いられます。6枚を超えると 3layer 同様に不定の色に見えることがあります。
・MRT が 4枚必要で、それなりにメモリを帯域を消費します。
・ソート時の Z 値が 14bit なので、交差したポリゴンの境界などの精度が
 3layer に劣ります。

●原理とアイデア

MRT (MultiRenderTarget) と Blend 機能だけを使い、レンダリングした
ピクセルの蓄積を行っていることが最大の特徴です。

レンダリング時に、直前の結果を即時反映させることができるのは
RenderTarget か DepthStencil しかありません。これらの機能の組み合わせ
だけで、描画したピクセルの選択と判定、蓄積のためのエンコードを実現する
必要があります。いくら Shader に自由度があっても、同じパスで出力を
受け取ることができないからです。
なお今回は DepthStencil は使用していません。

Direct3D 10.0 (ShaderModel4.0) では、128bit 浮動少数フォーマット
R32G32B32A32_FLOAT による Blend が可能です。これを利用しています。

RTc= RTc + Pc * RTa
RTa= RTa * Pa

RTc = RenderTarget color
RTa = RenderTarget alpha
Pc = Input Pixel color
Pa = Input Pixel alpha (U:2^8, D:2^-8)

下記の表は、CPU で Pixel 値のシミュレーションを行ったものです。
0x10 から順番に 0x11, 0x12 ~ とカラー値を同じピクセルに重ねていきます。
pixel ~ が書き込まれたフレームバッファの画素値、decode ~ がデコード
して取り出したカラーを表しています。

step 0
pixel U: hex=41800000 float=16
pixel D: hex=41800000 float=16
decode U:  10
decode D:  10

step 1
pixel U: hex=45888000 float=4368
pixel D: hex=41808800 float=16.0664
decode U:  11 10
decode D:  11 10

step 2
pixel U: hex=49908880 float=1.18402e+006
pixel D: hex=41808890 float=16.0667
decode U:  12 11 10
decode D:  12 11 10

step 3
pixel U: hex=4d989088 float=3.19951e+008
pixel D: hex=41808891 float=16.0667
decode U:  13 12 11 00
decode D:  20 12 11 10

step 4
pixel U: hex=51a09891 float=8.62193e+010
pixel D: hex=41808891 float=16.0667
decode U:  14 13 12 20 00
decode D:  00 20 12 11 10

step 5
pixel U: hex=55a8a099 float=2.3176e+013
pixel D: hex=41808891 float=16.0667
decode U:  15 14 13 20 00 00
decode D:  00 00 20 12 11 10

step 6
pixel U: hex=59b0a8a1 float=6.21563e+015
pixel D: hex=41808891 float=16.0667
decode U:  16 15 14 20 00 00 00
decode D:  00 00 00 20 12 11 10

step 7
pixel U: hex=5db8b0a9 float=1.66354e+018
pixel D: hex=41808891 float=16.0667
decode U:  17 16 15 20 00 00 00 00
decode D:  00 00 00 00 20 12 11 10

デコード結果を見ると、Blend 機能を使った積和演算のみでも、最初の 3値と
最後の 3値が正しく保持されていることがわかります。

アルゴリズム U と D のどちらかを使えば 3 layer が実現可能で、両方組み
合わせることで 6layer が実現できます。
このように、制限があるのは演算アルゴリズム上の問題なので、MRT 数を
増やしたとしても単純にレイヤー数が増えるわけではありません。

画素の割り当て下記のとおり。

Algorithm-U / MRT0,1

MRT-0      X    Y    Z   W
-----------------------------
Pixel0-2   R    G    B  ExpU
-----------------------------

MRT-1      X    Y    Z   W
-----------------------------
Pixel0-2   ZH   ZL   A  ExpU
-----------------------------

Algorithm-D / MRT2,3

MRT-2      X    Y    Z   W
-----------------------------
Pixel3-5   R    G    B  ExpD
-----------------------------

MRT-3      X    Y    Z   W
-----------------------------
Pixel3-5   ZH   ZL   A  ExpD
-----------------------------

●浮動少数演算の丸め問題

Blend の演算機能と浮動小数値の特性を使って、ピクセルの蓄積と適切な
値の選択を行っています。このとき非常に厄介な問題が、浮動少数演算時の
丸め処理です。

入力 3 値まではこの問題が発生しないため、当初実現できたのは 3 layer
のみでした。3 値を越えると桁あふれによって追い出された bit が丸め込まれ、
上位 bit に影響を与える可能性があります。

例えば

Algorithm-U: C B A

と入力された段階で D を入力すると A が切り捨てられます。このとき A の
値の重さによって上位 D C B に影響が出ます。例えばすべて A B C D が
すべて 0xff だった場合、あふれた 0xff の繰り上がりの 1 によって
上位すべて 0 になってしまいます。影響は無視できません。

この問題を回避するために、失われたピクセル値が何であったのか推測する
必要があります。もし丸め込まれていたら decode 時に減算すればいいわけです。

捨てられたピクセルは情報として保持されていませんが、同時に Algorithm-D
を用いることによって、6 値までなら失われた値を相互に補完することが
できそうです。

step3
Algorithm-U: C B A
Algorithm-D: C B A

step4
Algorithm-U: D C B
Algorithm-D: C B A

step5
Algorithm-U: E D C
Algorithm-D: C B A

step6
Algorithm-U: F E D
Algorithm-D: C B A

この場合、step5 なら U の捨てられた値は D の B で参照でき、step6 なら
D の C で参照することができます。

同じように Algorithm-D でも入力値を捨てたことによるまるめこみが入ります
が、Algorithm-U から求めることができます。

一見うまくいきそうですが、双方同時にまるめこみが発生した場合に残念ながら
適切な値をとることができませんでした。おそらく実現できるのは 5 layer
までと考えられます。U と D で反転した値を入力しておくことで、同値の
ずれを割り出すことができます。

この問題を解決ができなかったので、6 layer シェーダーでは値を 7bit に
小さくすることで、とりあえず桁上がりが発生しないようにしています。
8bit フルに格納できなかったのが残念です。

●今後に向けて

Direct3D 10.1 / ShaderModel4.1 では、Blend 機能が大幅に拡張されます。
特に MRT 単位で独立した Blend パラメータを設定でき、また UNORM/SNORM
フォーマットでの Blend も可能となります。
これにより、今よりも実装も実現もずっと楽になると考えられます。
layer を増やせるかもしれません。

とりあえず実現が目標だったので・・速度最適化などはほとんど行っていません。
特に並べ替えの実装はあまりに手抜きなので、まだまだ改善の余地は多く
残されていると思います。

シミュレーションのあと実際にシェーダーとして実装したところ、予想と異なる
挙動がありました。CPU と GPU による浮動少数演算の違いだと考えられます。
ss10 ではごまかしが入っているので・・改善しないと。

layer オーバー時に、Stencil を使って最前面の pixel だけ選択できないか
も考えていました。Stencil Test の結果と Depth Test の結果の組み合わせ
によって Pixel を捨てるかどうか自由に決められればできそうです。
現状は Stencil Test の結果だけで決まり、Depth Test との組み合わせは
Stencil の更新方法の選択のみなのでうまくいきませんでした。

DirectX SDK November 2007 Gather と 新DDS フォーマット

今回の DirectX SDK November 2007 では、ReleaseNote を見ただけでは
Graphics 周りだとほど大きな更新がなかったように見えます。

ところがこの更新は、一般の開発者にとっては非常に大きな、とても
意味のあるものとなりそうです。

マニュアルが大幅に刷新されています。

これまでいくつか指摘してきたマニュアルの不備や不具合なども修正され、
説明不足だった点もかなり手が入っているようです。
Direct3D 10.1 など開発や更新自体がひと段落し、マニュアルや解説の
方にも手が回るようなってきたのでしょうか。
と思ったら、build 番号もかなり増えています。
DirectX SDK Version 一覧

マニュアルの更新によって、Direct3D 10.1 の機能もかなり見えてきました。
Shader の説明でも、4_1 で追加された機能がわかりやすく表記されています。

D3D 10.1 / ShaderModel 4.1 の新機能といえば TextureCubeArray がありますが
他に Texture のサンプリング命令として Gather() があるようです。
これは一度のサンプリングで、テクスチャの 4ピクセルの値をまとめて参照
することができます。4 つのピクセルの R だけが、一度に xyzw として
読み出されるわけです。

ATI の RADEON X1900 系の機能に精通している方ならすぐに思い当たる
ものがあるかと思います。
かつて ATI が、NVIDIA ShadowMap に対抗して用意した ShadowBuffer 用の
サンプリングアクセラレータ機能が、まさに Gather() そのものでしょう。
4pixel 同時にサンプリングしてもハード的にコストがかからないので、
これはかなりよい機能だと思います。

実はこちらのエントリで DXGI_FORMAT の詳細を調べたときに、
機能フラグとして D3D10_FORMAT_SUPPORT_SHADER_GATHER が
存在していることを発見しました。このときはマニュアルやヘッダを検索
しても他に GATHER は出てこないので意味がわかりませんでした。

おそらく、このフラグがついているフォーマットだけが Gather() 命令で
利用可能なのだと思われます。(10.0 では無し)
Direct3D 10 DXGI_FORMAT の機能対応一覧
Direct3D 10.0 DXGI_FORMAT_SUPPORT

こちらで調べているような アセンブラ命令の定義の意味も、
マニュアルに乗るようになりました。

他に個人的に大きなトピックとして、DDS の詳細項目の追加があります。
DDS は比較的古いフォーマットです。Direct3D8 で FourCC が拡張されて
Direct3D9 のフォーマットも全部格納できるようになりました。
DDS Texture format memo

ですが、Direct3D10 の全フォーマットを格納することができませんでした。
今回 Direct3D 10 のフォーマットに対応すべく拡張方法が定義されました。

基本的には先頭の DDSURFACE ヘッダ 128 byte は互換性があります。
その直後に 20byte の DDS_HEADER_DXT10 ヘッダが追加された形となるようです。

つまりヘッダは合計 148byte 、少々中途半端です。この追加ヘッダの存在を
識別する方法がまだ不明です。また DDS_HEADER のうち未使用となった
dwCaps がちょうど 20byte あるので、ここに DDS_HEADER_DXT10 が収まるの
だったらしっくりきます。
ヘッダの種別は dwMagic で区別するようにみえますが、この具体的な値が
書かれていないようです。

とにかくこれでようやく TextureArray も dds ファイルとして格納できる
ようになりそうです。
付属の dxtex.exe (DirectX Texture Tool) はまだ D3D10 フォーマットに
対応していないようです。

EMOBILE 経由で PSP ブラウザ

ZEROProxy を使うと ZERO3 や EM・ONE が iPod touch のモデムになる
そうです。
伊勢的新常識 ZEROProxy
iPod touch は非常に薄くて小さいし、よさそうですね。

iPod touch と ZERO3/EM・ONE の間は無線LAN でつながります。
EM・ONE を WM6 にアップグレードする前に一度 WindowsXP のノートで
試してみました。

(1) EM・ONE (S01SH v1.03a) に ZEROProxy を入れる
(2) 無線LAN のアドホック接続で、EM・ONE と NotePC を接続する

Bluetooth で EM・ONE をモデム化するのと似ています。
だけど 無線LAN 方が Bluetooth より速いので、Bluetooth 接続よりも
速度が出ていました。
emobile の通信速度も Bluetooth より速いので、うまく安定すれば
ZEROProxy + アドホック接続は、ノートPC ユーザーにもお勧めな通信手段に
なるかもしれません。
(直後に EM・ONE のアップグレード発送+ノートPCを手放したので
実用レベルでは試してないです)

残念ながらブラウザでアクセスポイント経由の接続しかサポートして
いない PSP では、直接 ZERO3/EM・ONE につながらないと思われます。
今後のファームでアドホックのインターネット接続をサポートしてくれれば、
たぶん使えるようになるでしょう。

直結が無理でも、無線LAN のアクセスポイント経由だと使えます。

emone psp

無線LAN AP 経由で EM・ONE+ZEROProxy とつないで、
PSP のブラウザでインターネットにアクセスできました。

ここでは持ち歩ける小型の 無線LAN アクセスポイント を使っています。
Logitec SkyLink LAN-PWG/APR
USB 電源で動くので、外付けのバッテリーをつないでいます。
EM・ONE と上のセットをかばんの中に入れておけば、電車の中でも
ゲームをしているふりをしつつネットを見たりできると思います。

LAN-PGW/APR はルータですが、ZEROProxy を使うのでアクセスポイント
モードで使っています。

Proxy 経由なので、残念ながらゲームでのネット接続はおそらくできない
でしょう。リモートプレイも TCP 9293 が必要らしいので、この構成では
つながりませんでした。
リモートプレイをする(インターネット経由)

以下設定手順の詳細

●ルータ/アクセスポイントの設定

(1) アクセスポイント(AP)モードにします
(2) 無線LANの基本的な設定を行います。
  ・SSID の設定
  ・セキュリティの設定(WEP等)
(3) DHCPサーバーが有効になっていたら off にします。

ルータの初期 IP アドレスは仮に 192.168.1.200 としておきます。

●EM・ONE (WM端末) の設定

(1) ZEROProxy をインストールします。

(2) 無線LAN を ON にしておきます。

(3) 無線LAN の設定を行います。
  ・設定→接続→ネットワークカード→ワイヤレス
  ・ワイヤレスネットワークの構成から、新しい設定の追加
  ・ネットワーク名 に SSID を入れる
  ・接続先 社内ネットワーク設定
  ・ad-hoc にはチェックを入れない
  ・次へ
  ・認証: 任意(ルータに設定したものと合わせる)
  ・次へ
  ・ネットワーク認証の構成: IEEE 802.1x~のチェックをはずす

(4) IP アドレスを登録します
  ・設定→接続→ネットワークカード→ネットワークアダプタ
  ・社内ネットワーク設定
  ・内蔵ワイヤレスLAN を選択
  ・「指定したIPアドレスを使用する」
  ・IPアドレス: 192.168.1.2 (必要に応じてアレンジしてください)
  ・サブネットマスク: 255.255.255.0

EM・ONEα(S01SH2) で実験していますが、ZERO3 等他の WindowsMobile
端末でもほぼ同様だと思います。

●PSP の設定

(1) 設定→ネットワーク設定→インフラストラクチャーモード

(2) [新しい接続の作成] → 手動で入力する

(3) 無線LAN の設定をします
  ・SSID: ルータに設定したもの
  ・ワイヤレスLAN セキュリティ設定 : ルータの設定に合わせる
  ・アドレス設定: カスタム
  ・IPアドレス設定: 手動
  ・IPアドレス: 192.168.1.3
  ・サブネットマスク: 255.255.255.0
  ・デフォルトルータ: 192.168.1.2
  ・プライマリDNS: 192.168.1.2
  ・プロキシサーバー: 使用する
  ・プロキシサーバー アドレス: 192.168.1.2
  ・プロキシサーバー ポート番号: 8080
  ・インターネットブラウザ: 起動しない
  ・接続名: 任意

●接続実験

(1) ルータの電源をON
(2) ZEROProxy を起動する
(3) EM・ONE でダイヤルアップ接続する (HSDPA)
(4) EM・ONE で無線LANを ON、ルータ(アクセスポイント)に接続する
(5) PSP でブラウザを起動する。上で設定した接続を選ぶ。

関連エントリ
PS3/PSP emobile EM・ONE 経由でリモートプレイ

追記: この構成の接続は実用性は皆無です。機材があったので当時試してみただけですが、決してお勧めしません。ゲームはできずブラウザだけで速度も遅く、EM・ONE そのまま使ったほうが速いです。

2008/12/26 追記: WMWifiRouter を使った接続設定はこちらです。 WMWifiRouter の場合ブラウザ以外も使用できます。