Upgrade to Pro — share decks privately, control downloads, hide ads and more …

中級グラフィックス入門~効率的なメッシュレット描画~

Avatar for Pocol Pocol
July 22, 2025

 中級グラフィックス入門~効率的なメッシュレット描画~

一般公開バージョン

Avatar for Pocol

Pocol

July 22, 2025
Tweet

More Decks by Pocol

Other Decks in Programming

Transcript

  1. 4

  2. 5

  3. 12

  4. 13 B個 A個 C個 X個 Y個 Z個 ID3D12GraphicsCommandList::Dispatch(A, B, C)

    でコンピュートシェーダを起動。 [numthreads(X, Y, Z)] void main(…) { … }
  5. 14 Wave Active Lane Inactive Lane Wave 一定数のスレッドで構成される最小の処理単位 Waveの数は4, 8,

    16, 32, 64, 128 のいずれか[Microsoft 2021] Lane Wave内の各スレッド Active Lane Wave内で命令を実行するスレッド Inactive Lane Wave内で命令を実行しないスレッド
  6. • Wave内でスレッドに依存して変わる値をベクトル値と呼びます。Wave内でスレッドに依存しない値をスカラー値と呼びます。 • ベクトル値は VGPR (Vector General Purpose Register) に格納される(Wave内で全部共通にならない値)

    スカラー値は SGPR (Scalar General Purpose Register) に格納される(Wave内で全部共通になる値) • スカラー値による分岐は実行ダイバージェンスを発生させないため,別条件の分岐処理も実行するという無駄が無くなる → ベクトル値による分岐に比べるとパフォーマンスペナルティは比較的に少なくなる → スカラー分岐になるように「スカラー化する」(Scalarization)という手法がある → スカラー化するテクニックの1つとして,Wave Intrinsicsを利用する手法がある 16
  7. • Wave IntrinsicsはWave内のLane間でのデータの交換や演算を行うための組み込み関数で, 同期命令なしで,他のLaneの変数を参照したり,演算することが可能 • Wave Intrinsicsの各関数についての説明は, “HLSLのWave Intrinsicsについて“[shikihuiku 2020]

    が非常に参考になる • Wave Intrinsicsを用いた有用なテクニックについては ”Compute shader wave intrinsics tricks”[Sreckovic 2024]が参考になる 17 A B C D float4 sum = tex.Sample(smp, uv, int2(0, 0)) // A + tex.Sample(smp, uv, int2(0, 1)) // B + tex.Sample(smp, uv, int2(1, 0)) // C + tex.Sample(smp, uv, int2(1, 1)); // D float4 ave = sum * 0.25; float4 val = tex.Sample(smp, uv, int2(0, 0)); float3 sum = WaveReadLaneAt(val, 5) // A + WaveReadLaneAt(val, 6) // B + WaveReadLaneAt(val, 7) // C + waveReadLaneAt(val, 8) // D float4 ave = sum * 0.25; LaneId=5 LaneId=6 LaneId=7 LaneId=8
  8. 23 struct PayloadData { uint MeshletIndices[32]; }; groupshared PayloadData s_Payload;

    [numthreads(32, 1, 1)] void main ( uint gtId : SV_GroupThreadId, uint dtId : SV_DispatchThreadId, uint groupId : SV_GroupID ) { bool visible = false; if (dtId < MeshInfo.MeshletCount) { visible = IsVisible(CullingData[dtId], MeshInstance); } if (visible) { uint index = WavePrefixCountBits(visible); s_Payload.MeshletIndices[index] = dtId; } uint visibleCount = WaveActiveCountBits(visible); DispatchMesh(visibleCount, 1, 1, s_Payload); }
  9. 26 [outputtopology(“triangle”)] [numthreads(12, 1, 1)] void main ( in uint

    groupThreadId : SV_GroupThreadID, out vertices MSOutput vertices[8], out indices uint3 indices [12] ) { SetMeshOutputCounts(8, 12); if (groupThreadId < 8) { float4 localPos = InputVertices[groupThreadId]; float4 projPos = mul(ViewProjMatrix, localPos); vertices[groupThreadId].Position = projPos; vertices[groupThreadId].Color = InputColors[groupThreadId]; } indices[groupThreadId] = InputIndices[groupThreadId]; }
  10. • メリット • コンピュートシェーダ的に動作するので,並列動作に強い。 • 柔軟性があり,GPU内で可視性・LOD評価などがしやすい。 • ジオメトリ生成パフォーマンスが向上する。 • デメリット

    • 古いGPUで使用不可。 • デバッグが難しい あるいは 面倒くさい。 • メッシュアセットの生成コンバーターを変える必要がある。 27
  11. 31

  12. 32

  13. 45

  14. 47

  15. 50 const size_t max_vertices = 64; // 最大頂点数を定義. const size_t

    max_triangles = 124; // 最大プリミティブ数を定義. const float cone_weight = 0.0f; // メッシュレット領域を生成. size_t max_meshlets = meshopt_buildMeshletsBound(indices.size(), max_vertices, max_triangles); std::vector<meshopt_Meshlet> meshlets (max_meshlets); std::vector<uint32_t> meshlet_vertices (max_meshlets * max_vertices); std::vector<uint8_t> meshlet_triangles(max_meshlets * max_triangles * 3); // メッシュレットを生成. size_t meshlet_count = meshopt_buildMeshlets( meshlets.data(), meshlet_vertices.data(), meshlet_triangles.data(), indices.data(), indices.size(), &vertices[0].x, vertices.size(), sizeof(Vertex), max_vertices, max_triangles, cone_weight);
  16. 51

  17. 52

  18. 53

  19. 54

  20. 55 [outputtopology(“triangle”)] [numthreads(12, 1, 1)] void main ( in uint

    groupThreadId : SV_GroupThreadID, out vertices MSOutput vertices[8], out indices uint3 indices [12] ) { SetMeshOutputCounts(8, 12); if (groupThreadId < 8) { float4 localPos = InputVertices[groupThreadId]; float4 projPos = mul(ViewProjMatrix, localPos); vertices[groupThreadId].Position = projPos; vertices[groupThreadId].Color = InputColors[groupThreadId]; } indices[groupThreadId] = InputIndices[groupThreadId]; }
  21. 56 [outputtopology(“triangle”)] [numthreads(12, 1, 1)] void main ( in uint

    groupThreadId : SV_GroupThreadID, out vertices MSOutput vertices[8], out indices uint3 indices [12] ) { SetMeshOutputCounts(8, 12); if (groupThreadId < 8) { float4 localPos = InputVertices[groupThreadId]; float4 projPos = mul(ViewProjMatrix, localPos); vertices[groupThreadId].Position = projPos; vertices[groupThreadId].Color = InputColors[groupThreadId]; } indices[groupThreadId] = InputIndices[groupThreadId]; }
  22. 57 [outputtopology(“triangle”)] [numthreads(12, 1, 1)] void main ( in uint

    groupThreadId : SV_GroupThreadID, out vertices MSOutput vertices[8], out indices uint3 indices [12] ) { SetMeshOutputCounts(8, 12); if (groupThreadId < 8) { float4 localPos = InputVertices[groupThreadId]; float4 projPos = mul(ViewProjMatrix, localPos); vertices[groupThreadId].Position = projPos; vertices[groupThreadId].Color = InputColors[groupThreadId]; } indices[groupThreadId] = InputIndices[groupThreadId]; }
  23. • 入力システムセマンティクス値として次の値が利用可能。 • uint3 SV_DispatchThreadID • uint3 SV_GroupThreadID • uint

    SV_GroupIndex • uint3 SV_GroupID • uint SV_ViewID • 出力システムセマンティクス値として次の値が利用可能。 • bool SV_CullPrimitive 58
  24. 59 [outputtopology(“triangle”)] [numthreads(12, 1, 1)] void main ( in uint

    groupThreadId : SV_GroupThreadID, out vertices MSOutput vertices[8], out indices uint3 indices [12] ) { SetMeshOutputCounts(8, 12); if (groupThreadId < 8) { float4 localPos = InputVertices[groupThreadId]; float4 projPos = mul(ViewProjMatrix, localPos); vertices[groupThreadId].Position = projPos; vertices[groupThreadId].Color = InputColors[groupThreadId]; } indices[groupThreadId] = InputIndices[groupThreadId]; }
  25. • メッシュの出力数を設定します。 • 制約 • この関数はシェーダごとに1回だけ呼び出し可能 • 呼び出しは共有出力配列への書き込みが行われる前に実行しないといけない • この関数を呼び出さない場合,ラスタライズ処理は実行されない

    • 最初のアクティブスレッドからの入力値のみが使用される • この関数の呼び出しを先に実行せずに, 出力配列への書き込みに到達する実行パスがあってはならない 60
  26. 61 if (uniform_cond) { SetMeshOutputCounts(…); } if (uniform_cond) { verts[…]

    = …; } if (divergent_cond) { SetMeshOutputCounts(…); } verts[…] = …; if (uniform_cond) { SetMeshOutputCounts(…); } else { SetMeshOutputCounts(…); } verts[…] = …;
  27. 62 [outputtopology(“triangle”)] [numthreads(12, 1, 1)] void main ( in uint

    groupThreadId : SV_GroupThreadID, out vertices MSOutput vertices[8], out indices uint3 indices [12] ) { SetMeshOutputCounts(8, 12); if (groupThreadId < 8) { float4 localPos = InputVertices[groupThreadId]; float4 projPos = mul(ViewProjMatrix, localPos); vertices[groupThreadId].Position = projPos; vertices[groupThreadId].Color = InputColors[groupThreadId]; } indices[groupThreadId] = InputIndices[groupThreadId]; }
  28. 64

  29. 65

  30. • 次の2つの条件を要チェック。 • シェーダモデル6.5がサポートされていること。 • D3D12_MESH_SHADER_TIER_1以上がサポートされていること。 66 bool supportsSM6_5 =

    false; D3D12_FEATURE_DATA_SHADER_MODEL shaderModel = { D3D_SHADER_MODEL_6_5 }; auto hr = pDevice->CheckFeatureSupport(D3D12_FEATURE_SHADER_MODEL, &shaderModel, sizeof(shaderModel)); if (SUCEEDED(hr)) supportSM6_5 = (shaderModel.HighestShaderModel >= D3D_SHADER_MODEL_6_5); bool supportMS = false; D3D12_FEATURE_DATA_D3D12_OPTIONS7 features = {}; auto hr = pDevice->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS7, &features, sizeof(features)); if (SUCEEDED(hr)) supportMS = (features.MeshShaderTier >= D3D12_MESH_SHADER_TIER_1);
  31. • ID3D12Device2::CreatePipelineState()を使って生成する。 • 入力アセンブラ(IA)とStreamOutを無効化する必要がある。 • メッシュシェーダの指定は必須。 67 struct PSO_STREAM {

    CD3DX12_PIPELINE_STATE_STREAM_ROOT_SIGNATURE pRootSignature; CD3DX12_PIPELINE_STATE_STREAM_AS AS; // 増幅シェーダ. CD3DX12_PIPELINE_STATE_STREAM_MS MS; // メッシュシェーダ. … } stream; stream.AS = GetASByteCode(); stream.MS = GetMSByteCode(); … D3D12_PIPELINE_STATE_STREAM_DESC streamDesc = {}; streamDesc.pPipelineStateSubobjectStream = &stream; streamDesc.SizeInBytes = sizeof(stream); ID3D12PipelineState* pPso = nullptr; auto hr = pDevice->CreatePipelineState(&streamDesc, IID_PPV_ARGS(&pPso));
  32. • メッシュシェーダおよび増幅シェーダの起動には, ID3D12GraphicsCommandList6::DispatchMesh() を利用する。 • DispatchMesh()の引数をそれぞれ a, b, cとした場合 •

    3つのスレッドグループの数はそれぞれ64K未満でなければならない。 • a * b * c の積が 222(=4194304) を超えてはならない。 • DispatchMesh()をExecuteIndirect()で呼び出す場合は,以下を使用して設定する。 • D3D12_INDIRECT_ARGUMENT_TYPE_DISPATCH_MESH • D3D12_DISPATCH_MESH_ARGUMENTS 68
  33. 69 auto meshletCount = uint32_t(m_Meshlets.size()); pCmd->ClearRenderTargetView(handleRTV, m_ClearColor, 0, nullptr); pCmd->ClearDepthStencilView(handleDSV,

    D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr); pCmd->OMSetRenderTargets(1, &handleRTV, FALSE, &handleDSV); pCmd->RSSetViewports (1, &m_Viewport); pCmd->RSSetScissorRects(1, &m_ScissorRect); pCmd->SetGraphicsRootSignature(m_RootSignature); pCmd->SetPipelineState(m_PipelineState); pCmd->SetGraphicsRoot32BitConstants (ROOT_B0, 1, &meshletCount, 0); pCmd->SetGraphicsRootConstantBufferView(ROOT_B1, m_MatrixCB.GetGPUVirtualAddress()); pCmd->SetGraphicsRootDescriptorTable (ROOT_T0, m_PosVB .GetHandleGPU()); pCmd->SetGraphicsRootDescriptorTable (ROOT_T1, m_NorVB .GetHandleGPU()); pCmd->SetGraphicsRootDescriptorTable (ROOT_T2, m_TexVB .GetHandleGPU()); pCmd->SetGraphicsRootDescriptorTable (ROOT_T3, m_PrimVB .GetHandleGPU()); pCmd->SetGraphicsRootDescriptorTable (ROOT_T4, m_VertIB .GetHandleGPU()); pCmd->SetGraphicsRootDescriptorTable (ROOT_T5, m_Meshlets.GetHandleGPU()); pCmd->DispatchMesh(meshletCount, 1, 1);
  34. 70

  35. 71

  36. 72

  37. 76

  38. 80

  39. 81

  40. 82

  41. 86 bool CreateLodMeshlets(const ResMeshlets& meshlets, ResLodMeshlets& lodMesh) { // サブセットごとにLODメッシュレットに変換.

    std::vector<SubsetMeshlets> subsets; Conversion(meshlets, subsets); // サブセットのメモリを確保. lodMesh.Subsets.resize(subsets.size()); uint32_t maxLodLevel = 0; // マテリアルごとに処理. for(size_t i=0; i<subsets.size(); ++i) { // 入力データ. std::vector<LodMeshletInfo> input = std::move(subsets[i].Meshlets); // LOD範囲. ResLodRange range = {}; range.Offset = uint32_t(lodMesh.Meshlets.size()); range.Count = uint32_t(input.size()); // マテリアルごとの全LODを含むメッシュレット総数のカウンター. uint32_t totalMeshletCount = uint32_t(input.size()); lodMesh.Subsets[i].MaterialId = subsets[i].MaterialId; lodMesh.Subsets[i].MeshletOffset = range.Offset; lodMesh.Subsets[i].LodRangeOffset = uint32_t(lodMesh.LodRanges.size()); lodMesh.LodRanges.emplace_back(range); // メッシュレットを追加. add_range(lodMesh.Meshlets, input); // LODレベル. uint32_t lodIndex = 1; // 指定数に達するまでループ. while(input.size() > 1 && lodIndex < (kMaxLodLevels - 1)) { …. } // 総数を記録. lodMesh.Subsets[i].MeshletCount = totalMeshletCount; lodMesh.Subsets[i].LodRangeCount = lodIndex; maxLodLevel = max(maxLodLevel, lodIndex); } lodMesh.Positions = meshlets.Positions; lodMesh.Normals = meshlets.Normals; lodMesh.Tangents = meshlets.Tangents; lodMesh.TexCoords = meshlets.TexCoords; lodMesh.BoundingSphere = meshlets.BoundingSphere; lodMesh.MaxLodLevel = maxLodLevel; lodMesh.LodRanges.shrink_to_fit(); // 正常終了. return true; } // 接続性に基づいてメッシュレットをグループ化. auto groups = GroupMeshlets(input); bool isMerged = false; std::vector<LodMeshletInfo> simplifies; for(const auto& group : groups) { // グループ化したものを1つのメッシュにマージして,ポリゴン削減する. auto mergedInfo = SimplifyGroup(group, input, meshlets.Positions, meshlets.VertexIndices); // マージされていなければ以降の処理はスキップ. if (!mergedInfo.IsMerged) continue; float parentError = 0; for(auto& id : group.MeshletIds) { const auto& meshlet = input[id]; parentError = max(parentError, meshlet.GroupError); } // ポリゴン削減されたメッシュを,新しくメッシュレットに分割. auto newOnes = BuildMeshlets(mergedInfo, meshlets.Positions, lodIndex, subsets[i].MaterialId, parentError); const auto groupError = mergedInfo.Error + parentError; for(auto& id : group.MeshletIds) { // 1つ前のLOD(=入力データinput)が今新しく作ったメッシュレットの親になる. const auto offset = lodMesh.LodRanges.back().Offset; auto& parent = lodMesh.Meshlets[offset + id]; parent.ParentError = groupError; parent.ParentBounds = mergedInfo.BoundingSphere; } // 新しいメッシュレットを追加. add_range(simplifies, newOnes); // マージした. isMerged = true; } // 1回もマージされなければおしまい. if (!isMerged) break; // 新しいメッシュレットに差し替える. input = std::move(simplifies); // LOD範囲を設定. range.Offset = uint32_t(lodMesh.Meshlets.size()); range.Count = uint32_t(input.size()); lodMesh.LodRanges.emplace_back(range); totalMeshletCount += range.Count; // LODレベルをカウントアップ. lodIndex++; add_range(lodMesh.Meshlets, input);
  42. 88 idx_t constrainCount = 1; idx_t partsCount = count /

    kMinGroups; idx_t options[METIS_NOPTIONS] = {}; METIS_SetDefaultOptions(options); options[METIS_OPTION_OBJTYPE] = METIS_OBJTYPE_CUT; options[METIS_OPTION_CCORDER] = 1; // Identifies the connected components. options[METIS_OPTION_NUMBERING] = 0; // C-Style. idx_t edgeCut; // METISによって求められるカットの最終コスト. // METISを使ってグループ分けする. auto ret = METIS_PartGraphKway( &count, &constrainCount, xAdjacency.data(), edgeAdjacency.data(), nullptr, // vertex weights. nullptr, // vertex size edgeWeights.data(), &partsCount, nullptr, nullptr, options, &edgeCut, partition.data()); // グループ番号を記録する. std::vector<MeshletGroup> groups(partsCount); for(auto i=0u; i<meshletCount; ++i) { auto groupId = partition[i]; groups[groupId].MeshletIds.push_back(i); } // グループ分けした番号を返却. return groups; EdgeToMeshletMap e2m; MeshletToEdgeMap m2e; // 接続性を構築. BuildMeshletConectivity(meshlets, e2m, m2e); // 接続性が無い場合は,1つのグループとして返す. if (e2m.empty()) { MeshletGroup group; group.MeshletIds.resize(meshletCount); for(auto i=0u; i<meshletCount; ++i) { group.MeshletIds[i] = i; } return std::vector<MeshletGroup>{ group }; } idx_t count = idx_t(meshletCount); // idx_t はMETISで定義されている. std::vector<idx_t> partition; std::vector<idx_t> xAdjacency; std::vector<idx_t> edgeAdjacency; std::vector<idx_t> edgeWeights; partition .resize (count); xAdjacency.reserve(count + 1); for(size_t i=0; i<meshletCount; ++i) { auto offsetEdgeAdjacency = idx_t(edgeAdjacency.size()); for(const auto& edge : m2e[i]) { // 境界エッジからメッシュレットを探す. auto itr = e2m.find(edge); if (itr == e2m.end()) continue; // 接続されているメッシュレットについて処理. const auto& connections = itr->second; for(const auto& connectedMeshlet : connections) { // 自分自身ならスキップ. if (connectedMeshlet == i) continue; // 隣接エッジリストに登録されているかチェック. auto edgeItr = std::find( edgeAdjacency.begin() + offsetEdgeAdjacency, edgeAdjacency.end(), connectedMeshlet); // 未登録. if (edgeItr == edgeAdjacency.end()) { edgeAdjacency.emplace_back(idx_t(connectedMeshlet)); edgeWeights .emplace_back(1); } // 登録済み. else { auto d = std::distance(edgeAdjacency.begin(), edgeItr); assert(d >= 0); edgeWeights[d]++; } } } // メッシュレット開始番号を登録. xAdjacency.push_back(offsetEdgeAdjacency); } xAdjacency.push_back(idx_t(edgeAdjacency.size()));
  43. 89 void BuildMeshletConectivity ( const std::vector<LodMeshletInfo>& meshlets, EdgeToMeshletMap& edge2Meshlet, MeshletToEdgeMap&

    meshlet2Edge ) { // エッジリストを構築. for(size_t mId = 0; mId < meshlets.size(); ++mId) { const auto& meshlet = meshlets[mId]; for(size_t pId = 0; pId < meshlet.Primitives.size(); ++pId) { const auto& prim = meshlet.Primitives[pId]; for(size_t j=0; j<3; ++j) { const auto& e0 = prim.v[j]; const auto& e1 = prim.v[(j + 1) % 3]; if (e0 == e1) continue; Edge edge = { std::min(e0, e1), std::max(e0, e1) }; edge2Meshlet[edge].push_back(mId); meshlet2Edge[mId].emplace_back(edge); } } } // メッシュレットが1つでないものは複数接続されていてボーダーではないので,削除する. { remove_if(edge2Meshlet, [&](const auto& pair) { return pair.second.size() != 1; }); } } using Edge = std::pair<uint32_t, uint32_t>; struct EdgeHash { size_t operator() (const Edge& value) const { const auto hasher = std::hash<uint32_t>{}; return hasher(value.first) ^ hasher(value.second); } }; using EdgeToMeshletMap = std::unordered_map<Edge, std::vector<size_t>, EdgeHash>; using MeshletToEdgeMap = std::unordered_map<size_t, std::vector<Edge>>;
  44. • グループ化された各メッシュレットのインデックスバッファを1つにマージ • マージされたメッシュに対して meshoptimizer で簡略化(減ポリ) • この際に,境界エッジを固定化するオプション(meshopt_SimplifyLockBorder)を有効化 • 実装する際の注意点として,meshopt_simplifyで渡す位置座標は全て巡回が実行

    そのため,巡回数が少なくなるようにマージ時に数を減らした位置座標を作っておくと良い • 後述するLOD判定のために,meshopt_SimplifyErrorAbsolute を指定して,グループを簡略化する際の 元形状との絶対誤差を取得しておく 90
  45. 91 uint32_t options = meshopt_SimplifyLockBorder | meshopt_SimplifyErrorAbsolute; float groupError =

    0.0f; // Quadratic Error Metrics. float permissiveError = 0.03f; // 許容誤差 (3%未満とする). assert(targetIndexCount > 0); std::vector<uint32_t> indices(mergedIdx.size()); auto indexCount = meshopt_simplify( indices.data(), mergedIdx.data(), mergedIdx.size(), &mergedPos[0].x, mergedPos.size(), sizeof(asdx::Vector3), targetIndexCount, permissiveError, options, &groupError); indices.resize(indexCount); mergedIdx.clear(); mergedIdx.shrink_to_fit(); mergedPos.clear(); mergedPos.shrink_to_fit(); // エラー値を設定. result.Error = groupError; // 元の頂点インデックス番号を復元する. result.Indices.reserve(indices.size()); for(const auto& index : indices) { auto vertId = dict[index]; result.Indices.emplace_back(vertId); } // エラーが 0 なければ,マージされて変形した. result.IsMerged = (clusterError > 0.0f); std::unordered_map<uint32_t, uint32_t> dict; // mergeIndex <---> verteIndex の辞書. // グループごとにマージする. std::vector<uint32_t> mergedIdx; std::vector<Vector3> mergedPos; // 最大数でメモリ確保. mergedIdx.reserve(group.MeshletIds.size() * 256 * 3); mergedPos.reserve(group.MeshletIds.size() * 256); for(size_t i=0; i<group.MeshletIds.size(); ++i) { const auto& meshletId = group.MeshletIds[i]; const auto& meshlet = meshlets[meshletId]; for(size_t j=0u; j<meshlet.Primitives.size(); ++j) { const auto& tris = meshlet.Primitives[j]; // マージで壊れたやつは取り除く. if (tris.x == tris.y && tris.x == tris.z) continue; for(auto k=0; k<3; ++k) { auto vertId = meshlet.VertIndices[tris.v[k]]; auto index = uint32_t(mergedIdx.size()); mergedIdx.emplace_back(index); mergedPos.emplace_back(positions[vertId]); dict.try_emplace(index, vertId); } } } mergedIdx.shrink_to_fit(); mergedPos.shrink_to_fit(); // 重複データを削る. { std::vector<uint32_t> remap(mergedIdx.size()); auto vertexCount = meshopt_generateVertexRemap( remap.data(), mergedIdx.data(), mergedIdx.size(), mergedPos.data(), mergedPos.size(), sizeof(asdx::Vector3)); // 位置座標をリマップ. { std::vector<Vector3> pos(vertexCount); meshopt_remapVertexBuffer(pos.data(), &mergedPos[0].x, mergedPos.size(), sizeof(asdx::Vector3), remap.data()); mergedPos = std::move(pos); } // 辞書をリマップ. { std::unordered_map<uint32_t, uint32_t> remapDict; for(const auto& pair : dict) { auto newIdx = pair.first; auto vertId = pair.second; newIdx = remap[newIdx]; remapDict.try_emplace(newIdx, vertId); } dict = std::move(remapDict); } // 頂点インデックスをリマップ. mergedIdx = std::move(remap); }
  46. • 2次誤差尺度(QEM: Quadric Error Metrics)は 頂点ー平面 の2乗距離の総和を表す。 93 𝐸𝑣 =

    ෍ 𝑝∈𝑝𝑙𝑎𝑛𝑒𝑠(𝑣) 𝑝 ∙ 𝑣 2 = ෍ 𝑝 𝑣⊺𝑝 𝑝⊺𝑣 = 𝑣⊺ ෍ 𝑝 𝑝𝑝⊺ 𝑣 = 𝑣⊺ ෍ 𝑝 𝑄𝑝 𝑣 = 𝑣⊺𝑄𝑣 𝑣 頂点を𝑣 = 𝑥, 𝑦, 𝑧, 1 とし, 𝑄𝑣 = 𝑎2 𝑎𝑏 𝑎𝑐 𝑎𝑑 𝑎𝑏 𝑏2 𝑏𝑐 𝑏𝑑 𝑎𝑐 𝑏𝑐 𝑐2 𝑐𝑑 𝑎𝑑 𝑏𝑑 𝑐𝑑 𝑑2 となる。 平面方程式 𝑝 = 𝑎𝑥 + 𝑏𝑦 + 𝑐𝑧 + 𝑑 を 𝑝 = (𝑎, 𝑏, 𝑐, 𝑑) と表すと,
  47. 95 float ProjectError(float3 center, float radius, float screenScaleY) { if

    (isinf(radius)) return radius; // 未初期化データ(= LOD0 の親判定用). // screenScaleY = renderTargetHeight * 0.5f * (1.0f / tan(fov * 0.5f)) とします. float d2 = dot(center, center); return screenScaleY * radius / sqrt(d2 - radius * radius); } bool IsVisibleLod(MeshletInfo meshlet, float pixelErrorThreshold, float4x4 localToView, float screenScaleY) { float4 projGroupSphere = mul(localToView, float4(meshlet.GroupBoundingSphere.xyz, 1.0f)); float projGroupError = ProjectError(projGroupSphere.xyz, max(meshlet.GroupError, 1e-9f), screenScaleY); float4 projParentSphere = mul(localToView, float4(meshlet.ParentBoundingSphere.xyz, 1.0f)); float projParentError = ProjectError(projParentSphere.xyz, max(meshlet.ParentError, 1e-9f), screenScaleY); return (projGroupError <= pixelErrorThreshold && pixelErrorThreshold < projParentError); } 𝑑 𝑙 𝑟 ℎ
  48. 96

  49. 106

  50. • 視錐台カリング • 寄与カリング • 法錐カリング • 背面カリング • 微小プリミティブカリング

    • オクルージョンカリング …などなど。 • 最近ではNeural Networkを用いてカリングをする試みもある [Yu 2024; Yu 2025] 107
  51. • メッシュシェーダ実行直前にカリングを実行 • カリング結果に応じて,メッシュシェーダを起動するかどうかを 制御できるため,結果を保存しなくても良いので省メモリ化 • 描画するメッシュレット数をカウントして,ペイロードとしてメッシュシェーダに渡す 112 struct Payload;

    groupshread Payload g_Payload; [numthreads(32, 1, 1)] void main(uint dispatchId : SV_DispatchThreadID) { … DispatchMesh(visibleCount, 1, 1, g_Payload); } [outputtopology("triangle")] [numthreads(128, 1, 1)] void main( uint threadId : SV_GroupThreadID, uint groupId : SV_GroupID, in payload Payload payload, out vertices MSOutput vertices[256], out indices uint3 indices [256]) { … }
  52. 113 bool Contains(float4 planes[6], float4 sphere) { // sphereは事前に位置座標がワールド変換済み,半径もスケール適用済みとします. float4

    center = float4(sphere.xyz, 1.0f); for(int i=0; i<6; ++i) { if (dot(center, planes[i]) < -sphere.w) return false; // カリングする. } // カリングしない. return true; }
  53. 114 [numthreads(32, 1, 1)] void main(uint dispatchId : SV_DispatchThreadID) {

    bool visible = false; if (dispatchId < g_Constants.MeshletCount) { MeshletInfo info = g_Meshlets[dispatchId]; MeshInstanceParam instance = g_MeshInstances[g_Constants.InstanceId]; visible = IsVisible( info, g_TransParam.CameraPos, g_TransParam.Planes, instance.CurrWorld, g_TransParam.ViewProj); } if (visible) { uint index = WavePrefixCountBits(visible); g_Payload.MeshletIndices[index] = dispatchId; } uint visibleCount = WaveActiveCountBits(visible); DispatchMesh(visibleCount, 1, 1, g_Payload); } Wave Intrinsics
  54. • 自身のLane Index未満のActive Laneで引数にtrueを指定した個数を返却 • uint WavePrefixCountBits(bool bBit); 115 Wave

    bool visible = true; WavePrefixCountBits(visible) return 0; bool visible = false; WavePrefixCountBits(visible) return 1; bool visible = true; WavePrefixCountBits(visible) return 1; bool visible = true; WavePrefixCountBits(visible) return 2; bool visible = false; WavePrefixCountBits(visible) return 2; bool visible = true; WavePrefixCountBits(visible) return 3; Inactive Lane Active Lane
  55. • 引数にtrueを指定したLaneの数を,すべてのActive Laneに返却 • uint WaveActiveCountsBit(bool bBit); 116 Wave WaveActiveCountBits(true);

    WaveActiveCountBits(false); WaveActiveCountBits(true); WaveActiveCountBits(true); WaveActiveCountBits(true); WaveActiveCountBits(false); true WaveActiveCountBits()
  56. 117 [numthreads(32, 1, 1)] void main(uint dispatchId : SV_DispatchThreadID) {

    bool visible = false; if (dispatchId < g_Constants.MeshletCount) { MeshletInfo info = g_Meshlets[dispatchId]; MeshInstanceParam instance = g_MeshInstances[g_Constants.InstanceId]; visible = IsVisible( info, g_TransParam.CameraPos, g_TransParam.Planes, instance.CurrWorld, g_TransParam.ViewProj); } if (visible) { uint index = WavePrefixCountBits(visible); g_Payload.MeshletIndices[index] = dispatchId; } uint visibleCount = WaveActiveCountBits(visible); DispatchMesh(visibleCount, 1, 1, g_Payload); }
  57. 123 𝛼 cos 𝜋 2 + 𝛼 = − sin

    𝛼 dot(-viewDir, coneAxis) > -sin(coneAngle)) cos 𝜃 > cos 𝜋 2 + 𝛼 𝜃 coneAngle
  58. 124 bool IsVisible(MeshletInfo info, float3 cameraPos, float4 planes[6], float4x4 world,

    float4x4 viewProj) { // [-1, 1] に展開. float4 normalCone = UnpackUnorm(info.NormalCone) * 2.0f - 1.0f; // 角度がゼロかどうか判定. if (normalCone.w <= 1e-6f) return false; // ワールド空間に変換. float4 sphere = TransformSphere(info.BoundingSphere, world); float3 axis = normalize(mul((float3x3)world, normalCone.xyz)); // 視錐台カリング. if (!Contains(planes, sphere)) return false; // 寄与カリング. if (ContributionCulling(sphere, viewProj)) return false; // 視線ベクトルを求める. float3 viewDir = normalize(sphere.xyz - cameraPos); // 法錐カリング. if (NormalConeCulling(float4(axis, normalCone.w), viewDir)) return false; // カリングしない. return true; } float4 TransformSphere(float4 sphere, float4x4 world) { // 中心をワールド変換. float3 center = mul((float3x3)world, sphere.xyz); // 各軸の長さの2乗値を求める. float sx = dot(world._11_12_13, world._11_12_13); float sy = dot(world._21_22_23, world._21_22_23); float sz = dot(world._31_32_33, world._31_32_33); // 最も大きいものをスケールとして採用. float scale = sqrt(max(sx, max(sy, sz))); return float4(center, sphere.w * scale); }
  59. 125

  60. • スクリーン空間に変換し,[0, 1]の範囲内に収まっているかどうかチェックし, 収まっていなかったらカリングする • スクリーン空間でのAABBを求めて,チェック 129 float2 minAABB =

    1.0f.xx; float2 maxAABB = 0.0f.xx; for(uint i=0; i<3; ++i) { float2 posSS = (projPos[i].xy / projPos[i].w); posSS = posSS * 0.5f + 0.5f; // [0, 1]に変換. minAABB = min(minAABB, posSS); maxAABB = max(maxAABB, posSS); } culled = any(minAABB > 1.0) || any(maxAABB < 0.0); 0.0 1.0 1.0 maxAABB.x < 0.0 minAABB.x > 1.0 minAABB.y > 1.0 maxAABB.y < 0.0
  61. • 前述したように,SetMeshOutputCounts()の数は動的に変更できない (仕様に沿っていないため,コンパイルエラーなどになる) • そのため,SV_CullPrimitive セマンティクスを利用してカリング • Primitives属性として,プリミティブ単位で出力をすればいい 131 struct

    PrimOutput { float4 Color : COLOR0; bool Culling : SV_CullPrimitive; }; [outputtopology("triangle")] [numthreads(128, 1, 1)] void main ( uint threadId : SV_GroupThreadID, uint groupId : SV_GroupID, in payload Payload payload, out vertices MSOutput vertices[256], out indices uint3 indices [256], out primitives PrimOutput prims [256] )
  62. 132 [outputtopology("triangle")] [numthreads(128, 1, 1)] void main ( uint threadId

    : SV_GroupThreadID, uint groupId : SV_GroupID, in payload Payload payload, out vertices MSOutput vertices[256], out indices uint3 indices [256], out primitives PrimOutput prims [256] ) { uint meshletIndex = payload.MeshletIndices[groupId]; MeshletInfo info = g_Meshlets[meshletIndex]; SetMeshOutputCounts(info.VertexCount, info.PrimitiveCount); MeshInstanceParam instanceParam = g_MeshInstances[g_Constants.InstanceId]; for(uint i=0; i<2; ++i) { uint id = threadId + i * 128; if (id < info.PrimitiveCount) { // プリミティブインデックスを設定. uint3 tris = GetPrimitiveIndex(id + info.PrimitiveOffset); float3 posW [3]; // カリング用ワールド位置座標. float2 posSS[3]; // カリング用スクリーン空間座標. for (uint j = 0; j < 3; ++j) { /* 頂点データ処理の実装 */ } // カリング処理. bool culled = false; culled |= IsBackFaceOrZeroArea(posW, g_TransParam.CameraPos); culled |= PrimitiveCulling(posSS); // プリミティブアトリビュートを出力. PrimOutput output = (PrimOutput) 0; output.Color.rgb = ToSRGB(HueToRGB(groupId * 1.71f)); output.Color.a = 1.0f; output.Culling = culled; prims[id] = output; } } } uint idx = tris[j]; // 頂点数を超える場合は処理しない. if (idx >= info.VertexCount) continue; float4 localPos = float4(g_Positions[idx + info.VertexOffset], 1.0f); float4 worldPos = mul(instanceParam.CurrWorld, localPos); float4 viewPos = mul(view, worldPos); float4 projPos = mul(proj, viewPos); float3 localNormal = g_Normals[idx + info.VertexOffset]; float3 worldNormal = normalize(mul((float3x3) instanceParam.CurrWorld, localNormal)); MSOutput output; output.Position = projPos; output.Normal = worldNormal; output.TexCoord = g_TexCoords[idx]; vertices[idx] = output; posW [j] = worldPos.xyz; posSS[j] = (projPos.xy / projPos.w) * 0.5f + 0.5f; bool PrimitiveCulling(float2 posSS[3]) { bool culled = false; float2 mini = 1.0f.xx; float2 maxi = 0.0f.xx; // 視錐台カリング. for (uint i = 0; i < 3; ++i) { mini = min(mini, posSS[i]); maxi = max(maxi, posSS[i]); } culled |= (any(mini > 1.0f) || any(maxi < 0.0f)); // カリングする. // 微小プリミティブカリング. maxi *= g_TransParam.RenderTargetSize.xy; mini *= g_TransParam.RenderTargetSize.xy; culled |= any(round(mini) == round(maxi)); // カリングする. return culled; }
  63. 135

  64. • Octahedron Encoding [Cigolle 2014] • 球面上の法線を八面体にマッピング 136 float2 OctWrap(float2

    v) { return (1.0f – abs(v.yx)) * select(v.xy >= 0.0f, 1.0f, -1.0f); } float2 PackNormal(float3 v) { float3 n = normal / (abs(v.x) + abs(v.y) + abs(v.z)); n.xy = select(n.z >= 0.0f, n.xy, OctWrap(n.xy)); return n.xy * 0.5f + 0.5f; } float3 UnpackNormal(float2 v) { float2 enc = v * 2.0f – 1.0f; float3 n = float3(enc, 1.0f – abs(enc.x) – abs(enc.y)); float t = saturate(-n.z); n.xy += select(n.xy >= 0.0f, -t, t); return normalize(n); }
  65. 138 struct PackedNormal { float2 normal; // 圧縮済み法線ベクトル. uint sign;

    // 符号ビット. }; PackedNormal SignedOctEncode(float3 n) { PackedNormal result; n /= (abs(n.x) + abs(n.y) + abs(n.z)); result.normal.y = n.y * 0.5f + 0.5f; result.normal.x = n.x * 0.5f + result.normal.y; result.normal.y = n.x * -0.5f + result.normal.y; result.sign = (uint)saturate(n.z * FLT_MAX); return result; } float3 SignedOctDecode(PackedNormal v) { float3 result; result.x = (v.normal.x – v.normal.y); result.y = (v.normal.x + v.normal.y) – 1.0f; result.z = (float)v.sign * 2.0f – 1.0f; result.z = result.z * (1.0f – abs(result.x) – abs(result.y)); return normalize(result); }
  66. 141 float EncodeDiamond(float2 v) { float m = abs(v.x) +

    abs(v.y); if (m == 0.0f) return 0.0f; float x = v.x / m; float s = sign(v.x); return –s * 0.25 * x + 0.5f + s * 0.25f; } float EncodeTangent(float3 normal, float3 tangent) { float3 t1; if (abs(normal.y) > abs(normal.z)) t1 = float3(normal.y, -normal.x, 0.0f); else t1 = float3(normal.z, 0.0f, -normal.x); t1 = normalize(t1); float3 t2 = cross(t1, normal); float2 packedTangent = float2( dot(tangent, t1), dot(tangent, t2)); return EncodeDiamond(packedTangent); } float2 DecodeDiamond(float v) { if (v == 0.0f) return float2(0.0f, 0.0f); float2 result; float s = sign(v – 0.5f); result.x = -s * 4.0f * v + 1.0f + s * 2.0f; result.y = s * (1.0f – abs(result.x)); return normalize(result); } float3 DecodeTangent(float3 normal, float diamondTangent) { float3 t1; if (abs(normal.y) > abs(normal.z)) t1 = float3(normal.y, -normal.x, 0.0f); else t1 = float3(normal.z, 0.0f, -normal.x); t1 = normalize(t1); float3 t2 = cross(t1, normal); float2 packedTangent = DecodeDiamond(diamondTangent); return packedTangent.x * t1 + packedTangent.y * t2; }
  67. 142 float EncodeDiamond(float2 v) { float m = abs(v.x) +

    abs(v.y); float x = v.x / m; float s = sign(v.x); return -s * 0.25f * x + 0.5f + s * 0.25f; } float EncodeTangent(float3 normal, float3 tangent) { float3 t1; if (abs(normal.y) > abs(normal.z)) t1 = float3(normal.y, -normal.x, 0.0f); else t1 = float3(normal.z, 0.0f, -normal.x); t1 = normalize(t1); float3 t2 = cross(t1, normal); float2 packedTangent = float2( dot(tangent, t1), dot(tangent, t2)); return EncodeDiamond(packedTangent); } float2 DecodeDiamond(float v) { float2 result; float s = sign(v – 0.5f); result.x = -s * 4.0f * v + 1.0f + s * 2.0f; result.y = s * (1.0f – abs(v.x)); return normalize(v); } float3 DecodeTangent(float3 normal, float diamondTangent) { float3 t1; if (abs(normal.y) > abs(normal.z)) t1 = float3(normal.y, -normal.x, 0.0f); else t1 = float3(normal.z, 0.0f, -normal.x); t1 = normalize(t1); float3 t2 = cross(t1, normal); float2 packedTangent = DecodeDiamond(diamondTangent); return packedTangent.x * t1 + packedTangent.y * t2; }
  68. • Signed Octahedron Encodingと同じで精度改善可能 • 符号ビットを持たせることで,精度を倍に 143 SignedDiamond EncodeDiamond(float2 p)

    { SignedDiamond result; float m = abs(p.x) + abs(p.y); if (m == 0.0f) { result.sign = false; result.angle = 0.0f; return result; } float x = p.x / m; result.sign = (p.y >= 0.0f) ? true : false; result.angle = x * 0.5f + 0.5f; return result; } struct SignedDiamond { float angle; // エンコード済み角度. bool sign; // true = 1.0f, false = -1.0f. }; float2 DecodeDiamond(SignedDiamond p) { if(p.angle == 0.0f && !p.sign) return float2(0.0f, 0.0f); float2 result; float s = p.sign ? 1.0f : -1.0f; result.x = 2.0f * p.angle – 1.0f; result.y = s * (1.0f – abs(result.x)); return normalize(result); }
  69. • Signed Octahedron Encodingと同じで精度改善可能 • 符号ビットを持たせることで,精度を倍に 144 SignedDiamond EncodeDiamond(float2 p)

    { SignedDiamond result; float m = abs(p.x) + abs(p.y); float x = p.x / m; result.sign = (p.y >= 0.0f) ? true : false; result.angle = x * 0.5f + 0.5f; return result; } struct SignedDiamond { float angle; // エンコード済み角度. bool sign; // true = 1.0f, false = -1.0f. }; float2 DecodeDiamond(SignedDiamond p) { float2 result; float s = p.sign ? 1.0f : -1.0f; result.x = 2.0f * p.angle – 1.0f; result.y = s * (1.0f – abs(result.x)); return normalize(result); }
  70. • ShaderX5に計算により復元する方法が紹介されている[Schüler2007] • 微分値を用いてピクセルシェーダにて接線・従接線データを求める • 改良方法が[Schüler2013]で紹介されている 145 float3x3 tangent_frame(float3 N,

    float3 p, float2 uv) { float3 dp1 = ddx(p); float3 dp2 = ddy(p); float2 duv1 = ddx(uv); float2 duv2 = ddy(uv); float2x3 M = float2x3(dp1, dp2); float3 T = mul(float2(duv1.x, duv2.x), M); float3 B = mul(float2(duv1.y, duv2.y), M); return float3x3(normalize(T), normalize(B), N); } float3x3 cotangent_frame(float3 N, float3 p, float2 uv) { float3 dp1 = ddx(p); float3 dp2 = ddy(p); float2 duv1 = ddx(uv); float2 duv2 = ddy(uv); float3 dp2perp = cross(dp2, N); float3 dp1perp = cross(N, dp1); float3 T = dp2perp * duv1.x + dp1perp * duv2.x; float3 B = dp2perp * duv1.y + dp1perp * duv2.y; float invmax = rsqrt(max(dot(T, T), dot(B, B)); return float3x3(T * invmax, B * invmax, N); }
  71. 148 const auto& kMesh = …; const auto kTargetBits =

    16; const float3 kGlobalMin = kMesh.GetAABB().min; const float3 kGlobalDelta = kMesh.GetAABB().max – kGlobalMin; float3 largestMeshletDelta = 0.0f.xxx; for(const auto& meshlet : kMesh.GetMeshlets()) { const float3 delta = meshlet.GetAABB().max – mehlet.GetAABB().min; largestMeshletDelta = max(largestMeshletDelta, delta); } const float3 kMeshletQuantizationStep = largestMeshletDelta / ((1 << kTargetBits) – 1); const uint32_t3 kGlobalQuantizationSattes = kGlobalDelta / kMeshletQuantizationStep; const float3 kQuantizationFactor = (kGlobalQunaizationSates – 1) / kGlobalDelta; const float3 kDequantizationFactor = kGlobalDelta / (kGlobalQunatizationStates – 1); for(const auto& mehlet : kMesh.GetMeshlets()) { const uint32_t3 kQuantizationMeshletOffset = uint32_t3(meshlet.GetAABB().min – kGlobalMin) * kQuantizaitonFactor + 0.5f); for(const auto& vertex : meshlet.GetVertices()) { const float3 value = vertex; const uint32_t3 kGlobalQuantizedValue = uint32_t3((value – kGlobalMin) * kQuantizationFactor + 0.5f); const uint32_t3 kLocalQuantizedValue = kGlobalQuantizedValue – kQuantizedMeshletOffset; output.quantizedPosition[i++] = uint16_t3(kLocalQuantizedValue); // ローカル量子化値を保存. } outMeshlet.OffsetPosition = kQuantizationMeshletsOffset; // 各メッシュレットについて軸ごとの量子化メッシュレットオフセットを保存. } // 逆量子化のため,係数とkGlobalMinを保存。 output.Base = kGlobalMin; output.Factor = kDequantizationFactor;
  72. • 割と愚直な実装ですが,実装例は下記の通り 151 uint3 GetPrimitiveIndex(uint triangleIndex) { // 3バイト単位で三角形のインデックスが格納されている. uint

    baseByteOffset = triangleIndex * 3; // 各インデックスへのバイトオフセット. uint3 byteOffset = uint3(baseByteOffset + 0, baseByteOffset + 1, baseByteOffset + 2); // 各インデックスが含まれる4バイト境界を計算. uint3 alignedOffset = byteOffset & (~3u).xxx; // バイトをまたぐ場合を考慮して,8バイトロード. uint2 raw = g_Primitives.Load2(alignedOffset.x); // 必要なデータを決定. uint3 data; data.x = raw.x; data.y = (alignedOffset.y != alignedOffset.x) ? raw.y : data.x; data.z = (alignedOffset.z != alignedOffset.y) ? raw.y : data.y; // シフト量 uint3 shift = (byteOffset & (3u).xxx) * 8; // 頂点インデックスを抽出. return (data >> shift) & 0xFF.xxx; }
  73. 167 Depth Prepass G-Buffer Pass Lighting Pass V-Buffer Pass Classify

    Pass Lighting Pass Lighting Pass Lighting Pass Lighting Pass
  74. 168 Depth Prepass G-Buffer Pass Lighting Pass V-Buffer Pass Classify

    Pass Lighting Pass Lighting Pass Lighting Pass Lighting Pass
  75. 169

  76. • 今回の実装では,Visibility Bufferの描画にメッシュシェーダを使用 • その他の処理については、すべてコンピュートシェーダで実装 • 今回コンピュートシェーダを実装方式に選んだ理由としては, 非同期コンピュートに処理を逃がせるという利点があるため • 他の実装法としては,[Karis

    2021]や[Mishima 2025],そして[Doghramachi2017]のように 深度バッファをハックして,タイル描画を行い必要なシェーダを起動させるという方法がある 170 V-Buffer Pass Classify Pass Lighting Pass Lighting Pass Lighting Pass Lighting Pass
  77. 172

  78. 173

  79. • メッシュシェーダで描画するために必要なインスタンスIDや三角形IDなどは揃っているので, これをそのままピクセルシェーダに渡し,V-Bufferに書き込む • 今回の実装では,R32G32_UINTフォーマットとして,IDを書き出す 174 struct MSOutput { float4

    Position : SV_Position; uint InstanceId : INSTANCE_ID; uint MeshletId : MESHLET_ID; uint PrimitiveId : PRIMITIVE_ID; }; struct PSOutput { uint2 VisBuffer : SV_Target0; }; PSOutput main(const MSOutput input) { PSOutput output = (PSOutput)0; output.VisBuffer.x = ((input.InstanceId & 0xffffff) << 8) | (input.PrimitiveId & 0xff); output.VisBuffer.y = input.MeshletId; return output; }
  80. 175

  81. 176

  82. • シェーダごとに必要なピクセルだけを起動させてライティング処理を行いたい • そのために,シェーダ番号とピクセル番号をペアにした処理対象リストを作成 • 処理対象リストからシェーダごとに起動が必要なピクセル数をカウントし,オフセットに • 算出したオフセットを利用して,シェーダごとに固まるようにソート処理を実行 181 PixelId

    : 0 ShaderId : 2 PixelId : 1 ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 2 3 2 1 PixelId : 0 ShaderId : 2 PixelId : 4 ShaderId : 2 PixelId : 1 ShaderId : 3 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 6 ShaderId : 4 PixelId : 3 ShaderId : 5 2 0 5 7 8
  83. 182 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 0 0 0 0 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 2 5 7 8 0 6 0
  84. 183 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 1 0 0 0 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 2 5 7 8 0 6 PixelId : 0 ShaderId : 2
  85. 184 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 1 0 0 0 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 2 5 7 8 0 6 PixelId : 0 ShaderId : 2 2
  86. 185 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 1 1 0 0 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 2 5 7 8 0 6 PixelId : 0 ShaderId : 2 PixelId : 1 ShaderId : 3
  87. 186 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 1 1 0 0 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 2 5 7 8 0 6 PixelId : 0 ShaderId : 2 PixelId : 1 ShaderId : 3 5
  88. 187 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 1 1 1 0 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 2 5 7 8 0 6 PixelId : 0 ShaderId : 2 PixelId : 1 ShaderId : 3 PixelId : 2 ShaderId : 4
  89. 188 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 1 1 1 0 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 2 5 7 8 0 6 PixelId : 0 ShaderId : 2 PixelId : 1 ShaderId : 3 PixelId : 2 ShaderId : 4 7
  90. 189 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 1 1 1 1 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 2 5 7 8 0 6 PixelId : 0 ShaderId : 2 PixelId : 1 ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5
  91. 190 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 1 1 1 1 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 2 5 7 8 0 6 PixelId : 0 ShaderId : 2 PixelId : 1 ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 1
  92. 191 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 2 1 1 1 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 2 5 7 8 0 6 PixelId : 0 ShaderId : 2 PixelId : 1 ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2
  93. 192 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 2 1 1 1 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 2 5 7 8 0 6 PixelId : 0 ShaderId : 2 PixelId : 1 ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 3
  94. 193 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 2 2 1 1 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 2 5 7 8 0 6 PixelId : 0 ShaderId : 2 PixelId : 1 ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3
  95. 194 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 2 2 1 1 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 2 5 7 8 0 6 PixelId : 0 ShaderId : 2 PixelId : 1 ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 6
  96. 195 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 2 2 2 1 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 2 5 7 8 0 6 PixelId : 0 ShaderId : 2 PixelId : 1 ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 6 ShaderId : 4
  97. 196 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 2 2 2 1 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 2 5 7 8 0 6 PixelId : 0 ShaderId : 2 PixelId : 1 ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 6 ShaderId : 4 4
  98. 197 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 2 3 2 1 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 2 5 7 8 0 6 PixelId : 0 ShaderId : 2 PixelId : 1 ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4
  99. 198 [numthreads(8, 8, 1)] void main(uint3 dispatchId : SV_DispatchThreadID, uint

    groupIndex : SV_GroupIndex) { // ピクセル座標. const uint2 pixelId = RemapLane8x8(dispatchId.xy, groupIndex); const uint flatPixelId = (pixelId.x & 0xFFFF) | ((pixelId.y & 0xFFFF) << 16); // レンダーターゲットサイズを超えている場合は処理しない. if (any(pixelId >= g_Constants.RenderTargetSize)) return; // ビジビリティバッファからメッシュレットIDを取得. uint meshletId = g_VisibilityBuffer[pixelId].y; if (meshletId == 0) // 0は無効値(=書き込みされていない扱い). return; meshletId--; // 1始まりなので0始まりに戻す. // メッシュレット情報からシェーダIDを取得. const uint shaderId = (g_Meshlets[meshletId].MaterialId >> 16) & 0xFFFF; // シェーダ数を数える. InterlockedAdd(g_ShaderIdCounter[shaderId], 1); // 書き込み番号を取得. int index = 0; InterlockedAdd(g_WorkListCounter[0], 1, index); WorkItem item; item.ShaderId = shaderId; item.FlatPixelId = flatPixelId; g_WorkList[index] = item; } struct Constants { uint2 RenderTargetSize; uint MaxShaderCount; uint Reserved; }; struct WorkItem { uint ShaderId; uint FlatPixelId; }; struct MeshletInfo { uint VertexOffset; uint VertexCount; uint PrimitiveOffset; uint PrimitiveCount; uint NormalCone; float4 BoundingSphere; uint MaterialId; // hi-16bit : ShaderId, low-16bit : MaterialId. }; ConstantBuffer<Constants> g_Constants : register(b0); StructuredBuffer<MeshletInfo> g_Meshlets : register(t0); Texture2D<uint2> g_VisibilityBuffer : register(t1); RWStructuredBuffer<uint> g_ShaderIdCounter : register(u0); RWStructuredBuffer<WorkItem> g_WorkList : register(u1); RWStructuredBuffer<uint> g_WorkListCounter : register(u2); RWTexture2D<float4> g_RenderTarget : register(u3);
  100. 199 struct Constants { uint MaxShaderCount; uint3 Reserved; }; ConstantBuffer<Constants>

    g_Constants : register(b0); RWStructuredBuffer<uint> g_ShaderIdCounter : register(u0); RWStructuredBuffer<uint> g_ShaderIdOffset : register(u1); [numthreads(32, 1, 1)] void main(uint3 dispatchId : SV_DispatchThreadID, uint3 groupThreadId : SV_GroupThreadID) { const uint maxShaderCount = min(g_Constants.MaxShaderCount, MAX_SHADERS); for(uint i=0; i<maxShaderCount; i+= 32) { uint index = groupThreadId.x + i; if (index >= maxShaderCount) break; uint offset = WavePrefixSum(g_ShaderIdCounter[index]); g_ShaderIdOffset [index] = offset; g_ShaderIdCounter[index] = 0; // カウンターを再使用するためにリセット. } }
  101. 200 [numthreads(32, 1, 1)] void main(uint3 dispatchId : SV_DispatchThreadID) {

    const uint maxShaderCount = min(g_Constants.MaxShaderCount, MAX_SHADERS); // ワークリスト数を取得. uint workListCounter = 0; if (WaveIsFirstLane()) { workListCounter = g_WorkListCounter[0]; } workListCounter = WaveReadLaneFirst(workListCounter); // ワークリスト数を超えていたら終了. if (dispatchId.x >= workListCounter) { return; } // ソート前データを取得. WorkItem input = g_InputWorkList[dispatchId.x]; const uint shaderId = input.ShaderId; // 書き込み場所を取得. uint index = 0; InterlockedAdd(g_ShaderIdCounter[shaderId], 1, index); // グローバルオフセットを足しこむ. index += g_ShaderIdOffset[shaderId]; // ソート後データを書き込み. g_OutputWorkList[index] = input.FlatPixelId; } struct WorkItem { uint ShaderId; uint FlatPixelId; }; struct Constants { uint MaxShaderCount; uint3 Reserved; }; ConstantBuffer<Constants> g_Constants : register(b0); StructuredBuffer<WorkItem> g_InputWorkList : register(t0); StructuredBuffer<uint> g_WorkListCounter : register(t1); RWStructuredBuffer<uint> g_ShaderIdCounter : register(u0); RWStructuredBuffer<uint> g_ShaderIdOffset : register(u1); RWStructuredBuffer<uint> g_OutputWorkList : register(u2);
  102. • Shader Model 6.5から利用可能なWaveMatch()とWaveMultiPrefixCountBits()を用いて, 書き込み回数の抑制を行った • WaveMatch()を使用して,自分と同じシェーダを持つレーンをビットマスクから判別 • countbits()を用いて同じシェーダを持つレーン数を求める •

    書き込みインデックス算出にはWaveMultiPrefixCountBits()を使用する • カウンター数の加算は,ビットマスクが一番小さい所で1回だけ書き込む 203 uint shaderId = 3; uint4 mask = WaveMatch(shaderId);// 01000101 uint4 counts = countbits(mask); uint shaderCount = counts.x + counts.y + counts.z + counts.w; // 3 if (WaveGetLaneIndex() == lowestLane) // 最初に登場するレーンで書き込み. { InterlockedAdd(g_ShaderIdOffset[shaderId], shaderCount); }
  103. 204 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 ShaderId=2 0 0 0 0 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 0 0 0 0 0 6
  104. 205 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 ShaderId=2 1 0 0 0 1 0 0 0 0 0 0 0 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 0 0 0 0 0 6
  105. 206 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 ShaderId=2 1 0 0 0 1 0 0 0 2 0 0 0 0 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 0 0 0 0 0 6
  106. 207 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 ShaderId=2 1 0 0 0 1 0 0 0 2 0 0 0 0 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 0 0 0 0 0 6
  107. 208 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 ShaderId=2 1 0 0 0 1 0 0 0 2 2 0 0 0 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 0 0 0 0 0 6
  108. 209 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 ShaderId=2 1 0 0 0 1 0 0 0 2 ShaderId=3 0 1 0 0 0 1 0 1 3 2 3 0 0 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 0 0 0 0 0 6
  109. 210 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 ShaderId=2 1 0 0 0 1 0 0 0 2 ShaderId=3 0 1 0 0 0 1 0 1 3 ShaderId=4 0 0 1 0 0 0 1 0 2 2 3 2 0 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 0 0 0 0 0 6
  110. 211 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 ShaderId=2 1 0 0 0 1 0 0 0 2 ShaderId=3 0 1 0 0 0 1 0 1 3 ShaderId=4 0 0 1 0 0 0 1 0 2 ShaderId=5 0 0 0 1 0 0 0 0 1 2 3 2 1 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 0 0 0 0 0 6
  111. 212 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 ShaderId=2 1 0 0 0 1 0 0 0 2 ShaderId=3 0 1 0 0 0 1 0 1 3 ShaderId=4 0 0 1 0 0 0 1 0 2 ShaderId=5 0 0 0 1 0 0 0 0 1 2 3 2 1 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 2 0 0 0 0 6
  112. 213 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 ShaderId=2 1 0 0 0 1 0 0 0 2 ShaderId=3 0 1 0 0 0 1 0 1 3 ShaderId=4 0 0 1 0 0 0 1 0 2 ShaderId=5 0 0 0 1 0 0 0 0 1 2 3 2 1 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 2 0 0 0 0 6
  113. 214 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 ShaderId=2 1 0 0 0 1 0 0 0 2 ShaderId=3 0 1 0 0 0 1 0 1 3 ShaderId=4 0 0 1 0 0 0 1 0 2 ShaderId=5 0 0 0 1 0 0 0 0 1 2 3 2 1 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 2 0 0 0 0 6
  114. 215 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 ShaderId=2 1 0 0 0 1 0 0 0 2 ShaderId=3 0 1 0 0 0 1 0 1 3 ShaderId=4 0 0 1 0 0 0 1 0 2 ShaderId=5 0 0 0 1 0 0 0 0 1 0 3 2 1 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 2 5 0 0 0 6
  115. 216 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 ShaderId=2 1 0 0 0 1 0 0 0 2 ShaderId=3 0 1 0 0 0 1 0 1 3 ShaderId=4 0 0 1 0 0 0 1 0 2 ShaderId=5 0 0 0 1 0 0 0 0 1 0 0 2 1 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 2 5 7 0 0 6
  116. 217 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 ShaderId=2 1 0 0 0 1 0 0 0 2 ShaderId=3 0 1 0 0 0 1 0 1 3 ShaderId=4 0 0 1 0 0 0 1 0 2 ShaderId=5 0 0 0 1 0 0 0 0 1 0 0 0 1 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 2 5 7 8 0 6
  117. 218 PixelId : 0 ShaderId : 2 PixelId : 1

    ShaderId : 3 PixelId : 2 ShaderId : 4 PixelId : 3 ShaderId : 5 PixelId : 4 ShaderId : 2 PixelId : 5 ShaderId : 3 PixelId : 7 ShaderId : 3 PixelId : 6 ShaderId : 4 ShaderId=2 1 0 0 0 1 0 0 0 2 ShaderId=3 0 1 0 0 0 1 0 1 3 ShaderId=4 0 0 1 0 0 0 1 0 2 ShaderId=5 0 0 0 1 0 0 0 0 1 0 0 0 0 Id Counter 0 0 0 1 2 3 4 5 Id Offset 0 Index 0 0 2 5 7 8 0 6
  118. 219 #if ENABLE_OPTIMIZATION // 自分と同じシェーダを持っているレーンを調べて,シェーダ数を求める. uint4 mask = WaveMatch(shaderId); uint

    count = SumCountBits(mask); // 自分が最初だったら,シェーダ数を書き込む. uint lowLane = GetLowestLane(mask); if (WaveGetLaneIndex() == lowLane) { InterlockedAdd(g_ShaderIdCounter[shaderId], count); } uint index = 0; uint workCount = WaveActiveCountBits(true); if (WaveIsFirstLane()) { InterlockedAdd(g_WorkListCounter[0], workCount, index); } index = WaveReadLaneFirst(index); index += WavePrefixCountBits(true); #else // シェーダ数を数える. InterlockedAdd(g_ShaderIdCounter[shaderId], 1); // 書き込み番号を取得. int index = 0; InterlockedAdd(g_WorkListCounter[0], 1, index); #endif #if ENABLE_OPTIMIZATION // 自分と同じシェーダを持っているレーンを調べて,ローカル番号を求める. uint4 mask = WaveMatch(input.ShaderId); uint index = WaveMultiPrefixCountBits(true, mask); // シェーダ数を求める. uint count = SumCountBits(mask); uint lowLane = GetLowestLane(mask); // 自分が最初だったらシェーダ数を書き込み,ローカルオフセットを取得. uint localOffset = 0; uint globalOffset = 0; if (WaveGetLaneIndex() == lowLane) { InterlockedAdd(g_ShaderIdCounter[input.ShaderId], count, localOffset); globalOffset = g_ShaderIdOffset [input.ShaderId]; } // 同じシェーダを持つレーンで共有出来るようにする. localOffset = WaveReadLaneAt(localOffset, lowLane); globalOffset = WaveReadLaneAt(globalOffset, lowLane); // ローカルオフセットを足しこむ. index += localOffset; // グローバルオフセットを足しこむ. index += globalOffset; #else const uint shaderId = input.ShaderId; // 書き込み場所を取得. uint index = 0; InterlockedAdd(g_ShaderIdCounter[shaderId], 1, index); // グローバルオフセットを足しこむ. index += g_ShaderIdOffset[shaderId]; #endif // ビットを数え上げて、水平加算する. uint SumCountBits(uint4 mask) { uint4 count = countbits(mask); return dot(count, 1u.xxxx); } // レーンビットマスクの中から最も低いレーン番号を求める uint GetLowestLane(uint4 mask) { uint4 lowLanes = (uint4)(firstbitlow(mask) | uint4(0, 32, 64, 96)); return min(min(lowLanes.x, lowLanes.y), min(lowLanes.z, lowLanes.w)); }
  119. 221

  120. 222

  121. • ワークリスト作成時に各シェーダに属するピクセル数をカウントアップ済み • GPU上で数を数えているので,データはGPU上にあってCPU上にない • そのため,GPU上でドローコールの引数(Indirect Argument)を作成 • 作成したドローコール引数が格納されているバッファをExecuteIndirect()の引数に設定して, コンピュートシェーダによるシェーディング処理を実行

    224 [numthreads(32, 1, 1)] void main(uint3 dispatchId : SV_DispatchThreadID) { const uint maxShaderCount = min(g_Constants.MaxShaderCount, MAX_SHADERS); if (dispatchId.x >= maxShaderCount) return; DispatchArgument arg; arg.ThreadX = g_ShaderCounter[dispatchId.x]; arg.ThreadY = 1; arg.ThreadZ = 1; g_IndirectArgs[dispatchId.x] = arg; }
  122. • V-Bufferに格納されたインスタンスIDを使用して,ワールド行列を取得 • プリミティブIDを使用して,3頂点のデータを取得 225 uint shaderOffset = g_ShaderIdOffset[g_Constants.ShaderId]; uint

    flatPixelId = g_WorkList[shaderOffset + dispatchId.x]; // ピクセル座標を算出. uint2 pixelId; pixelId.x = flatPixelId & 0xFFFF; pixelId.y = (flatPixelId >> 16) & 0xFFFF; if (any(pixelId > g_Constants.RenderTargetSize)) return; // ビジビリティバッファ取得. uint2 visibility = g_VisibilityBuffer[pixelId]; if (visibility.y == 0) return; // IDを復元. uint instanceId = ((visibility.x >> 8) & 0xFFFFFF); uint primitiveId = (visibility.x & 0xFF); uint meshletId = visibility.y - 1; // 1始まりなので,0始まりにするためにマイナス1. // インスタンスデータ取得. MeshInstanceParam instance = g_MeshInstances[instanceId]; // メッシュレット情報取得. MeshletInfo meshlet = g_Meshlets[meshletId]; // プリミティブ番号を取得. uint3 tris = GetPrimitiveIndex(primitiveId + meshlet.PrimitiveOffset); // 頂点インデックスを取得. uint3 idx = uint3( g_VertexIndices[tris.x + meshlet.VertexOffset], g_VertexIndices[tris.y + meshlet.VertexOffset], g_VertexIndices[tris.z + meshlet.VertexOffset]);
  123. • マテリアル評価するために,重心座標を求める • カメラからスクリーンの各ピクセルを目標にレイを飛ばして求める 226 float3 intersect(float3 p0, float3 p1,

    float3 p2, float3 rayOrigin, float3 rayDir) { float3 eo = rayOrigin – p0; float3 e1 = p1 – p0; float3 e2 = p2 – p0; float3 r = cross(rayDir, e2); float3 s = cross(eo, e1); float iV = 1.0f / dot(r, e1); float V1 = dot(r, e0); float V2 = dot(s, rayDir); float b = V1 * iV; float c = V2 * iV; float a = 1.0f – b- c; return float3(a, b, c); } rayOrigin = cameraPosition; rayDir = normalize( cameraAxisX * (pixelCoord.x + 0.5f) + cameraAxisY * ((Height – pixelCoord.y – 1) + 0.5f) + cameraAxisZ);
  124. 228 BarycentricDeriv CalcFullBary(float4 pt0, float4 pt1, float4 pt2, float2 pixelNdc,

    float2 winSize) { BarycentricDeriv ret = (BarycentricDeriv)0; float3 invW = rcp(float3(pt0.w, pt1.w, pt2.w)); float2 ndc0 = pt0.xy * invW.x; float2 ndc1 = pt1.xy * invW.y; float2 ndc2 = pt2.xy * invW.z; float invDet = rcp(determinant(float2x2(ndc2 - ndc1, ndc0 - ndc1))); ret.m_ddx = float3(ndc1.y - ndc2.y, ndc2.y - ndc0.y, ndc0.y - ndc1.y) * invDet * invW; ret.m_ddy = float3(ndc2.x - ndc1.x, ndc0.x - ndc2.x, ndc1.x - ndc0.x) * invDet * invW; float ddxSum = dot(ret.m_ddx, float3(1,1,1)); float ddySum = dot(ret.m_ddy, float3(1,1,1)); float2 deltaVec = pixelNdc - ndc0; float interpInvW = invW.x + deltaVec.x*ddxSum + deltaVec.y*ddySum; float interpW = rcp(interpInvW); ret.m_lambda.x = interpW * (invW[0] + deltaVec.x*ret.m_ddx.x + deltaVec.y*ret.m_ddy.x); ret.m_lambda.y = interpW * (0.0f + deltaVec.x*ret.m_ddx.y + deltaVec.y*ret.m_ddy.y); ret.m_lambda.z = interpW * (0.0f + deltaVec.x*ret.m_ddx.z + deltaVec.y*ret.m_ddy.z); ret.m_ddx *= (2.0f/winSize.x); ret.m_ddy *= (2.0f/winSize.y); ddxSum *= (2.0f/winSize.x); ddySum *= (2.0f/winSize.y); ret.m_ddy *= -1.0f; ddySum *= -1.0f; float interpW_ddx = 1.0f / (interpInvW + ddxSum); float interpW_ddy = 1.0f / (interpInvW + ddySum); ret.m_ddx = interpW_ddx*(ret.m_lambda*interpInvW + ret.m_ddx) - ret.m_lambda; ret.m_ddy = interpW_ddy*(ret.m_lambda*interpInvW + ret.m_ddy) - ret.m_lambda; return ret; } struct BarycentricDeriv { float3 m_lambda; float3 m_ddx; float3 m_ddy; };
  125. 229 // ワールド空間位置を取得. float4 worldPos[3]; worldPos[0] = mul(instance.CurrWorld, float4(g_Positions[idx.x], 1.0f));

    worldPos[1] = mul(instance.CurrWorld, float4(g_Positions[idx.y], 1.0f)); worldPos[2] = mul(instance.CurrWorld, float4(g_Positions[idx.z], 1.0f)); float4 viewPos[3]; viewPos[0] = mul(g_ViewParam.View, worldPos[0]); viewPos[1] = mul(g_ViewParam.View, worldPos[1]); viewPos[2] = mul(g_ViewParam.View, worldPos[2]); float4 projPos[3]; projPos[0] = mul(g_ViewParam.Proj, viewPos[0]); projPos[1] = mul(g_ViewParam.Proj, viewPos[1]); projPos[2] = mul(g_ViewParam.Proj, viewPos[2]); float2 pixelNdc = ((pixelId + 0.5f.xx) / (float2)g_Constants.RenderTargetSize) * float2(2.0f, -2.0f) - float2(1.0f, -1.0f); // 重心座標を求める. BarycentricsParam baryParam = CalcFullBary(projPos[0], projPos[1], projPos[2], pixelNdc, g_Constants.RenderTargetSize); float3 worldNormal[3]; worldNormal[0] = normalize(mul((float3x3)instance.CurrWorld, g_Normals[idx.x])); worldNormal[1] = normalize(mul((float3x3)instance.CurrWorld, g_Normals[idx.y])); worldNormal[2] = normalize(mul((float3x3)instance.CurrWorld, g_Normals[idx.z])); float2 uv[3]; uv[0] = g_TexCoords[idx.x]; uv[1] = g_TexCoords[idx.y]; uv[2] = g_TexCoords[idx.z]; // 補間した頂点データを求める. ShadingParam param; { BaryInterpolate3(baryParam, worldPos[0].xyz, worldPos[1].xyz, worldPos[2].xyz, param.WorldPos); BaryInterpolate3(baryParam, worldNormal[0], worldNormal[1], worldNormal[2], param.Normal); param.Normal = normalize(param.Normal); param.MaterialId = meshlet.MaterialId; BaryInterpolate2WithDeriv(baryParam, uv[0], uv[1], uv[2], param.TexCoord, param.DxTexCoord, param.DyTexCoord); } // シェーディング処理. float4 color = Shading(param); // レンダーターゲットに書き込み. g_RenderTarget[pixelId] = color;
  126. 230

  127. 231

  128. 232

  129. 237

  130. • [Okuda 2019] 奥田雅史, 川名勇気, 落合仁美子, 二階堂将也, “『描画が出来る人』ってどうやって育てればいいんだろう?~描画エンジニア育成プロジェクトポストモーテム~”, CEDEC 2019

    • [Wihlidal 2016] Grahm Wihlidal, “Optimizing the Graphics Pipeline with Compute”, GDC 2016. • [Uralsky 2019] Yury Uralsky, “MESH SHADING: Towards greater efficiency in geometry processing”, SIGGRAPH 2019 Courses: Advances in Real-Time Rendering in Games. • [Karis 2021] Brian Karis, Rune Stubbe, Graham Wihlidal, “A Deep Dive into Nanite Virtualized Geometry”, SIGGRAPH 2021 Courses: Advances in Real-Time Rendering in Games. • [Jansson 2024] Erik Jansson, “GPU-driven Rendering with Mesh Shaders in Alan Wake2”, Digital Dragons 2024. • [Lopez 2025] Nicolas Lopez, “Rendering ‘Assassin’s Creed Shadows’”, GDC 2025. • [Mishima 2025] Hitoshi Mishima, “RE ENGINE Meshlet Rendering Pipeline”, Rendering Engine Architecture Conference 2025. • [Microsoft 2021] Microsoft, “DirectX-Specs : HLSL Wave Size”, https://microsoft.github.io/DirectX-Specs/d3d/HLSL_SM_6_6_WaveSize.html, 2021. • [Microsoft 2023] Microsoft, “DirectX-Specs : Mesh Shader”, https://microsoft.github.io/DirectX-Specs/d3d/MeshShader.html, 2023. • [Microsoft 2024] Microsoft, “DirectXShaderCompile Wave Intrinsics”, https://github.com/microsoft/DirectXShaderCompiler/wiki/Wave-Intrinsics, 2024 • [shikihuiku 2020] shikihuiku, “HLSLのWave Intrinsicsについて”, https://shikihuiku.github.io/post/wave_intrinsics1/, 2020. • [Sreckovic 2024] , “Compute shader wave intrinsics tricks”, https://medium.com/@marehtcone/compute-shader-wave-intrinsics-tricks-e237ffb159ef, 2024. • [Honda 2019] 本多圭, “フラスタムカリング入門、良いフラスタムの作り方”, CEDEC 2019. • [Mishima 2018] 三嶋仁, “最新タイトルのグラフィックス最適化事例”, CEDEC 2018. • [Pohlmann 2021] Matthew Pohlmann, “Samurai Landscapes: Building and Rendering Tsushima Island on PS4”, https://gdcvault.com/play/1027352/Samurai- Landscapes-Building-and-Rendering, GDC 2021. 238
  131. • [Hable 2021] John Hable, “Visibility Buffer Rendering with Material

    Graphs”, http://filmicworlds.com/blog/visibility-buffer-rendering-with-material-graphs/, 2021. • [Burns 2013] Christopher A. Burns, Warren A. Hunt, “The Visibility Buffer: A Cache-Friendly Approach to Deferred Shading”, The Journal of Computer Graphics Techniques, vol.2, no.2, pp.55-69, 2013. • [stack overflow 2017], stack overflow, “Radius of projected sphere in screen space”, https://stackoverflow.com/questions/21648630/radius-of-projected- sphere-in-screen-space, 2017. • [Garland 1997] Michael Garland, Paul S. Heckbert, “Surface simplification using quadric error metrics”, SIGGRAPH 97, pp.208-216, August 1997. • [Nam 2025] キュウ キャル, 南 相培, 佐光 一輝, “モバイルにも使える軽量な構造を持つ仮想化ジオメトリシステムの設計と実装について”, CEDEC 2025. • [Kuth 2024] Bastian Kuth, Max Oberberger, Felix Kawala, Sander Reitter, Sebastian Michel, Matthaus Chadas, Quirin Meyer, “Towards Practical Meshlet Compression”, 2024. • [AMD 2024] AMD, “GPU Open : Meshlet compression”, https://gpuopen.com/learn/mesh_shaders/mesh_shaders-meshlet_compression, 2024. • [Cigolle 2014] Zina H. Cigolle, San Donow, Daniel Evangelakos, Michael Mara, Morgan McGuire, Quirin Meyer, “A Survey of Efficient Representations for Independent Unit Vectors”, Journal of Computer Graphics Techniques, Vol.3, No.2, pp.1-30, 2014. • [John White 3D 2017] John White 3D, “Signed Octahedron Normal Encoding”, https://johnwhite3d.blogspot.com/, 2017. • [Schüler2007] Christian Schüler, “Normal Mapping without Precomputed Tangents”, ShaderX5, Chapter 2.6, pp.131-140, 2007. • [Schüler2013] Christian Schüler, “Followup: Normal Mapping Without Precomputed Tangents”, http://www.thetenthplanet.de/archives/1180, 2013. • [Geffroy 2020] Jean Geffroy, Axel Gneiting, Yixin Wang, “Rendering the Hellscape of Doom Eternal”, SIGGRAPH 2020 Advances in Real-Time Rendering course, 2020. • [Ong 2023] Jeremy Ong, “Tangent Spaces and Diamon Encoding”, https://www.jeremyong.com/graphics/2023/01/09/tangent-spaces-and-diamond-encoding/, 2023. • [Mclaren 2022] James Mclaren, “Adventures with Deferred Texturing in Horizon Forbidden West”, GDC 2022. 239
  132. • [Haar 2015] Ulrich Haar, Sebastian Aaltonen, “GPU-Driven Rendering Pipelines”,

    SIGGRAPH 2015: Advances in Real-Time Rendering in Games, 2015. • [Takeshige 2018] 竹重 雅也, “DirectX Raytracing – The life of a ray tracing kernel”, CEDEC 2018. • [Akuzawa 2024] 阿久澤 陽菜, ルフェ マキシム, “Mesh shaderを活用したスキニングメッシュに対するサブディビジョンサーフェイス”, CEDEC 2024. • [Kapoulkine 2025] Arseny Kapoulkine, “meshoptimizer”, https://github.com/zeux/meshoptimizer, 2025. • [Wikipedia 2025a] Wikipedia, “Epipolar geometry, “, https://en.wikipedia.org/wiki/Epipolar_geometry, 2025 • [Wikipedia 2025b] Wikipedia, “Spherical cap”, https://en.wikipedia.org/wiki/Spherical_cap, 2025. • [Valient 2007] Michal Valient, “Deferred Rendering in Killzone 2”, GDC 2007. • [Legarde 2014] Sebastien Lagarde, Charles de Rousiers, “Moving Frostbite to Physically Based Rendering 3.0”, SIGGRAPH 2014 Course: Physically Based Shading in Theory and Practice, 2014. • [Engel 2016] Wolfgang Engel, “The filter and Culled Visibility Buffer”, GDC Europe 2016. • [Anagnostou 2018] Kostas Anagnostou, “GPU driven rendering experiments”, Digital Dragons 2018. • [KarypisLab 2022] Prof. George Karipis’s research group, “METIS”, https://github.com/KarypisLab/METIS, 2022. • [Baerentzen 2021] Andres Baerentzen, Eva Rotenberg, “Skeletonization via Local Separators”, ACM Transaction on Graphics, Vol.40, Issue 5, No.187, pp.1-18, 2021. • [monsho 2023] もんしょ, “もんしょの巣穴 DirectXの話 第182回 Visibility Buffer”, https://sites.google.com/site/monshonosuana/directx%E3%81%AE%E8%A9%B1/directx%E3%81%AE%E8%A9%B1-%E7%AC%AC182%E5%9B%9E, 2023. • [Ciardi 2018] Francesco Cifariello Ciardi, “Intro to GPU Scalarization – Part 1”, https://flashypixels.wordpress.com/2018/11/10/intro-to-gpu-scalarization-part- 1/ • [Shocker 2023] Shocker_0x15, “現代のGPUの実行スタイルとレイトレ(2023)”, https://speakerdeck.com/shocker_0x15/modern-gpu-execution-and-ray-tracing, レイトレ 合宿9 セミナー, • [Doghramachi 2017] Hawar Doghramachi, Jean-Normand Bucci, “Deferred+: Next-Gen Culling and Rendering for the Dawn Engine”, GPU Zen, pp.77-104, 2017. 240
  133. • [Yu 2024] 于 承中, 内村 創, “ニューラルネットワークを用いた高精度なオブジェクトカリングシステムの提案”, CEDEC +

    KYUSHU 2024. • [Yu 2025] 于 承中, 内村 創, “NueralPVS: 『グランツーリスモ7』におけるニューラルネットワークを用いた次世代オブジェクトカリングシステム”, CEDEC 2025. 241
  134. 242

  135. • Unityにおける大量オブジェクトのレンダリング高速化事例 〜GPU駆動レンダリング & Hi-Zカリングの統合〜, CEDEC 2024. • 『GRANBLUE FANTASY:

    Relink』ソフトウェアラスタライザによる実践的なオクルージョンカリング, CEDEC 2024. • 自動生成マップの描画を高速化!~ComputeShaderを使ったオクルージョンカリングの理論と実装~, CEDEC 2023. • Practical technologies to create Big World city with time-of-day/昼夜の変化のある「ビッグワールド」の町の実現のための実用 的な技術の紹介, CEDEC 2023. • 内製レンダリングエンジンにおける採用技術と最適化手法, CEDEC 2019. • 最新タイトルのグラフィックス最適化事例, CEDEC 2018. • Umbra Software:次世代に向けたレンダリングと可視性の最適化について, CEDEC 2012. • 更に進化した遮蔽カリングシステムUmbra3の実力,CEDEC 2011. …などなど 248
  136. • [Pohlmann 2021] “Samurai Landscapes: Building and Rendering Tsushima Island

    on PS4”, 1. 前フレームの深度を1/4解像度のターゲットにダウンサンプルする 2. 現在のビュー空間にリプロジェクションする 3. エピポール検索+ヒューリスティックを用いてリプロジェクションすることにより残った穴埋めをする 4. max-depthのミップチェーンを生成する 249
  137. 250 • 3次元空間を異なる一のカメラから撮影した幾何 • カメラ𝑂𝐿 から見えるスクリーン上の𝑋𝐿 は複数の候補が考えられる(例えば,𝑋1 , 𝑋2 ,

    𝑋3 など) • 1つのカメラ上のみでは,3D上の位置を特定不可 • しかし,カメラ𝑂𝑅 から同一物体が𝑋𝑅 上に表示されていることが分かった場合には, 𝑋が3D上の位置であると特定可能
  138. 252 [numthreads(8, 8, 1)] void main(uint3 dispatchId : SV_DispatchThreadID) {

    if (any(dispatchId.xy >= g_Constants.RenderTargetSize)) return; // 前フレームの深度を取得. float zPrev = g_PrevDepthMap[dispatchId.xy]; // 前フレームのクリップ空間. float4 posClipPrev = float4(ToClipPos(dispatchId.xy), zPrev, 1.0f); // 現在フレームのクリップ空間に変換する. float4 posClip = mul(g_TransParam.PrevClipToCurrClip, posClipPrev); posClip.xyz /= posClip.w; // Perspective Divide. // テクスチャ座標に変換する. float2 uv = posClip.xy * 0.5f + 0.5f.xx; // テクスチャ座標が適切かどうかチェック. if (all(uv >= 0.0f) && all(uv <= 1.0f)) { // Z値. float z = posClip.w; // ニア平面より手前ならクランプ. if (z <= g_TransParam.Current.NearClip) z = g_TransParam.Current.NearClip; // 深度バイアスを考慮. z += g_Constants.DepthBias; // ファー平面よりも奥ならクランプ. if (z > g_TransParam.Current.FarClip || zPrev >= g_TransParam.Previous.FarClip) z = g_TransParam.Current.FarClip; // ピクセル位置を取得. float2 xy = floor(uv * g_Constants.RenderTargetSize); // リプロジェクションしたZ値を書き込む. CurrDepthAtomicMax(xy, z); } // 範囲外の処理. { // 現在フレームのファー平面(Reverse-Zを想定)のクリップ空間位置. float4 posClip = float4(ToClipPos(dispatchId.xy), 0.0f, 1.0f); // 前フレームのクリップ空間位置に変換. float4 posClipPrev = mul(g_TransParam.CurrClipToPrevClip, posClip); // 画面外であることをチェック. if (any(abs(posClipPrev.xy) >= abs(posClipPrev.w))) { float2 uv = (posClipPrev.xy / posClipPrev.w) * 0.5f + 0.5f.xx; float z = g_PrevDepthMap.SampleLevel(LinearClamp, uv, 0.0f) + g_Constants.DepthBias; CurrDepthAtomicMax(dispatchId.xy, z); } } }
  139. 253 [numthreads(8, 8, 1)] void main(uint3 dispatchId : SV_DispatchThreadID) {

    // エピポールを求める. float3 epipole = normalize(g_TransParam.Previous.CameraPos - g_TransParam.Current.CameraPos); // 射影空間でのエピポールを求める. float4 clipEpipole = mul(g_TransParam.Current.Proj, float4(epipole, 0.0f)); // UV空間でのエピポールを求める. float2 uvEpipole = normalize(clipEpipole.xy * 0.5f + 0.5f.xx); // テクスチャ座標を求める. float2 uv = (dispatchId.xy + 0.5f.xx) * g_TransParam.RenderTargetSize.zw; // 深度を取得. float zMax = g_SrcDepth.SampleLevel(LinearClamp, uv, 0.0f); // 穴埋めされていない箇所であることをチェック. if (zMax == -FLT_MAX) { // UV空間での検索方向を求める. float2 normalEpipole = normalize(uvEpipole * uv); // 検索の刻み幅を求める. float2 dUvStep = normalEpipole * g_TransParam.RenderTargetSize.zw; // エピポーラ検索を行う. for(uint mip=1; mip <= g_Constants.MipLevels; ++mip) { // 検索点を求める. float2 uvSearch = uv + dUvStep; // 検索点における深度を取得 float z = g_SrcDepth.SampleLevel(LinearClamp, uvSearch, mip); // 次のミップを調べるために,2倍する(テクセルサイズが2倍になるため). dUvStep *= 2.0f; // 深度が適切であるかどうかチェック. if (z != -FLT_MAX) { z = max(z, g_DstDepth[dispatchId.xy]); z += g_Constants.DepthBias; z = min(z, g_TransParam.Current.FarClip); // 正常終了. return; } } // 適切な深度が取得できない場合は,背景として遮蔽されない扱いにする. if (zMax < g_TransParam.Current.NearClip) zMax = g_TransParam.Current.FarClip; // ファー平面でクランプ. zMax = min(zMax, g_TransParam.Current.FarClip); // 検索に引っかからなかった場合のフォールバックとして書き込み. g_DstDepth[dispatchId.xy] = zMax; } }
  140. • [Oberberger2024; Kuth2024; AMD2024] 257 0 1 2 2 3

    1 2 3 4 2 4 5 5 2 0 0 0 0 0 5 6 6 7 7 8 8 8 9 1 1 32 bit Index 32 bit Index 32 bit Index 96 bit per Triangles
  141. • [Oberberger2024; Kuth2024; AMD2024] 258 0 1 2 2 3

    1 2 3 4 2 4 5 5 2 0 0 0 0 0 5 6 6 7 7 8 8 8 9 1 1 8 bit Index 8 bit Index 8 bit Index 24 bit per Triangles
  142. • [Oberberger2024; Kuth2024; AMD2024] 259 0 1 2 3 4

    5 6 7 8 9 R R L L L L L L R 8 bit Index 1 bit Code 9 bit per Triangles
  143. • [Oberberger2024; Kuth2024; AMD2024] 260 0 1 2 3 4

    5 6 7 8 9 R R L L L L L L R 8 bit Index 1 bit Code 9 bit per Triangles
  144. • [Oberberger2024; Kuth2024; AMD2024] 262 0 1 2 + +

    + + + + + R R L L L L L L R 0 1 1 bit Code 1 bit Code 8 bit Index
  145. • [Oberberger2024; Kuth2024; AMD2024] 263 0 1 2 + +

    + + + + + R R L L L L L L R 0 1 1 bit Code 1 bit Code 3 bit Index 5 bit per Triangles
  146. 266

  147. 267

  148. 268

  149. 271