ハイライト
Metal は WWDC2023 でレイ トレーシング API を拡張し、ヘア レンダリング用のカーブ プリミティブ、複雑なシーン用のマルチレベル インスタンス化、および GPU 駆動の間接インスタンスによる構造構築の高速化を追加し、ゲーム レベルとプロダクション レベルのレンダラーの両方が大規模なシーンを処理できるようにしました。
主な内容
三角形から曲線へ: ヘア レンダリングの問題点
髪と毛皮を三角形でモデリングすることは、グラフィックス開発者にとって悪夢です。1 本の髪の毛を偽物に見せるには数十の三角形が必要で、キャラクターには何百万もの髪の毛がある場合があります。メモリ使用量は爆発的に増加し、アクセラレーション構造の構築には長い時間がかかり、さらにズームインするとまだギザギザが見えます。
Metal 2023 では、4 つの曲線基底関数 (Bezier、Catmull-Rom、B-Spline、linear) を直接サポートする曲線ジオメトリ (Curve Primitives) が導入されています。曲線はカメラがズームインしても滑らかなままで、三角形よりもメモリの使用量が少なく、構造の構築がより高速になります。
シーンスケールのボトルネック: インスタンス化の進化
モアナ島のような複雑なシーンをレンダリングする場合、すべてのジオメトリを 1 つのアクセラレーション構造に詰め込むだけではメモリを消費してしまいます。インスタンス化により、同じオブジェクトを異なる場所に繰り返し表示でき、ジオメトリ データのコピーは 1 つだけ保存されます。
2023 年のマルチレベル インスタンス化はさらに一歩進んでおり、インスタンス アクセラレーション構造に他のインスタンス アクセラレーション構造を含めることができます。ヤシの木は幹と葉のインスタンスで構成され、森全体がその木をインスタンス化します。したがって、モアナのシーンは何百万ものインスタンスを保存します。
動的シーン向けの GPU ドライバーの最適化
ゲームはフレームごとにインスタンスのアクセラレーション構造を再構築するため、CPU 側のインスタンス記述子を埋めることがボトルネックになります。間接インスタンス アクセラレーション構造記述子を使用すると、インスタンスのカリング、記述子の入力、および最終的なインスタンスのカウントを GPU 上で完了でき、CPU を完全にバイパスできます。
##詳細
曲線ジオメトリ記述子
(06:42)
メタル 2023 新規MTLAccelerationStructureCurveGeometryDescriptor、髪や植生などの曲線ジオメトリに特に使用されます。
let geometryDescriptor = MTLAccelerationStructureCurveGeometryDescriptor()
geometryDescriptor.controlPointBuffer = controlPointBuffer
geometryDescriptor.radiusBuffer = radiusBuffer
geometryDescriptor.indexBuffer = indexBuffer
geometryDescriptor.controlPointCount = controlPointCount
geometryDescriptor.segmentCount = segmentCount
geometryDescriptor.curveType = .round
geometryDescriptor.curveBasis = .bezier
geometryDescriptor.segmentControlPointCount = 4
キーポイント:
controlPointBuffer:ストレージコントロールポイントの場所radiusBuffer: 曲線に沿って補間された各制御点の半径indexBuffer: 各曲線セグメントの開始制御点インデックスcurveType:.round円筒形の断面であり、.flat視覚のためのパフォーマンスを最適化するcurveBasis: オプション.bezier、.catmullRom、.bspline、.linearsegmentControlPointCount: 各曲線セグメントの制御点の数 (2、3、または 4)
加速構造の構築
(07:29)
プリミティブ アクセラレーション構造を作成するための 3 つのステップのプロセス:
// 1. 创建描述符
let accelerationStructureDescriptor = MTLPrimitiveAccelerationStructureDescriptor()
accelerationStructureDescriptor.geometryDescriptors = [geometryDescriptor]
// 2. 查询内存需求
let sizes = device.accelerationStructureSizes(descriptor: accelerationStructureDescriptor)
let heapSize = device.heapAccelerationStructureSizeAndAlign(size: sizes.accelerationStructureSize)
// 3. 分配并构建
var accelerationStructure = heap.makeAccelerationStructure(size: heapSize.size)
let scratchBuffer = device.makeBuffer(length: sizes.buildScratchBufferSize,
options: .storageModePrivate)!
let commandEncoder = commandBuffer.makeAccelerationStructureCommandEncoder()!
commandEncoder.build(accelerationStructure: accelerationStructure,
descriptor: accelerationStructureDescriptor,
scratchBuffer: scratchBuffer,
scratchBufferOffset: 0)
commandEncoder.endEncoding()
キーポイント:
- ヒープからアクセラレーション構造を割り当てることで、その後のリソース管理のオーバーヘッドを削減できます。
scratchBufferビルド プロセス中にのみ使用され、ビルドの完了後に再利用できます。- 複数のビルド操作を同じコマンド エンコーダーにエンコードし、並行して実行できます。
インスタンスの高速化構造とマルチレベルのインスタンス化
(11:30)
インスタンスの加速構造は、複数の元の加速構造を参照し、変換行列を通じてシーンに配置されます。
var instanceASDesc = MTLInstanceAccelerationStructureDescriptor()
instanceASDesc.instanceCount = instanceCount
instanceASDesc.instancedAccelerationStructures = [mountainAS, treeAS, leafAS]
instanceASDesc.instanceDescriptorType = .userID
// 分配实例描述符缓冲区
let size = MemoryLayout<MTLAccelerationStructureUserIDInstanceDescriptor>.stride
let instanceDescriptorBuffer = device.makeBuffer(length: size * instanceCount,
options: .storageModeShared)!
instanceASDesc.instanceDescriptorBuffer = instanceDescriptorBuffer
// 填充实例描述符
var instanceDesc = MTLAccelerationStructureUserIDInstanceDescriptor()
instanceDesc.accelerationStructureIndex = 0
instanceDesc.transformationMatrix = transformMatrix
instanceDesc.mask = 0xFFFFFFFF
キーポイント:
instancedAccelerationStructures配列内のインデックスを介してaccelerationStructureIndex引用mask選択的交差を実現するためのライト マスク フィルタリングに使用されます。- マルチレベルのインスタンス化中、インスタンス アクセラレーション構造には他のインスタンス アクセラレーション構造を含めることができ、最大レベルが渡されます。
max_levelsラベル指定
GPU ドライバーの間接インスタンスのビルド
(14:06)
間接インスタンス アクセラレーション構造により、GPU がインスタンス生成を完全に制御できるようになります。
var instanceASDesc = MTLIndirectInstanceAccelerationStructureDescriptor()
instanceASDesc.instanceDescriptorType = .indirect
instanceASDesc.maxInstanceCount = maxInstances
instanceASDesc.instanceCountBuffer = instanceCountBuffer
instanceASDesc.instanceDescriptorBuffer = instanceDescriptorBuffer
キーポイント:
maxInstanceCountは事前に設定された上限であり、実際の量は GPU によって書き込まれます。instanceCountBuffer- インスタンスの数が GPU カリング後に動的に決定されるシナリオに適しています
- 間接的なインスタンス記述子パス
accelerationStructureID配列インデックスではなく、アクセラレーション構造体を直接参照する
構造最適化の加速: 改修と圧縮
(19:22)
ジオメトリがわずかに移動する場合、加速構造全体を再構築する必要はありません。 refit を使用して境界ボックスを更新します。
let scratchBuffer = device.makeBuffer(length: sizes.refitScratchBufferSize,
options: .storageModePrivate)!
commandEncoder.refit(sourceAccelerationStructure: accelerationStructure,
descriptor: asDescriptor,
destinationAccelerationStructure: accelerationStructure,
scratchBuffer: scratchBuffer,
scratchBufferOffset: 0)
ビルドが完了したら、圧縮を使用して余分なメモリを再利用します。
// 查询压缩后大小
sizeCommandEncoder.writeCompactedSize(accelerationStructure: accelerationStructure,
buffer: sizeBuffer,
offset: 0,
sizeDataType: .ulong)
// 分配新加速结构后执行压缩
compactCommandEncoder.copyAndCompact(sourceAccelerationStructure: accelerationStructure,
destinationAccelerationStructure: compactedAccelerationStructure)
キーポイント:
- 再フィットは再構築よりもはるかに高速で、フレームごとに更新される動的ジオメトリに適しています。
- 圧縮は、ビルド中に控えめに割り当てられたメモリを再利用でき、元のアクセラレーション構造に最も明白な影響を与えます。 ・独自の加速構造により圧縮完了後に解放可能
シェーダーのレイ交差
(21:48)
メタル シェーディング言語で光線交差を実行します。
[[kernel]]
void trace_rays(acceleration_structure<instancing> as, /* ... */) {
intersector<instancing, max_levels<3>, triangle_data, curve_data> i;
i.assume_geometry_type(geometry_type::curve | geometry_type::triangle);
i.assume_curve_type(curve_type::round);
i.assume_curve_basis(curve_basis::bezier);
i.assume_curve_control_point_count(3);
ray r(origin, direction);
intersection_result<instancing, max_levels<3>, triangle_data, curve_data> result = i.intersect(r, as);
if (result.type == intersection_type::triangle) {
float distance = result.distance;
float2 coords = result.triangle_barycentric_coord;
// shade triangle...
} else if (result.type == intersection_type::curve) {
float distance = result.distance;
float param = result.curve_parameter;
// shade curve...
}
// 获取实例层级信息
for (uint i = 0; i < result.instance_count; ++i) {
uint id = result.instance_id[i];
}
}
キーポイント:
instancingタグによりインスタンスの構造交差を高速化できるようになりますmax_levels<3>インスタンス化レベルの最大数を指定するtriangle_data重心の座標を提供します。curve_data曲線パラメータを提供するassume_geometry_type仮説ステートメントを待つことで交差点のパフォーマンスが向上しますresult.instance_idレイが通過したすべてのインスタンス ID を含む配列
重要ポイント
- 髪/植物レンダラー
- 何をすべきか: 髪の毛と草の三角形モデルを曲線ジオメトリに置き換えます。
- 実行する価値がある理由: メモリが 50% 以上削減され、クローズアップでのエイリアシングがなくなり、構造構築が高速化されます。
- 開始方法: を使用します
MTLAccelerationStructureCurveGeometryDescriptor曲線を定義して選択します.catmullRom最も自然な外観を実現する基本関数
- 大規模オープンワールドゲーム
- やるべきこと: マルチレベル インスタンスを使用して、森林や都市などの繰り返しのシーンを構築します。
- 価値がある理由: モアナのシーンにより数百万のインスタンスが節約され、レベル 3 のインスタンス化により、トレース時間への影響を最小限に抑えながらビルド時間が短縮されます。
- 開始方法: 静的コンテンツをマルチレベルのインスタンス加速構造に置き、動的コンテンツは個別に管理され、動的部分のみが各フレームで再構築されます。
- GPU 駆動のカリング システム
- やるべきこと: GPU 上で錐台カリングと LOD 選択を完了し、インスタンス アクセラレーション構造を直接生成します。
- 実行する価値がある理由:
MTLIndirectInstanceAccelerationStructureDescriptorCPU と GPU のラウンドトリップを排除し、数千のインスタンス シナリオに適しています - 開始方法: 間接インスタンス記述子とインスタンス数をバッファーに出力するコンピューティング シェーダーを作成します。これは、構造構築を高速化するために直接使用されます。
- 実稼働グレードのパス トレーサ
- 内容: 再調整と圧縮を組み合わせて、動的なシーンのレンダリングを最適化します。
- 実行する価値がある理由: 再フィットは変形したメッシュを処理し、圧縮は静的ジオメトリ メモリをリサイクルします。この 2 つを組み合わせることで、各フレームのコストが大幅に削減されます。
- 開始方法: 静的ジオメトリが構築された後、圧縮が実行され、動的ジオメトリがフレームごとに再フィットされ、完全な再構築が定期的に実行されます。
関連セッション
- メタルによるレイ トレーシングの発見 — 2020 年のメタル レイ トレーシングの基礎の紹介
- メタル レイ トレーシングのパフォーマンスを最大化する — レイ トレーシングのパフォーマンス最適化についての詳細な説明
- レイ トレーシングを使用してメタルをレンダリング — レイ トレーシング レンダリングの実践的なコード
- レイ トレーシングを高速化するメタル — 公式サンプル コードとドキュメント
コメント
GitHub Issues · utterances