月別アーカイブ: 2009年8月

OpenGL ES 2.0 Emulator

OpenGL ES 2.0 は必要な機能のみ用意されているため非常にわかりやすくなっています。
レガシーだけど互換性のために残っている API が、ほとんど無いからです。
定期的に仕様をゼロから作り直す DirectX に似ているといえるかもしれません。

複数の描画方法が残っていると、どれを使えばよいのかわからなくなることがあります。
OpenGLES 2.0 では悩むことが少なくて済みました。
モバイル向けサブセットであり仕様を割り切っていること、ShaderModel 3.0 相当の
シェーダーが利用可能なことから、OpenGL の入門としても参考になりました。

PC 上でも OpenGL ES 2.0 Emulator を使えば全く同じ API を使うことが出来ます。

AMD Developer Central GPU Tools
Imagination PowerVR Insider

この両者、初期化がわずかに異なるだけで dll 名も一緒。全く同じように使えるようです。
コードを共有化する場合、NativeWindowType でどちらを使っているか判断できます。

#include  
#include  
#include  

#ifdef	NativeWindowType
# define  USE_GLES20_AMD   1
#else
# define  USE_GLES20_PVR   1
# include 
#endif

初期化の違いもこれだけです。

#if USE_GLES20_AMD
static const EGLint attrib[]= {
    EGL_RED_SIZE,       5,
    EGL_GREEN_SIZE,     6,
    EGL_BLUE_SIZE,      5,
    EGL_ALPHA_SIZE,     0,
    EGL_DEPTH_SIZE,     16,
    EGL_STENCIL_SIZE,   0,
    EGL_SAMPLES,        4,
    EGL_NONE
};
egl_Display= eglGetDisplay( EGL_DEFAULT_DISPLAY );
#else // PVR
static const EGLint attrib[]= {
    EGL_LEVEL,                  0,
    EGL_SURFACE_TYPE,           EGL_WINDOW_BIT,
    EGL_RENDERABLE_TYPE,        EGL_OPENGL_ES2_BIT,
    EGL_NATIVE_RENDERABLE,      EGL_FALSE,
    EGL_DEPTH_SIZE,             EGL_DONT_CARE,
    EGL_NONE
};
hDC= GetDC( hWnd );
egl_Display= eglGetDisplay( hDC );
#endif

EGLint  majorv= 0;
EGLint  minorv= 0;
eglInitialize( egl_Display, &majorv, &minorv );

EGLint	configs= 0;
eglGetConfigs( egl_Display, NULL, 0, &configs );
eglChooseConfig( egl_Display, attrib, &egl_Config, 1, &configs );
egl_Surface= eglCreateWindowSurface( egl_Display, egl_Config, hWnd, NULL );
static const EGLint   attrlist[]= {
    EGL_CONTEXT_CLIENT_VERSION,
    2,
    EGL_NONE
};
egl_Context= eglCreateContext( egl_Display, egl_Config, EGL_NO_CONTEXT, attrlist );
eglMakeCurrent( egl_Display, egl_Surface, egl_Surface, egl_Context );

PC では本来の OpenGL が使えるので、3D を描画する目的でわざわざ Emulator を使う
必要はないです。元々 GL ES 自体がサブセットであり OpenGL でほぼ同じ API が使えます。
今回いろいろ試しているのも手持ちの描画エンジンを様々なプラットフォームで
そのまま動くようにしているから。

GLSL に mat4x3 が無いとかバイナリ形式の扱いとか多少機能不足もありますが
その辺は何とでもなります。Direct3D Mobile がなかなか更新されないので、
Mobile 向け API は GL ES 2.0 で統一してくれた方が良いですね。

Direct3D11 (DirectX11) に慣れすぎたせいで、ついリソース生成をサブスレッドで
実行してしまいます。リソースの生成は描画と同じメインスレッドで無いと失敗します。

関連エントリ
OpenGL ES 2.0
OpenGLES2.0 DDS テクスチャを読み込む
OpenGLES2.0 Direct3D とのフォーマット変換
OpenGLES 2.0 頂点フォーマットの管理
OpenGLES2.0 の頂点
OpenGLES2.0 D3D座標系
OpenGLES2.0 シェーダー管理

OpenGL ES 2.0

MID SmartQ5 が気になったので、3D 描画性能などを調べていたら下記のページに
いくつかヒントがあることに気がつきました。

Khronos Conformant Products

Khronos のトップより右上の View all Conformatn Products。
OpenGL ES 2.0 に対応しているグラフィックスコアがだいたいわかります。
2.0 に対応しているのはこのあたり。

・Imagination Technologies PowerVR SGX 535
・Samsung Electronics FIMG-3DSE v1.5
・ARM Mali-400 MP
・Imagination Technologies PowerVR SGX 520
・NVIDIA Corporation Tegra
・Vivante Corporation
・ARM Mali 200
・Imagination Technologies PowerVR SGX530
・Advanced Micro Devices AMD Z430?

