710 likes | 1.26k Views
DOUBLE-S.T.E.A.L. における リアルタイム CG 表現技法. 株式会社ぶんか社ゲーム開発事業部 川瀬 正樹 kawase@bunkasha.co.jp. 本日の発表内容. Xbox の Direct3D マテリアルの表現 水面の表現 被写界深度(DOF)処理 縮小バッファによる高速化 画像フィルタ. Xbox の Direct3D. PC の Direct3D との全体的な違い API 呼び出しオーバヘッドが少ない GPU ネイティブな専用コマンドの追加 よりハードウェアに特化した API
E N D
DOUBLE-S.T.E.A.L.におけるリアルタイムCG表現技法DOUBLE-S.T.E.A.L.におけるリアルタイムCG表現技法 株式会社ぶんか社ゲーム開発事業部 川瀬 正樹 kawase@bunkasha.co.jp
本日の発表内容 • XboxのDirect3D • マテリアルの表現 • 水面の表現 • 被写界深度(DOF)処理 • 縮小バッファによる高速化 • 画像フィルタ
XboxのDirect3D • PCのDirect3Dとの全体的な違い • API呼び出しオーバヘッドが少ない • GPUネイティブな専用コマンドの追加 • よりハードウェアに特化したAPI • GPUコマンドキュー(プッシュバッファ)を直接生成することも可能 • Xbox拡張機能 • GeForce3のOpenGL拡張機能とほぼ同じ • プログラマブルシェーダの拡張
ピクセルシェーダの拡張 • レジスタコンバイナ機能の追加 • ベクタ/スカラ毎に各ステージで最大4入力3出力 • ファイナルコンバイナの追加 • フォグブレンド etc.
ジェネラルコンバイナ命令(1) • xmma d0,d1,d2, s0,s1,s2,s3 • d0=s0*s1 • d1=s2*s3 • d2=s0*s1+s2*s3 • xmmc d0,d1,d2, s0,s1,s2,s3 • d0=s0*s1 • d1=s2*s3 • d2= (r0.a>0.5) ? s2*s3 : s0*s1
ジェネラルコンバイナ命令(2) • xdd d0,d1, s0,s1,s2,s3 • d0=s0・s1 • d1=s2・s3 • xdm d0,d1, s0,s1,s2,s3 • d0=s0・s1 • d1=s2*s3
ファイナルコンバイナ命令 • xfc s0,s1,s2,s3, s4,s5, s6 • 最終出力 rgb=s0*s1 + (1-s0)*s2 + s3 • 最終出力 a=s6 • 特殊入力レジスタ • PROD = s4*s5 • SUM = r0+v1
その他 • テクスチャシェーダ(アドレッシング命令)の追加 • texm3x2depth/texbrdf/texm3x3diff etc. • GPUリソースの自由な定義 • 同一メモリを複数のフォーマットでアクセス • ミップマップの動的生成に利用 • デプスステンシルバッファをARGBテクスチャとして利用
反射/フレネル項 • 古典的な反射表現 • 手法 • 問題点 • より物理的な光の挙動 • 手法 • 鏡面反射と拡散反射の相関 • 問題点
古典的な反射表現 • 拡散反射+鏡面反射(環境マップ) • 最近では環境マップも動的に描画される • 問題点 • フレネル項を反映しにくい • 拡散反射と鏡面反射に相関が無いため、エッジ部分の反射光が入射光を超えてしまう
より物理的な光の挙動 • 拡散反射と鏡面反射の相関 • 鏡面反射は素材の表面(屈折面)で起こる • 拡散反射は素材の表面下で起こる • 最初に鏡面反射が起こる • 残りの一部が拡散反射を起こす • 残りは透過/吸収される
計算方法は? • 鏡面反射率 r の計算 • 鏡面反射カラー S を r で重み付け • 拡散反射カラー D を 1-r で重み付け
フレネルによる反射率計算 • 真面目な計算など不要 • 法線と視線の内積から計算 例: s:適当なスケール b:適当なバイアス • 光沢の低い素材ではsを小さく • 頂点/ピクセルシェーダ内で容易に計算可能
フレネル効果(1) フレネル項なし フレネル項なし 頂点単位フレネル ピクセル単位フレネル
問題点 • 鏡面反射に環境マップを利用すると、ダイナミックレンジが低過ぎる • 現実世界では光は極めて広い強さを持つ • 現実では正面での反射率は10%に満たない素材が多い 反射率の低い部分ではハイライトが弱くなり、 見栄えが良くない • 太陽が映りこんでも灰色 etc.
太陽光の反射は10%でも眩しい • 太陽や車のヘッドライトの輝度は、CGでの輝度1.0より桁違いに明るい • 10%の反射ですら1.0より強いことが多い • 太陽ならまだまだ桁違いに強い • 反射前の最大輝度が1.0では到底再現不可能
ハイダイナミックレンジ(HDR)表現 • 何らかの方法で1.0を超える輝度を表現したい • 反射率の低い部分に合わせて環境マップを作成する(カラーレンジの圧縮) • αチャネルを付加的な輝度情報とする
カラーレンジの圧縮 • 予めカラーレンジを圧縮しておく • 逆に反射率の高いエッジ部分が犠牲に • 全体的に反射率の高いマテリアルでは致命的 • 鏡面や金属素材等 ⇒RGB各8bitの分解能では難しい • DirectX9以降では改善される • ダブル・スティールでは環境マップのみ0.5倍圧縮(輝度2.0まで表現可能)
αチャネルを付加的な輝度に • 環境マップのαチャネルに輝度情報を追加 • どの段階でαチャネルから輝度情報に戻すか • 描画時にα値からRGB値を補正する • 描画後は既にHDRではない ⇒グレアフィルタを適用しにくい(輝度抽出が難しい) • あまり良好な結果を得られない • ポストプロセスのグレアフィルタと同時に補正する • 描画時はマテリアルを考慮してαチャネルへ出力 • HDRを利用したより高品質なグレアを生成できる • α値でグレアをコントロール可能 ⇒反射だけでなく光源も描画できる • 残像効果に利用可能
描画ピクセルシェーダサンプル xps.1.1 // v0.rgb = diffuse color // v0.a = fresnel reflection ratio tex t0 // .rgb = ベーステクスチャ tex t2 // .rgb = 環境マップ // .a = 環境マップの付加輝度 tex t3 // shadow map // v0 = v0 * shadowmap + amb mad v0.rgb, v0, PSR_SHADOW, c[PSC_AMB] // base.rgb = base * diffuse // env.rgb = env * specular xmma t0.rgb, t2.rgb, r1.rgb, t0, v0, t2, c[PSC_SPEC] lrp r0.rgb, v0.a, t2, t0 +mul r0.a, v0.a, t2.a // αには付加輝度(反射率*環境マップα)を出力 // さらに光源を加算(適当な調整) mad_x2 r0.rgb, r0.a, t2, r0 // 環境マップ描画時は _x2 は無効 // フォグブレンド後さらに調整 xfc FOG.a, r0, FOG.rgb, PROD, t2, r0.a, r0.a
半透明素材はα値に注意 • 不透明度としてのα値をそのまま出力すると、意図せず高い輝度になってしまう • 半透明素材ではαチャネルを書き込み禁止に • 半透明でかつ環境マッピングの必要な素材 • 重要でない場合はαチャネルには書き込まない • ⇒HDRをあきらめる • 環境マップから計算したα値を別パスで出力 • マルチパス描画の負荷とのトレードオフ
グレア • 眼やカメラ内部で発生する虚像 • レンズ内での光の散乱 • 光の回折 • グレアとなる光は極一部 • 実質的に強い光でしか確認できない • 眩しさを感じる要因の一つ
グレアの生成手法 • ジオメトリ情報から生成 • 遮蔽チェックが必要(かつ確実ではない) • 直接光にしか適用できない(反射光を抽出できない) • 光源の数に比例して負荷が増大 • 個々のグレアは高画質 • ポストプロセスで生成 • アルゴリズムは非常に単純(遮蔽チェック等が不要) • 反射光にも適用可能(キラキラ輝く水面/車のボディ) • グレアの形状を任意に生成可能 • 光源数やシーンの複雑さに依存しない • ただし固定の塗りつぶし負荷が極めて高い • 元画像のエイリアシングの影響をもろに受ける
ポストプロセスによる生成 • 画素の輝度に応じた虚像を生成する • RGB値とα値を乗じた値を実際の明るさとみなす(適当) • 抽出した輝度を適当な形状に加工 • 解像度を落とすとエイリアシングが発生 • 200x150程度が限界? • 元のバッファに加算 • 抽出した輝度は残像として利用
グレア形状の生成 • ピクセルシェーダでブラー画像を生成 • クロスや六角形のスターを生成 • n=1から任意回数ループ(ピンポンブラー) • n回目はn-1回目のレンダリングターゲットをテクスチャに使用 • 直線上に適当な間隔(4^nに比例した距離)でサンプリングして加算 • 1回毎に4テクスチャステージ使用可能 • 離れる程輝度を低く • 例:輝度 = 0.9^(4^n * d) n = 描画パス(0 ~ 任意数) d =近い順に 0,1,2,3 (各テクスチャステージに対応) • カラーの変更でスペクトルの分離現象を表現可能 ⇒1回でサンプリング間隔の4倍の長さに成長 • さらに任意形状にループ(2/4/6方向 etc.) • 総計でおよそ10回以上の描画パスが必要
輝度付きテクスチャ • 発光体マテリアルの表現 • ベーステクスチャのαチャネルに輝度情報を埋め込む • 描画時にαチャネルに出力 • α値が高い程シェーディングの影響を少なく α値が高い=カラーが圧縮されていることを意味するため ⇒グレアフィルタによって自動的に輝く • 電球/ネオン/夜のビルの窓/灯り etc. • 環境マップにも反映される ⇒水面/車のボディに映り込んだネオンが輝く
グレア処理 + 元画像 元画像 元画像α値 = 生成したグレア 最終画像 最終画像
動的環境マップのミップマップ • 動的環境マップのミップマップ生成は重要 • フレネルによりエッジ部の反射率が高い ⇒エイリアシングが目立つ • 光沢の低い鏡面反射の近似 • ミップマップの最大レベルを制限 • D3DTSS_MIPMAPLODBIAS ではなくD3DTSS_MAXMIPLEVEL • 多くの場合バイアスよりも良い近似となる
ミップマップ生成 • 各レベルへのレンダリングで生成 • 16*16程度まで生成 • バイリニアサンプルによるボックスフィルタ • エイリアシングが目立ちやすい • 特に、光沢の近似として適正レベルより小さいマップが使われている時 • 本来のミップマップ生成フィルタとしては正しいはずだが…
フィルタを変更 127と128の座標のピクセルは、最低解像度まで縮小されて初めて相互に影響する ⇒フィルタを変更 複数のテクスチャステージからジッタリングしてサンプリング オリジナル 環境マップ 256*256 ボックスフィルタ 16*16 エイリアスが 目立つ 理想的なブラー 16*16
水面の表現 • 水面シミュレーション • メガデモ等で知られる簡易シミュレーション t1(x,y) を時間 t-1 の水面の高さ t2(x,y) を時間 t-2 の水面の高さとした時 時間 t の水面の高さ t(x,y) は t(x,y) = ( ( t1(x-1,y) + t1(x+1,y) + t1(x,y-1) + t1(x,y+1) ) * 0.5 – t2(x,y) ) * attn • ハイトフィールド(高低マップ)を更新 • (オフセット)バンプ環境マッピング • ハイトフィールドから法線マップを作成 • 平面反射環境マップから法線に従ってuvをオフセット • いわゆる EMBM による近似 • ピクセル単位フレネル項/スペキュラ-を追加
オフセット環境マッピング(1) • 反射ベクトルの変化の近似としては雑 • 近くのオブジェクトの映りこみに対しては有効 • ほとんどの人は気付かない • 他に有効な(実用的な)手段がない • 遠くの背景では誤差が大きい • しかし通常の背景ではあまり気にならない • 太陽などの強いライトの映りこみ(ハイライト)では誤差が目立つ • キューブ/スフィア環境マップは適正が逆 • 無限遠を前提とした近似 • 遠くの背景の映りこみには極めて有効 • 近くのオブジェクトの映りこみには使いものにならない ⇒全体的にはオフセット環境マップが有効 • 太陽光のみバンプスペキュラ-を追加
オフセット環境マッピング(2) オフセット環境マップ スペキュラ- 追加 グレアを切って判りやすくした画像
水面シミュレーション • 2パスでハイトフィールドテクスチャを更新 1. w(x,y) = ( ( t1(x-1,y) + t1(x+1,y) + t1(x,y-1) + t1(x,y+1) ) * 0.5 2. t(x,y) = ( w(x,y) – t2(x,y) ) * 0.98 w(x,y) = ( ( t1(x-1,y) + t1(x+1,y) + t1(x,y-1) + t1(x,y+1) ) * 0.5 // 上下左右の前フレーム高低を加算して半分化 xps.1.1 def c0, 0.0f, 0.0f, 0.0f, 0.5f // スケールバイアス def c1, 0.5f, 0.5f, 0.5f, 0.5f // t0, t1 weight def c2, 0.5f, 0.5f, 0.5f, 0.5f // t2, t3 weight tex t0 // 1フレーム前(上) tex t1 // 1フレーム前(下) tex t2 // 1フレーム前(左) tex t3 // 1フレーム前(右) // 上下左右のフレームを加算して半分化 // ただし 0 ~ 1 を -1 ~ 1 に変換してから処理 //add_d2 r0, t0_bx2, t1_bx2 //add_d2 r1, t2_bx2, t3_bx2 xmma DISCARD, DISCARD, r0, t0_bx2, c1, t1_bx2, c1.a xmma DISCARD, DISCARD, r1, t2_bx2, c2, t3_bx2, c2.a // 加算した後にレンジを 0~1 に戻す add r0, r0, r1 mad r0, r0, c0.a, c0.a t(x,y) = ( w(x,y) – t2(x,y) ) * 0.98 // 生成したワークサーフェスと2フレーム前の高低マップを使用 xps.1.1 def c0, 0.0f, 0.0f, 0.0f, 0.49f // 減衰 * 0.5f def c1, 0.0f, 0.0f, 0.0f, 0.5f // スケールバイアス tex t0 // w(x,y) tex t1 // ht2(x,y) 2フレーム前 // ワークから2フレーム前を減算 // ただし 0 ~ 1 を -1 ~ 1 に変換してから処理 sub r0, t0_bx2, t1_bx2 // 全体をスケールさせて減衰 // その後レンジを 0~1 に戻す // r0 = r0 * 減衰 * 0.5f + 0.5f mad r0, r0, c0.a, c1.a • DISCARD は出力の破棄
法線マップの生成 • 上下左右の高低差から生成 • rg(Nx,Ny)要素からb(Nz)を計算(正規化を近似) • オフセットバンプマップにしか使わず、かつ法線マップをミップマップ化しないなら正規化は不要 // (nx, ny, nz) = (r, g, b) xps.1.1 #define R_MASK c0 #define G_MASK c1 #define B_MASK c2 def c0, 1.0f, 0.0f, 0.0f, 0.0f def c1, 0.0f, 1.0f, 0.0f, 0.0f def c2, 0.0f, 0.0f, 1.0f, 0.0f def c3, 0.5f, 0.5f, 0.5f, 0.5f // スケール/バイアス tex t0 // 高低マップ上ジッタ tex t1 // 高低マップ下ジッタ tex t2 // 高低マップ左ジッタ tex t3 // 高低マップ右ジッタ // 上下左右の高低差を取得 sub_x2 r0.rgb, t2.a, t3.a // 左-右 nx(r) ソース +sub_x2 r0.a, t0.a, t1.a // 上-下 ny(g) ソース // 正規化 nz = 1 - sqrt(1 - nx^2 - ny^2) を近似 // r1.rgb = r0.rgb ^ 2 + r0.a ^ 2 xmma DISCARD.rgb, DISCARD.rgb, r1.rgb, r0.rgb, r0.rgb, r0.a, r0.a // r1.rgb = 1 – r1.rgb = 1 - (r0.rgb ^ 2 + r0.a ^ 2) mad r1.rgb, -r1, r1, 1-ZERO // nx, ny, nz を r, g, b にセット xmma DISCARD.rgb, DISCARD.rgb, r0.rgb, r0.rgb, R_MASK, r0.a, G_MASK mad r0.rgb, r1, B_MASK, r0 // スケール/バイアス mad r0.rgb, r0, c3.a, c3.a
ミップマップ化 • 平面反射環境マップ • 視点を水面に対して鏡面反射させてレンダリングした画像 • バイリニアによるボックスフィルタでレンダリング • 光沢フィルタとして利用しないため単純なボックスフィルタでOK • 速度に影響するならミップマップなしでも許容レベル • 多少ノイズが発生する • 法線マップ(ミップマップは必須) • 単純なボックスフィルタでは生成できない • 各レベル毎に正規化が必要 • 1レベル小さくなる毎に隣の法線(テクセル値)との距離が2倍になることを考慮すべき? • ダブル・スティールでは適当に見栄えのする値でrg(Nx,Ny)をスケーリングした後、正規化(の近似)でb(Nz)を再計算
バンプ環境マッピング • 同次射影(q!=1.0)テクスチャで反射環境マップをスクリーン座標にマッピング • テクスチャ uv を法線マップを元にオフセット • texbem はテクスチャ座標の同次除算と同時に利用できない • オフセットした uv の置換マップを利用 • 256*256程度で許容範囲 • あまり大きくなるとフィルレートに影響
置換マップ • オフセットしたuv座標の置換マップを作成 • スクリーン系のuv座標にピクセルシェーダでオフセット • オフセットしたスクリーン系uv値をgbに出力 • 水面レンダリング時 • スクリーンにマッピングした同次射影uvから依存フェッチ(texreg2gb) • オフセットされた環境マップにアクセス • 置換マップの ar 要素が余るので利用 • フレネル反射率 • 置換マップ作成時に、キューブマップで正規化した視線ベクトルと法線マップとの内積から計算して a 要素に出力 • スペキュラ用 H・N • 置換マップ作成時に、キューブマップで正規化したハーフアングルベクトル(L+E)/|L+E|と法線マップ(N)との内積を r 要素に出力 • 水面レンダリング時に texreg2ar でn乗値に変換
水面描画テクスチャ uv 置換マップ r: スペキュラ-N・H a: フレネル反射率 g,b: オフセット済み u,v 最終画像 法線マップ r,g,b : N(x,y,z) * 0.5 + 0.5 ハイトマップ
水面描画ピクセルシェーダ // 水面バンプ平面反射マップシェーダ // αには光源度(反射率*環境マップα)を出力 xps.1.1 tex t0 // base texture tex t1 // .gb = remap GB texture // .a = Fresnel reflect term // .r = H.N texreg2gb t2, t1 // remap offset reflect env map texreg2ar t3, t1 // .rgb = Specular (H.N)^n // r0.rgb = base.rgb = diffuse // t2.rgb = env.rgb = env * specular xmma r0.rgb, t2.rgb, DISCARD.rgb, t0, v0, t2, c[PSC_SPEC] // bump specular を加算 // t3.rgb = bump specular mad t2.rgb, c[PSC_LIGHT0], t3, t2 +mad t2.a, c[PSC_LIGHT0].a, t3.b, t2.a // reflection lrp r0.rgb, t1.a, t2, r0 +mul r0.a, t1.a, t2.a // αには光源度(反射率*環境マップα)を出力 // さらに光源を加算 mad_x2 r0.rgb, r0.a, t2, r0 // 環境マップ描画時は _x2 は無効 xfc FOG.a, r0, FOG.rgb, PROD, t2, r0.a, r0.a
被写界深度(DOF)処理 • マルチサンプリング処理は非現実的 • 焦点から離れる程ぼやけさせることができれば充分な効果を得られる • デプスバッファを利用 • ピクセル単位にデプス値を読み、焦点からの距離を元に、スクリーン上でのぼやけ量を計算 • 元画像とブラー処理した画像を、ぼやけ量に従ってブレンド • ブラー画像の生成は4テクスチャステージでフィルタリング • ぼやけ量の計算はピクセル毎には少し複雑 • 焦点距離やカメラの絞り情報から計算
デプス値からテーブル参照 • デプス値から直接ぼやけ量をルックアップ • 一次元テクスチャを利用 • wバッファなら256段階のテーブルで充分 • ニアクリップがある程度遠いならzの方が精度が良い • フレーム毎に256要素(視点からの距離)のぼやけ量を計算して一次元テクスチャにセット • デプスフォーマット D24S8 を A8R8G8B8 としてピクセル単位にサンプリング(α部分にデプス値の上位8ビットがロードされる) • 依存フェッチ(texreg2ar)でぼやけ量テーブルから再サンプリング ⇒ぼやけ量(0~1)を取得
ぼやけ量からブレンド(1) • 元画像とブラーをかけた画像の2枚の合成では、中間部分の画質が劣化する • Xboxではテクスチャを4ステージ利用可能 • デプスバッファ/ルックアップテーブルで既に2ステージ使用 ⇒残り2ステージで、ブラー画像を2枚まで利用可能 • 元画像と合わせて計3枚のブレンド
ぼやけ量からブレンド(2) • ブラー画像のサイズはかなり小さめでも問題ない • ダブル・スティールでは256*192, 160*120 • ブラー画像の重みが100%になるとエイリアスが目立つ ⇒どれだけぼやけた状態でも元の画像を少し混ぜておく • ぼやけ量からカラー/ブレンド用αの計算 • r=0→ .a = 0.0 .rgb = ブラー画像1(結果には影響しない) • r=0.5→ .a = 1.0 より少し小さい値 .rgb = ブラー画像1 • r=1.0→ .a = 1.0 より微妙に小さい値 .rgb = ブラー画像2に画像1を少し合成 r = ぼやけ量(0.0~1.0)
被写界深度ピクセルシェーダ xps.1.1 def c0, 0.0f, 0.0f, 0.0f, 0.15f // (0.9f - 0.75f) def c1, 0.0f, 0.0f, 0.0f, 0.75f tex t0 // t0.a : wバッファ texreg2ar t1, t0 // t1.a : ブラー量 tex t2 // t2 : ブラーテクスチャ1 tex t3 // t3 : ブラーテクスチャ2 mad_d2 r0.rgb, t1_bx2.a, c0.a, c1.a +mov r0.a, t1.a mul r1.rgb, t1_bx2.a, c1.a +xmmc_x2 DISCARD.a, DISCARD.a, r0.a, t1.a, c1.a, 1-ZERO, r0.b // 出力αに従って元画像とαブレンディングされる xfc r1.b, t3, t2, ZERO, ZERO, ZERO, r0.a
被写界深度処理情報 元画像 元画像へのブレンド結果 w値 w値->ぼやけ量変換テーブル ブラー画像1(256*192) ブラー画像2(160*120)
被写界深度処理画像 元画像 被写界深度処理後 ぼやけ量情報