【CEDEC2018】最新タイトルのグラフィックス最適化事例

19.2K Views

January 17, 24

スライド概要

CEDEC2018 (Computer Entertainment Developers Conference 2018)で行われた講演
『最新タイトルのグラフィックス最適化事例』
で使用されたスライドです。

講演概要は以下のサイトをご覧ください。
https://cedec.cesa.or.jp/2018/session/detail/s5abb1a358f9fd.html

※CEDECの資料公開サイトCEDiLでも本資料が公開されています。
https://cedil.cesa.or.jp/

profile-image

株式会社カプコンが誇るゲームエンジン「RE ENGINE」を開発している技術研究統括によるカプコン公式アカウントです。 これまでの技術カンファレンスなどで行った講演資料を公開しています。 【CAPCOM オープンカンファレンス プロフェッショナル RE:2023】  https://www.capcom-games.com/coc/2023/ 【CAPCOM オープンカンファレンス RE:2022】  https://www.capcom.co.jp/RE2022/ 【CAPCOM オープンカンファレンス RE:2019】  http://www.capcom.co.jp/RE2019/

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

関連スライド

各ページのテキスト
1.

最新タイトルのグラフィックス 最適化事例 株式会社カプコン 三嶋仁 1

2.

はじめに • RE ENGINE – カプコンのゲームエンジン • 直近は2タイトルをリリース予定 – BIOHAZARD RE:2 – Devil May Cry 5 2

3.

求められるもの • 見た目の改善 • 高いパフォーマンス – Biohazard7よりも広い空間 – 大量のステージ 3

4.

CPUの改善 • バウンディングボックス(AABB)の最適化 • フラスタムカリングの処理の最適化 4

5.

最適化指針 • 不要な更新の削減 • メモリのキャッシュを有効活用 – 連続したメモリアクセス • SIMDの利用 – 一つの命令で複数のデータに処理 – 固定の処理は直接組み込み命令(intrinsic)で記述 5

6.

実際のゲーム中のAABB • 複数のメッシュでキャラクタを構成 – 各メッシュのAABB群はジョイント数と同数 最終的なAABB 体のAABB群 顔のAABB群 6

7.

AABBの作成 • 主にジョイント付きメッシュが問題 – ジョイント数が100以上のものが存在 – 各ジョイント単位でAABBを計算 – 最終的に代表のAABBに統合 • 問題の性質 – 計算量が多い – 固定の計算処理 – 各要素は線形に整列 7

8.

任意の点の回転 • 位置の要素をベクトルに展開 – 各行と積を取り、任意の軸の移動量に変換 x x x x * 00 01 02 03 y y y y * 10 11 12 13 = X = Y z z z z * 20 21 22 23 = Z 30 31 32 33 = W 位置の展開 行列の行 回転した点の位置=X+Y+Z+W 8

9.

AABBの回転後のAABB • 最大値と最小値の2点の回転を利用 – 最大値の移動量を(X,Y,Z,W) – 最小値の移動量を(x,y,z,w) • 8つの頂点は以下の組み合わせ X+Y+Z+W X+Y+z+W x+Y+Z+w x+Y+z+w X+y+Z+W X+y+z+W x+y+Z+w x+y+z+w • 8頂点の各要素の最大値、最小値が新しいAABB – SIMDで実装 9

10.

AABBの作成 SIMD vs C++ • 組み込み命令はC++より6.4倍早く処理が完了 – 1メッシュ 161関節, 216個のメッシュの更新時間 処理時間(msec) 10 8 6 4 2 0 1 2 11

11.

フラスタム(視錐台)カリング • 視野に物体が入っているかチェックし見えない ものを省く • 事前に必要な情報を収集 – 視錐台の収集 • カメラ、ミラー、影 – 処理対象のAABBを収集 • スレッド単位でキャッシュライン分収集 12

12.