iPhone が PowerVR SGX 535 であることもわかります。
下の方には Cell の記載も。JETSTREAM-A は Cell を使ったレンダラでしょうか。

PowerVR SGX は Unified Shader で GPGPU や ShaderModel4.0 (DirectX10)
も見据えた設計になってますが、Mali 400 等は Vertex/Pixel が独立した
ShaderModel 3.0 系のデザインとなってるようです。

ARM社がグラフィックス描画処理コア「Mali-400 MP」を発表,画素処理部の数を可変に

Wikipedia NVIDIA Tegra によると Tegra も GeForce6 系らしいのでおそらく
ShaderModel 3.0 でしょう。
GLES 2.0 自体 3.0 世代 (DirectX9) なので十分ですが、ハード機能的には世代が
混在している状態です。実際の速度は機能よりシェーダーユニットの個数で決まるため
判断は難しくなっています。

GLES 2.0 世代はおおざっぱに 14M~35M triangle/sec, 275M~1G pixel/sec あたりと
仮定すると 275MHz x Pixel Units または 20cycle/vertex くらいの計算でしょうか。

Freescale i.MX31 は PowerVR MBX Lite (Wikipedia PowerVR) が使われていました。
i.MX51 は GLES 2.0 対応なので別物です。どうやら AMD Z430 らしい。
(Google i.mx51+z430)

Direct3D Mobile と T-01A の Snapdragon

Toshiba T-01A の d3dmcaps を調べてもらいました。
wm_gamer さん協力ありがとうございました。

Direct3D Mobile DeviceCaps 一覧

Driver="YAMATO_D3DM", Description="AMD Yamato D3D Mobile Driver"
   yes D3DMDEVCAPS_HWTRANSFORMANDLIGHT
   yes D3DMDEVCAPS_HWRASTERIZATION

上記の通り 3D アクセラレータが有効となっています。AMD (ATI) core です。
ドライバはこれまで見たことがない YAMATO_D3DM というもの。

T-01A は Qualcomm の Snapdragon を搭載しています。
Snapdragon は Cortex-A8 同様、新しい ARMv7 世代の ARM core を採用しており、
さらに 1GHz で動作するのが特徴です。

Impress Qualcommの携帯電話向けプロセッサ「Scorpion」~独自実装で1GHz駆動を実現

上の記事によると、かつての StrongARM のように独自の実装であることがわかります。
Cortex-A8 と同じくアウトオブオーダーのスーパースカラで、レジスタリネーミングなど
より積極的な改良が行われているようです。
嬉しいのは VFP 対応なこと。WindowsMobile も、iPhone のように当たり前に
浮動小数演算を使えるようになって欲しいところです。

3D 性能も強化されており、上の d3dmcaps を見ても機能の対応度がこれまでの
MSM7201A (Touch Diamond 他) とは全然違います。

OpenGLES2.0 対応とのことなので、ハードウエア的にはシェーダー世代のはず。
Direct3D8 相当で固定パイプしかない Direct3D Mobile (D3DM) は手狭なのでしょう。
現状で性能を出し切るにはおそらく OpenGLES 2.0 が必要です。

Mobile の世界では Direct3D , OpenGL の立場が逆転しており、Direct3D Mobile の
仕様はかなり遅れているといわざるを得ません。

d3dmcpas のリストを見て特筆すべき点は、出力対応フレームバッファとして 32bit
D3DMFMT_A8R8G8B8 が列挙されていることです。
WindowsMobile は 16bit カラーしか対応しておらず、これまでも最大発色数は 65536色 の
ままでした。当たり前のようにフルカラー対応機種が出ている携帯電話と比べると、だいぶ見劣り
していたことになります。

T-01A / Snapdragon がフルスクリーン時のフルカラー出力に対応しているのだとしたら
私が知る限りでは初だと思います。

関連エントリ
HTC Touch Diamond で Direct3DMobile その(10) d3dmclock v1.10 3Dクロック 更新
HTC S11HT の 3dmcaps

Direct3D Compute Shader 対応の GeForce ドライバ

NVIDIA の新しいドライバ 190.62 がコンピュートシェーダに対応したらしいので試しました。
Windows7 RC/RTM どちらでも Compute Shader 4.0 動いてます。

(Direct3D11)
*HARDWARE  = 10.0 (a000)
 feature threading  DriverConcurrentCreates=1
 feature threading  DriverCommandLists=0
 feature d3d10_x  ComputeShaders_Plus_RawAndStructuredBuffers_Via_Shader_4_x=1
 feature doubles  DoublePrecisionFloatShaderOps=0

関連エントリ
Direct3D11/DirectX11 GeForce の ComputeShader とドライバ
Direct3D11/DirectX11 ComputeShader 4.0 を使う
Direct3D11/DirectX11 (6) D3D11 の ComputeShader を使ってみる

Direct3D ファントムダストと破壊の穴

ファントムダスト Phantomdust というゲームがありました。
今更ですが…忘れないうちにいくつか書いておきます。

マップの破壊表現は複数の手法の組み合わせで出来ています。
オブジェクト、パーティクル、破裂シェーダー、破壊のへこみ穴 等。
今回説明するのは「穴」の表現です。

攻撃やダメージ等によって、地形モデルのどの位置にでも削れたような穴があきます。
地面だけでなく、壁でも天井でも同様に破壊の痕跡が残ります。
このへこんだ形状は別モデルとして用意してあり、ステンシルマスクを用いて背景の
描画時に合成しています。
戦っているうちに地形の至る所が穴だらけになるわけです。

例えば下記リンク先のイレースシェルの地面がそうです。
いくつも穴がありますが、もともとは平らな地面のモデルデータです。

famitsu.com
Impress GAME Watch

●特徴

メリット

・デカールと違い立体的に見えます。
・背景モデルデータを書き換えることなく形状を変化出来ます。
・立体的なデータなのでヒット位置を厳密に割り出さなくても描画できます。
・ノーマルマップの背景に対して低コストで適用できます。

デメリット

実装時に様々な制限が生じます。問題をどのように解決したのかは後述します。
欠点というか実装できなかったのはコリジョンの追従です。コリジョンの変更はコストが
高く、ゲーム内容に関わるため通信同期も必要でした。残念ながら見送りました。

●この方法を用いた理由

破壊表現は当初からの課題でした。
プロトタイプの段階では通常のマテリアルを使っていたので、レイヤテクスチャ+
メッシュを分割+動的に頂点を書き換えていました。
問題となったのはノーマルマップです。
Xbox 1 の当時はまだ珍しかったのですが、最終的に背景もキャラも全面にノーマルマップを
使っています。これはハイポリのディテールを少ないポリゴンで再現可能な手法なので、
結果として頂点数を減らすように方針転換しました。よって

・頂点を動かすだけのポリゴンの細分化が出来ない
  頂点を減らせるのがメリットだし、TS の容量も計算も減らしたい
・変形した形状の Tangent Space の再計算はコストがかかる
・PixelShader の 8+4 stage をノーマルマップで使い切っている
  レイヤを増やすとマルチパスになる

●破壊穴モデルの形状

実際は平べったい形状ですが便宜上球と思って構いません。
穴の中のへこみを表現するモデルなので、面が裏返った内側を向いたデータです。

破壊の穴と地面の交差を判定するボリュームモデルと、実際に穴の中を描画する
描画モデルの 2つが必要となります。

・交差判定のためのボリュームモデル
・穴の中を描画するモデルデータ

描画モデルのマテリアルが単純かつ軽量なものだったので、この両者を兼用しています。
つまり描画モデルをそのまま断面の判定にも使用しています。

●断面の生成

シャドウボリュームと同じです。
テクスチャをフェッチしない専用の軽量シェーダーを割り当てて、ボリューム形状を
ステンシルバッファに 2回書きます。

(1) depth test かつ 表面、stencil +1
(2) depth test かつ 裏面、stencil -1

裏面と面面の差分が地面との交差面となります。上記の設定だと stencil が 0以外で
交差面です。
この条件を元に、同じ位置に描画モデルをレンダリングすれば合成ができます。

余談ですが、両面ステンシル機能がある GPU なら一度の描画で差分が求まります。
両面ステンシルが無くても両面マテリアルで代用可能で、例えばフレームバッファの
Alpha (DestAlpha) に加算で書き込むと一回で生成できます。
このとき描画頂点数は減りますがピクセル負荷は変わりません。

多くの GPU は Depth + Stencil の書き込みに特化したモードがあり、フィルレート
が倍になるように最適化されています。なので DestAlpha を使ってもあまりメリット
はありませんでした。
それにこのゲームは DestAlpha をすでに別の用途で利用していました。

●形状の変更

このゲームは表現上ステンシルバッファを多用しています。
8bit しかない stencil を bitmask して複数使い分けているところもあります。

・影
・オーラ
・地面や建物の破壊穴

もしゲーム画面か古い CGWORLD を見ることが出来るなら、穴の中にも形状に沿って影が
落ちていることがわかると思います。
でも描画しているモデルデータは平らなままです。

背景の影もシャドウボリュームを使っているのは、このように破壊によって動的に
地形が変わる可能性があるため。テクスチャや頂点への焼き込みだと、地形の変化に
対応できなくなります。

●負荷の相殺

破壊が進むと描画の負荷が増えて重くなりそうに見えますが実はそうでもありません。