視錐台カリング SIMD vs C++ • 組み込み命令はC++より2.6倍早く処理が完了 – カメラ1個、影22個、メッシュが1024個 処理時間(msec) 5 4 3 2 1 0 1 2 13

13.

CPUの最適化 • 固定処理で負荷が高いならSIMDの検討 – 眠っている演算資源の活用 – 特にメモリが連続的なら対応するべき – 組み込み命令はシェーダを書ければ容易に利用可能 14

14.

GPU側の最適化 • マテリアルの描画の最適化 – MultiDraw,ExecuteIndirect • 遮蔽カリング • 影 • その他 – DepthBoundsTest 15

15.

描画コマンドについて • CAPCOMは伝統的に中間描画コマンドを採用 – MTF,RE ENGINE共 • 中間描画コマンド – 各スレッドが自由な優先で描画コマンドを作成 • 最高の並列度 – 次のフレームから描画コマンドをソート – 各APIの描画コマンドに変換 16

16.

マテリアルの描画の最適化 • 従来のマテリアルのレンダリング – メッシュのパーツのOn/Off • 同一マテリアルの連続描画ができない MaterialSetup(cmdlist); for (UINT i = 0; i < drawCount; i++) cmdlist.DrawIndexed(indexCount[i],startIndexLocation[i],baseVertexLocation[i]); • 描画コマンド発行の負荷 17

17.

マテリアルの描画の最適化 • メッシュの描画はMDIを使用 – MultiDrawIndirect / ExecuteIndirect DX11はNVAPI,AGSでMDIを利用可能,DX12はExecuteIndirectで標準サポート – DrawIndexedInstancedIndirectの連続実行 MaterialSetup(cmdlist); cmdlist.MultiDrawIndirect(argBuffer, argByteOffset, drawCount); • メッシュ単位でIndirectArgumentを保持 – メッシュアセット • パーツ表示状態に応じてランタイムで再構成 18

18.

IndirectArgument • 一つの巨大なIndirectArgumentBuffer – Heapの様に使用するバッファ領域を割り当て • 各メッシュ単位で独立した領域を保持 • インスタンシングならインスタンス数N個でも一個分の領域 IndexCount InstanceCount StartIndexLocation BaseVertexLocation StartInstanceLocation 19

19.

ExecuteIndirect? • 複数の描画コマンドを同時に実行可能 – MaxCommandCount CPUから明示的な個数 – CountBuffer GPUから間接的に個数を制御 • MaxCommandCountとCountBufferのminが実コマンド数 void ExecuteIndirect( ID3D12CommandSignature UINT ID3D12Resource UINT64 ID3D12Resource UINT64 *pCommandSignature, MaxCommandCount, *pArgumentBuffer, ArgumentBufferOffset, *pCountBuffer, CountBufferOffset); https://docs.microsoft.com/ja-jp/windows/desktop/api/d3d12/nf-d3d12-id3d12graphicscommandlist-executeindirect 20

20.

ExecuteIndirect • 実際の中身 • PCのDX12のPIX上では以上のように展開 21

21.

パフォーマンス • 大きな改善はない • GPUカリング – MDIが有効 22

22.

GPUベースのカリング • オクルージョンカリング – MDI/ExecuteIndirectのCountBufferを使用 • メモリアライメントが4bytes • IndirectArgumentの実行数を制御可能 – Predicate命令で実現可能 • メモリアライメントが8bytes • 不採用 • MDI/ExecuteIndirectを利用 23

23.

通常レンダリング 24

24.

メッシュ単位オクルージョンカリング 25

25.

オクルージョンカリング • データ構造 • 遮蔽情報の構築 • 描画処理 • カリングの実行 – 単品メッシュ – インスタンシング 26

26.

データ構造 • 可視判定結果はVisibleBufferで管理 – – – – 前のスライドのCountBufferの別名 要素数はゲーム中の最大メッシュ数に相当 各要素は可視なら0xffff,不可視なら0 ByteAddressBuffer 27

27.

データ構造 • メッシュの情報 – AABB • CPUで作ってもよし、GPUで作ってもよし – 可視判定結果のByteOffset • メッシュ単位でVisibleBufferOffsetを保持 – IndirectArgumentのByteOffset 28

28.

遮蔽情報の構築 • 256x128のMSAAx4の深度バッファに描画 – ShaderResourceViewで参照しない設定 • 遮蔽モデルはアーティストが簡易背景を作成 29

29.

可視判定 • 2段階の処理 – 視野内の判定 • • • • CSでカメラとのAABBの内外判定を実行 カメラの内部にいる場合は”可視”であると判断 カメラの外部の場合は”不可視”であると判断 本処理は一回のDispatchですべてのメッシュをテスト – 遮蔽されているかの判定 • テスト対象のAABBをレンダリング • Instancingを利用し、1DrawCallですべてのAABBのテスト レンダリングを実行 30

30.
[beta]
コンピュートシェーダの可視判定
• AABBがカメラの内部かチェック
PSのみで可視判定はNearPlaneで正常に表示されない
[numthreads(64, 1, 1)]
void CS_CulltestFirst(uint iid : SV_DispatchThreadID){
if (iid < TestCount)
uint
Index = iid+indexOffset
uint
objectId = InstanceIndirectBuffer.Load(Index << 2);
InstanceBoundingBox object = InstancBoundingBuffer[objectId];
bool flag = (innertestBoxPoint(object.minpos-camNear,object.maxpos+ minpos-camNear,camPos)) ;
RWCountBuffer.Store( objectId << 2, flag ? 0xffff : 0);
}
}

31

31.

遮蔽の可視判定 • [earlydepthstencil] を使いEarlyZを有効化 – PixelShaderでVisibleBufferに0xffffをいれる – EarlyZでカリングされればCSの可視判定結果を維持 [earlydepthstencil] void PS_Culltest(OccludeeOutput I){ RWVisibleBuffer.Store(I.outputAddress, 0xffff); } 32

32.

WAVE命令が使える場合 • 書き込み抑え[dorobot16]を利用 – WAVE単位で同一アドレスへの書き込みを最小化 [earlydepthstencil] void PS_Culltest(OccludeeOutput I){ uint hash = WaveCompactValue(I.outputAddress); [branch] if (hash == 0){ RWCountBuffer.Store(I.outputAddress, 0xffff); } } 33

33.

カリング結果の適応 • メッシュ単位のカウントバッファが完成 – 表示 – 非表示 VisibilityBufferに0xffff VisibilityBufferに0 • ExecuteIndirectを利用して結果の反映 – VisibleBufferをCountBufferとして使用 34

34.

単品のメッシュの描画 • VisibilityBufferをCountBufferとして直接使用 • ExecuteIndirect – 描画コマンドの数はMaxCommandBuffer指定 • CountBufferが0なら、非表示 • CountBufferが0xffffなら、MaxCommandBufferで表示 • 単品のオクルージョンカリングは完了 35

35.

結果 36