このゲームは背景もキャラもすべてノーマルマップを使った重いシェーダーを適用
しています。最初から背景のピクセルが一番重い前提で負荷を計算しているので、
地形の描画面積を減らす処理は描画負荷の軽減につながります。

ノーマルマップを適用していない破壊の穴モデルは、数が増えれば増えるほど地形の
描画負荷を軽くすることになります。
ステンシルバッファへのレンダリングが増えますが相殺して軽くなります。
(でも頂点は軽くなりません。この判断は今思うと微妙だったかもしれません。)

同じようにキャラクタの描画負荷も相殺するのでアップになっても速度は変わりません。
半透明のエフェクトはまずいです。

●矛盾の解決

実際に組み込んでみると単純な表現でもさまざまな問題が生じます。
他の描画や表現と共存が必要だからで、1つ 1つ対策を施していきます。

・穴が開くと困る場所

破壊モデル合成時に、特殊な形状や半透明など矛盾が生じる場所があります。
あらかじめマテリアルの設定で描画しないように除外しています。
これは描画パスを分けることで実現しています。

・穴同士の重なり

Z test は別の判定に用いるので、描画の重なりの解決に Z バッファが使えません。
同じ位置にポリゴンが重なると矛盾が生じるので、描画位置を決める際に当たり判定を
行っています。必ず他の穴と重ならない位置に配置しています。

・地面の下だけ描画

破壊の穴形状は多少ずれても問題がないように球形をしています。
上半分が見えてしまわないように、地面の下に潜り込んだ部分だけ切り取るため
depth test を併用しています。Stencil がパスしてかつ、Z Fail が条件です。
地面の下にめり込んだ部分だけが描画されるようになります。

・depth 強制更新

ただ描画するだけでは不十分であることがわかります。
見た目と Z buffer に食い違いが生じるからです。
たとえば穴の上に置いた物や影が、地面の位置でまっすぐに切れてしまいます。

よって描画モデルを描き込むときに同時に depth を強制的に上書きします。
depth (Z buffer) が更新できれば、その後に描画する Shadow Volume も穴の形に
沿って落ちてくれます。

・遠くの穴が見えてしまう

地面が階層化している場所など、上の段の破壊穴と下の段の穴が重なって描画される
ことがあります。depth test の条件に Z Fail を設定しているということは、常に
遠くのオブジェクトが優先されることに等しいからです。
穴の中にさらに遠くの穴が見えてしまいます。

そこで穴モデル自体を Zソートして手前から描画し、同時にステンシルに 0 を書き
込んで消していきます。遠くの穴を描画する段階では depth test の Z Fail を
通っても、stencil テストに失敗するので描画されなくなります。

・地形との合成

上の問題を対処するためステンシルバッファを 0 で消してしまうと、今度はその
位置だけ地形を除外できなくなります。つまり穴の位置に上書きしてしまいます。
ステンシルをクリアする場合、別の条件で区別できる値を書き込まなければなりません。
元々ステンシル値の 0 または 0 以外で判定していたので思ったより簡単ではないです。
もしくは描画順番で何とかします。交差面を求めた後、地形の穴の中を描画する前に
先に地面を描画し、その後破壊穴モデルを描画すれば 0 クリアでも問題ありません。

・穴の上に重なるモデルの表示順番

地面に穴が開くはずの場所に他のモデルが置いてある場合も描画順番が問題となります。
例えばキャラが地面に立っていて、わずかに地面にめり込んでいる場合。(本当は
それが良くないのですが)
穴を開ける前の Z バッファで描画してしまうとめり込んだ部分が切れてしまいます。
だから地形に乗っているものは、穴を開けて Z バッファを更新したあとに描画する
ことになります。

・個数制限と消去

破壊の穴は同時に 30個描画しています。上限はただの定数値で、何らかの制限があった
わけではないです。これを超えた場合、視野に入っていないものから消しています。

●描画手順のまとめ

(1) 破壊可能部分の depth のみ
(2) 破壊穴モデルの交差判定
(3) 破壊穴の描画と depth 更新+識別可能な stencil で書き直す(要確認)
(4) 地形の上に乗っているモデル描画、不透明物一般
(5) 破壊不可能部分描画
(6) 破壊可能部分のカラー描画
(7) シャドウボリューム
(8) 影の合成
(9) 半透明地形の描画
(10) 半透明エフェクト

記憶だけで書いているので、おそらく異なっている部分や足りない部分があります。
これ以外にも特殊なエフェクトや表現があったので、実際はもっと描画手順が複雑です。
具体的に設定したステートの組み合わせなどは、おそらくもう一度実際に作ってみないと
わからないと思います。
それに今のハードならもっと簡単でしょう。ShaderModel 1.x の当時とは違うので、
別の表現方法も使えると思います。