36.
[beta]
インスタンシングメッシュの描画
• インスタンシングの疑似コード
VSOut VS(VSInput I)
{
VSOut o;
uint instanceIdx = InstancingIndex.Load(
I.instanceId*4);
float4x4 wmat = InstanceTable[instanceIdx ].mat;
float3 pos = mul(float4(I.pos,1 wmat);
o.Pos = mul(float4(pos,1),vp);
…….
return o;
}

struct Instance
{
float4x4 mat;
……
};
StructuredBuffer<Instance> InstanceTable;
ByteAddressBuffer InstancingIndex;

• InstancingIndexはCPUから作成
– CPUでLODやソートを制御しているため
37

37.

オクルージョンカリングの適応 • IndirectArgumentのInstanceCountの修正 – InstanceCountは可視のインスタンス数 IndexCount InstanceCount StartIndexLocation BaseVertexLocation StartInstanceLocation • InstancingIndexの修正 – 可視のIndexのみリスト 38

38.

InstancingIndexの修正 • 有効なインデックスのみを描画できるように先 頭から詰めなおす VisibleBuffer visible invisible visible invisible invisible invisible visible InstancingIndex Batch1 Batch2 Batch3 Batch4 Batch5 Batch6 Batch7 InstancingIndex Batch1 Batch3 Batch7 • コンパクションはPrefixCountを利用 – WaveIntrinsicやSharedGroupMemoryで処理 39

39.
[beta]
SharedGroupだけの例
struct InstanceDrawInfo{

if (isVisible){

uint

instanceCountMultiCount;//下位16bit instance Count 上位 MultiCount

uint

instanceDataByteOffset;

uint

indirectArgumentBufferByteOffset;

outputIndex = VIA_MAD24u(outputIndex+instanceNum,4,info.instanceDataByteOffset);
RWInstancingIndex.Store( outputIndex, targetIndex); //書き出す
}

};

instanceNum += count;

RWByteAddressBuffer

RWDrawIndirectArguments2;

RWByteAddressBuffer

RWInstancingIndex;

StructuredBuffer<InstanceDrawInfo>

IDI;

//

//有効インスタンス数をカウントアップ

if (i >= maxInstanecNum)//終端に達したら終了

//元はCPUで作成 メッシュのインデックスの配列

break;

//CPUで作成

}

groupshared uint temp_countbuffer;

//IndirectArgumentのMultiDraw向けパッチ
for (uint i = threadID;i < (info.instanceCountMultiCount >> 16);i+=WAVENUM){

#define WAVENUM 32

uint index = VIA_MAD24u(i, 20, info.indirectArgumentBufferByteOffset);

[numthreads(WAVENUM,1,1)]

RWDrawIndirectArguments2.Store( index+4,instanceNum);

void CS_InstancingCompaction(uint workID : SV_GroupID, uint threadID : SV_GroupThreadID){
InstanceDrawInfo info = IDI[workID + VIA_CONSTANT32bit];

uint

instanceNum = 0; //出力数

uint

maxInstanecNum = (info.instanceCountMultiCount & 0xffff);//インスタンス数

uint

thread_mask = (1<<threadID)-1;

}
}

for (uint i = threadID;true;i+=WAVENUM){
temp_countbuffer = 0;
uint isVisible = 0;

uint targetIndex;
if (i < maxInstanecNum){
targetIndex = RWInstancingIndex.Load( VIA_MAD24u(i,4,info.instanceDataByteOffset));

isVisible = VisibleBuffer.Load( VIA_MUL24u( targetIndex, 4));
}
InterlockedOr(temp_countbuffer,(isVisible ? 1 : 0)<<threadID);

uint

mask = temp_countbuffer;

uint

count = countbits(mask);

uint

outputIndex = countbits(mask & thread_mask);

40

40.

半透明のインスタンシングメッシュ • 単品メッシュとして処理 – CountBufferを利用して実現 – OITなら、そのままレンダリング可能? 41

41.

実際のシーンでの比較 処理時間(マイクロ秒) 2 1 0 500 1000 Series1 1500 2000 2500 Series2 API DirectX12, GPU AMD Radeon R9 Fury X, 計測 RadeonProfiler 42

42.

ここまでの最適化 • Mesh単位でGPUオクルージョンカリング判定 – CountBufferを0xffffと0として処理 • 事前準備に簡易モデルを利用 – 低解像度の深度バッファの構築 • 最高ではないが十分な結果 43

43.
[beta]
カリング時の問題
• ピクセル以下の領域がテストできない
– AABBの投影面積が小さすぎる
– VSで頂点を膨らませる
• Conservative Rasterizationがない場合

・

・

・

・

・

・

・

・

・

・

・

・

float4 VS_Culltest(float3 pos : Position) : SV_Position{
float4 o = mul(float4(pos, 1), vp);
float4 c = mul(float4(boundinx_box_center.xyz,1), vp);
o.xy += sign((o.xy- c.xy)) *inverseHalfScreenSize * o.w;
return o;
}
44

44.

背景のカリング • プロップスやキャラクターはMesh単位で十分 • 超巨大な物体は常に見えている状態 • 自動でメッシュを分割 • 分割単位でフラスタムカリング/オクルージョン カリングをするように対応 46

45.

メッシュ単位オクルージョンカリング 47

46.

背景のカリングもカリング有効化 48

47.

自動分割 • 256トライアングルを1バッチとして切り出す – 各バッチは連続したIndirectArgument – バッチ単位にAABBを構築 49

48.

背景のカリング • 普通のメッシュのカリングとほぼ同様 – CSで視錐台 – PSで可視判定 50

49.

Compaction • 歯抜けになったコマンドを再度詰めなおす – WaveIntrinsicを使えば簡単に実装可能 – なくてもSharedGroupMemoryで同様のことが可能 Batch1 Batch2 Batch3 Batch1 Batch3 Batch7 Batch4 Batch5 Batch6 Batch7 51

50.

マイクロな描画コマンドが大量 • すべてのドローが768インデックス以下になる – Hardwareによるがそのまま実行すると大量の SubDrawCallが発生しパフォーマンスが低下 • 隣接IndirectArgumentが連続していたら統合 52

51.

パフォーマンスの変化 処理時間(マイクロ秒) 3 2 1 0 500 1000 Series1 1500 2000 2500 Series2 API DirectX12, GPU AMD Radeon R9 Fury X, 計測 RadeonProfiler 53

52.

背景の自動分割カリングのまとめ • 必要な可視範囲に限定化 – メッシュのインデックスを一定間隔で分割 • フラスタムカリングとオクルージョンカリングを実施 – カリング後のIndirectArgumentを連結 • 背景の作りによるが効果的 54

53.

Z-prepass • 深度を先に描画し、後続の複雑なシェーダの多 重描画を抑制することを目的 • BH7は、AlphaTestは強制的にZ-prepassを実行 – 実際のシェーディングはEqualでレンダリング • フルのZ-prepassは頂点コストが高い 55

54.

部分的なZ-prepass • 自動分割されたモデルを再利用 – カメラに近いモデルのみZ-prepassを実行 処理時間(マイクロ秒) 4 939.864 3 1404.695 2 1784.143 1 1962.44 0 500 1000 1500 Series1 Series2 2000 2500 API DirectX12, GPU AMD Radeon R9 Fury X, 計測 RadeonProfiler 56

55.

部分的なZ-prepassのまとめ • 頂点コストは少ない – カメラに近いところのみ • オーバードロー抑制効果が高い – 屋内はとても効果的 – 屋外でも地面や立体的な地形では有効に動作 57

56.

設定によるパフォーマンスの差 OCCLU SI ONCU LLING とGB UF FER の処理時間(マイクロ秒) Series1 1962.44 1784.143 Series2 1803.872 1404.695 939.864 977.473 240.322 142.02 5 6 142.149 158.937 240 241.584 1 2 3 4 API DirectX12, GPU AMD Radeon R9 Fury X, 計測 RadeonProfiler 58

57.

影の最適化 • DevilMayCry5で特に問題となった – DirectionalLightで広い範囲を描画 • 影キャッシュは視点が変わるとフラッシュ • 直接影を描画すると頂点パフォーマンスが問題 • 3つのアプローチで対応 – DMC5はD16Unormを利用し帯域削減 – GPU上で背景を細かくフラスタムカリング – 事前にシャドウマップをベイク 60

58.

背景のフラスタムカリング • 前の項目と同様の手法 – 静的な背景のみに適用、あらゆる光源で実行 処理時間(ミリ秒) 4 3.5 3 2.5 2 1.5 1 0.5 0 1 2 61

59.

シャドウマップの事前ベイク • DirectionalLight用の巨大な解像度の深度を圧縮 – ランタイムで解凍 – 元のアイデアは[Kevin]のSparseShadowTree • 似たような仕組みを導入 – ポイントクラウドの代わりに深度バッファから圧縮 – 最適化のアルゴリズムの違い • 16kx16k,32kx32k程度の解像度を利用 62

60.

見た目の比較 普通の影 圧縮された影 63

61.

データ構造のイメージ • 一様格子 + 圧縮ツリー or 無圧縮ブロック – ツリー構造 四分木 – 無圧縮ブロック Raw Root Node UniformGrid Leaf Raw Leaf Leaf 64

62.

作成工程 • アセット作成 • コンバート – – – – – 深度バッファから圧縮領域の切り出し 切り出された深度バッファの並び替え 圧縮してできたツリーの構築 成型処理 出力フォーマットに変換 65

63.

シャドウマップのアセット作成 • 1024x1024単位で区切ってレンダリング – R32G8Typelessで作成 • Stencilはより圧縮するかのオプションとして使用 – アセットとして保存するため、LZ4で圧縮 • 生データだと16Kで1GB,64Kで16GBになるため 66

64.

圧縮領域の切り出し • シャドウマップを128x128で切り出す – 圧縮率は128が良好 • 最大7階層の四分木 • 深度値の最大値/最小値を取得 – 最終的なフォーマットは勾配を16bit精度で持つため 復元後の深度の精度を上げるためにタイル内を0-1で 正規化 68

65.

モートン符号化 • リニアな2D画像はメモリアクセスが非効率 2D画像のメモリ配置 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 メモリ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 69

66.

モートン符号化 • 2次元のデータを1次元のタイルに変換 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 メモリ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 変換後のメモリ 0 1 4 5 2 3 6 7 8 9 12 13 10 11 14 15 70

67.

データの圧縮 level0 level1 level2 0 1 2 3 4 5 6 7 16 17 8 9 10 11 18 19 12 13 14 15 20 メモリ 0 1 4 5 2 3 6 7 8 9 12 13 10 11 14 15 16 17 18 19 20 71

68.

圧縮 • ツリー構造は線形に操作しながら作成 – ボトムアップでツリーを構築 20 level2 16 level1 17 18 19 level0 0 1 4 5 1 4 5 2 2 3 6 7 8 9 14 15 13 10 11 14 10 11 14 15 メモリ 0 3 6 7 8 9 12 15 16 17 18 19 20 72

69.

圧縮 • 2x2の要素から新しいノードを構成 同一平面上に存在するかチェック A B C D |A+D-B-C| < EPSILON EPSILON = TILE_SIZE/2*FLT_EPSILON/(MAXZ-MINZ) 経験則的なパラメータ TILE_SIZEは128 73

70.

圧縮 • 同一平面上であればノードを勾配としてマーク – 勾配時は、DDX,DDYと中心の深度情報を保持 – 子ノードの2x2を破棄 • 平面になければ無圧縮状態としてノードをマーク – 深度情報を保持しない – 子ノードの2x2への参照インデックスを保持 • いずれの場合も新しいノードをメモリの終端に追加 74

71.

圧縮 • Level0がすべて平面なら5個のノードで表現 20 level2 16 level1 17 18 19 level0 0 0 1 4 5 1 4 5 2 2 3 6 3 7 6 8 7 9 10 8 9 10 11 11 12 13 14 12 15 16 13 17 14 18 15 19 20 75

72.

圧縮 • Level0が一部無圧縮なら9ノードで表現 20 level2 16 level1 17 18 19 level0 0 0 1 4 5 1 4 5 2 2 3 6 3 7 6 8 7 9 12 8 9 12 13 13 10 11 14 10 15 16 11 17 14 18 15 19 20 76

73.

圧縮 • すべてが同一平面なら1個のノードで表現 20 level2 level1 level0 0 1 4 5 2 3 6 7 8 9 12 13 10 11 14 15 16 17 18 19 20 77

74.

ツリー構築後 • フィルター – ツリーのリーフが同一平面上か調査し統合 • 異なる枝に同一平面が存在する可能性 • 同一なら統合 – ノードの子リーフがすべて同一平面 • 冗長なノードを削減し、子のリーフをノードとして置換 • 検証 – 数値誤差の許容範囲 • 許容範囲外ならnelder-meadで補正し誤差を最小化 78

75.

出力データ構造に変換 • Header – 4 DWORDs 大きさ情報などを格納 • UniformGrid – (Resolution/128)^2 DWORDs • 無圧縮 32bit 31bit-1bit 無圧縮bit 参照先の相対アドレス – 128*128 DWORDs – 深度バッファ 79

76.

出力データ構造に変換タイル • 圧縮 – 先頭にMaxZ, MinZを書き込む • 2DWORDs分展開用のMaxZ,MinZを書き込む – ノードの種類で書き込む内容を変更 NODE 2bits 32bits 32bits DEPTH 深度(30bit) なし SLOPE 深度(30bit) 16bit Snorm DDX / 16bit DDY NODE 15bit UL Index /15bit UR Index 16bit LL Index /16bit LR Index 80

77.

ゲームでの利用 • クリアの代わりにベイクした深度を利用 – ベイクした深度を通常のシャドウマップにコピー – ベイク対象のメッシュはレンダリング対象から外す • 頂点処理のパフォーマンス向上 • 使用されている解像度 – 16kx16k – エンジン上のアセットサイズ • 最大サイズ70.7MB • 最小サイズ2.15MB 82

78.

比較 普通の影 36ms 圧縮された影 20ms API DirectX12, GPU NVDIA Geforce GTX 1070Ti, 83

79.

影の最適化のまとめ • シャドウキャスト時の背景は視錐台でカリング • 事前に指向性光源の深度情報をベイク – ベイクすることでDepthのClearの代わりに動作 – 無駄な背景描画コストを削減 • 代わりにメモリを利用 85

80.

DepthBoundsTest • Zの範囲のみ描画を実行する機能 – 主にピクセルシェーダの抑制に使う – DX12(creators update),DX11.3で使用可能 • DX11はNVAPI,AGSで使用可能 • RE ENGINEはデカールとライトシャフトで使用 86

81.

デカール • 深度テストが失敗したピクセルで動作 – 完全に壁に隠れた場合は処理を省きたい – DepthBoundsTestで解決 DepthBoundsTest 無効 DepthBoundsTest 有効 87

82.

DepthBoundsTestを適応した結果 GBufferの処理時間 (ミリ秒) 3.5 3 2.5 2 1.5 1 0.5 0 1 2 API DirectX12, GPU AMD Radeon R9 Fury X, 計測 RadeonProfiler 88

83.

まとめ • CPUの最適化 – SIMD • GPUの最適化 – MDIを利用したメッシュの描画の最適化 – 事前にシャドウマップをベイク – DepthBoundsTest 91

84.

展望 • Bindless化 – VertexBuffer,IndexBufferの統一 – Textureのバインドレス化 – 同一シェーダは一回のみのDrawCall 92

86.

参考文献 • SIMD at Insomniac Games、 Andreas Fredriksson,GDC2015 • GPU-Driven Rendering Pipelines、Ulrich Haar (Ubisoft Entertainment), Sebastian Aaltonen (Ubisoft Entertainment) • Optimizing the Graphics Pipeline with Compute,Graham Wihlidal • Improved Culling for Tiled and Clustered Rendering,Michal Drobot • AMD GeometryFX • SparseShadowTree, Kevin Myers • Rendering with Conviction ,Stephen Hill 94