Vulkan VK_EXT_opacity_micromap レイトレーシング透明度最適化:GPU負荷40%削減実装ガイド【2026年6月新拡張】
Vulkan 1.4の新拡張VK_EXT_opacity_micromapでレイトレーシングの透明度処理を最適化。マイクロマップ構造でレイキャスト負荷を40%削減する実装方法を解説。
約11分で読めますVK_EXT_opacity_micromapとは:レイトレーシング透明度処理の革新
2026年5月にリリースされたVulkan 1.4の新拡張機能「VK_EXT_opacity_micromap」は、レイトレーシングにおける透明度判定の処理負荷を劇的に削減する技術です。
従来のレイトレーシングでは、植物の葉・フェンス・グリッドなど部分的に透明なジオメトリに対して、レイが三角形と交差するたびにシェーダーを実行してアルファテストを行う必要がありました。この処理は森林シーン・複雑な建築物など透明オブジェクトが大量に配置された環境で、レイトレーシングの最大のボトルネックとなっていました。
VK_EXT_opacity_micromapは、三角形表面を微細なマイクロトライアングルに分割し、各マイクロトライアングルに「完全不透明」「完全透明」「不確定(シェーダー判定必要)」の3状態を事前計算して格納します。レイキャスト時にはこのマイクロマップを参照し、「完全不透明」なら即座にヒット、「完全透明」なら即座にミス、「不確定」のみシェーダー実行という最適化を行います。
NVIDIAの公式技術資料によれば、植物が密集した森林シーンで最大42%、建築物の多いシーンで平均38%のレイトレーシング性能向上が確認されています。
出典: Unsplash / Unsplash License
マイクロマップの内部構造とメモリレイアウト
VK_EXT_opacity_micromapの中核は、三角形表面を再帰的に4分割して生成されるマイクロトライアングルの階層構造です。
以下のダイアグラムはマイクロマップの構造を示しています。
graph TD
A["三角形メッシュ"] --> B["サブディビジョンレベル指定"]
B --> C["マイクロトライアングル生成"]
C --> D["不透明度状態計算"]
D --> E["マイクロマップバッファ"]
E --> F["アクセラレーション構築時バインド"]
F --> G["レイキャスト時参照"]
D --> D1["完全不透明: 0b00"]
D --> D2["完全透明: 0b01"]
D --> D3["不確定: 0b10/0b11"]
各マイクロトライアングルの状態は2ビットでエンコードされます。サブディビジョンレベルNの三角形は4^N個のマイクロトライアングルを持ち、メモリサイズは (4^N * 2) / 8 バイトです。例えばレベル5では1024個のマイクロトライアングル、256バイトのストレージが必要です。
メモリレイアウトは以下のように配置されます。
Offset 0x0000: [マイクロマップヘッダー]
- subdivision_level: uint8_t
- format: VkOpacityMicromapFormatEXT
- triangle_count: uint32_t
Offset 0x0010: [マイクロトライアングルデータ配列]
- 各エントリ2ビット、32エントリごとに8バイトパック
- アライメント: 16バイト境界
構築時にはテクスチャのアルファチャネルをサンプリングし、各マイクロトライアングル内のすべてのサンプルが不透明なら0b00、すべて透明なら0b01、混在する場合は0b10を設定します。
実装ステップ1:マイクロマップのビルドとメモリ管理
VK_EXT_opacity_micromapの実装は3段階に分かれます。最初のステップはマイクロマップデータの生成とVulkanバッファへの格納です。
以下のコードは、テクスチャからマイクロマップを生成する処理を示しています。
#include <vulkan/vulkan.h>
// マイクロマップビルド情報構造体
struct MicromapBuildInfo {
uint32_t subdivisionLevel; // 2-5を推奨(4^2=16 ~ 4^5=1024マイクロ三角形)
VkFormat alphaFormat; // VK_FORMAT_R8_UNORM等
VkImage alphaTexture;
VkImageView alphaTextureView;
};
// マイクロマップデータ生成(CPUサイド)
std::vector<uint8_t> generateOpacityMicromap(
const MicromapBuildInfo& info,
const std::vector<Vertex>& vertices,
const std::vector<uint32_t>& indices
) {
uint32_t triangleCount = indices.size() / 3;
uint32_t microtrianglesPerTriangle = 1 << (info.subdivisionLevel * 2); // 4^N
uint32_t bitsPerTriangle = microtrianglesPerTriangle * 2;
uint32_t bytesPerTriangle = (bitsPerTriangle + 7) / 8;
std::vector<uint8_t> micromapData(triangleCount * bytesPerTriangle, 0);
for (uint32_t triIdx = 0; triIdx < triangleCount; triIdx++) {
// 三角形の頂点取得
glm::vec3 v0 = vertices[indices[triIdx * 3 + 0]].position;
glm::vec3 v1 = vertices[indices[triIdx * 3 + 1]].position;
glm::vec3 v2 = vertices[indices[triIdx * 3 + 2]].position;
glm::vec2 uv0 = vertices[indices[triIdx * 3 + 0]].uv;
glm::vec2 uv1 = vertices[indices[triIdx * 3 + 1]].uv;
glm::vec2 uv2 = vertices[indices[triIdx * 3 + 2]].uv;
// マイクロトライアングルごとの状態計算
for (uint32_t microIdx = 0; microIdx < microtrianglesPerTriangle; microIdx++) {
// 重心座標計算(サブディビジョングリッド座標→重心座標)
auto [u, v] = getMicrotriBarycentric(microIdx, info.subdivisionLevel);
glm::vec2 uv = uv0 * (1 - u - v) + uv1 * u + uv2 * v;
// アルファ値サンプリング(実装ではテクスチャ読み込み処理を想定)
float alpha = sampleAlphaTexture(info.alphaTexture, uv);
// 状態判定(閾値0.1/0.9を使用)
uint8_t state;
if (alpha >= 0.9f) state = 0b00; // 完全不透明
else if (alpha <= 0.1f) state = 0b01; // 完全透明
else state = 0b10; // 不確定
// 2ビットパッキング
uint32_t bitOffset = microIdx * 2;
uint32_t byteOffset = triIdx * bytesPerTriangle + bitOffset / 8;
uint32_t bitInByte = bitOffset % 8;
micromapData[byteOffset] |= (state << bitInByte);
}
}
return micromapData;
}
// Vulkanバッファ作成
VkBuffer createMicromapBuffer(VkDevice device, const std::vector<uint8_t>& data) {
VkBufferCreateInfo bufferInfo = {};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = data.size();
bufferInfo.usage = VK_BUFFER_USAGE_MICROMAP_BUILD_INPUT_READ_ONLY_BIT_EXT |
VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
VkBuffer buffer;
vkCreateBuffer(device, &bufferInfo, nullptr, &buffer);
// メモリアロケーション・データ転送は省略
return buffer;
}
サブディビジョンレベルの選択は重要です。レベル3(64マイクロ三角形)はメモリ効率重視、レベル5(1024マイクロ三角形)は精度重視です。NVIDIAの推奨値はレベル4(256マイクロ三角形)で、メモリコストとヒット精度のバランスが最適とされています。
実装ステップ2:Opacity Micromapオブジェクトの構築
生成したマイクロマップデータから、VkMicromapEXTオブジェクトを構築します。これはBottom-Level Acceleration Structure(BLAS)に関連付けられます。
以下のダイアグラムは構築フローを示しています。
sequenceDiagram
participant App as アプリケーション
participant Dev as VkDevice
participant Cmd as VkCommandBuffer
participant GPU as GPU
App->>Dev: vkCreateMicromapEXT()
Dev-->>App: VkMicromapEXT
App->>Dev: vkGetMicromapBuildSizesEXT()
Dev-->>App: ビルドサイズ情報
App->>Dev: バッファ確保(マイクロマップ本体+スクラッチ)
App->>Cmd: vkCmdBuildMicromapsEXT()
Cmd->>GPU: マイクロマップ構築
GPU-->>Cmd: 完了
App->>Cmd: パイプラインバリア(VK_PIPELINE_STAGE_MICROMAP_BUILD_BIT_EXT)
実装コードは以下の通りです。
// Micromapオブジェクト作成
VkMicromapEXT createOpacityMicromap(
VkDevice device,
VkBuffer micromapDataBuffer,
uint32_t triangleCount,
uint32_t subdivisionLevel
) {
// Micromap作成
VkMicromapCreateInfoEXT micromapCreateInfo = {};
micromapCreateInfo.sType = VK_STRUCTURE_TYPE_MICROMAP_CREATE_INFO_EXT;
micromapCreateInfo.type = VK_MICROMAP_TYPE_OPACITY_MICROMAP_EXT;
micromapCreateInfo.size = calculateMicromapSize(triangleCount, subdivisionLevel);
// ストレージバッファ
VkBuffer micromapBuffer = createBuffer(device, micromapCreateInfo.size,
VK_BUFFER_USAGE_MICROMAP_STORAGE_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
micromapCreateInfo.buffer = micromapBuffer;
micromapCreateInfo.offset = 0;
VkMicromapEXT micromap;
vkCreateMicromapEXT(device, µmapCreateInfo, nullptr, µmap);
// ビルドサイズクエリ
VkMicromapBuildInfoEXT buildInfo = {};
buildInfo.sType = VK_STRUCTURE_TYPE_MICROMAP_BUILD_INFO_EXT;
buildInfo.type = VK_MICROMAP_TYPE_OPACITY_MICROMAP_EXT;
buildInfo.mode = VK_BUILD_MICROMAP_MODE_BUILD_EXT;
buildInfo.dstMicromap = micromap;
VkMicromapBuildSizesInfoEXT sizeInfo = {};
sizeInfo.sType = VK_STRUCTURE_TYPE_MICROMAP_BUILD_SIZES_INFO_EXT;
vkGetMicromapBuildSizesEXT(device, VK_ACCELERATION_STRUCTURE_BUILD_TYPE_DEVICE_KHR,
&buildInfo, &sizeInfo);
// スクラッチバッファ
VkBuffer scratchBuffer = createBuffer(device, sizeInfo.buildScratchSize,
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
// ビルド実行(コマンドバッファ内)
VkMicromapTriangleEXT triangleArray[triangleCount];
for (uint32_t i = 0; i < triangleCount; i++) {
triangleArray[i].subdivisionLevel = subdivisionLevel;
triangleArray[i].format = VK_OPACITY_MICROMAP_FORMAT_2_STATE_EXT; // 2状態モード
triangleArray[i].dataOffset = i * calculateTriangleDataSize(subdivisionLevel);
}
buildInfo.data.deviceAddress = getBufferDeviceAddress(device, micromapDataBuffer);
buildInfo.scratchData.deviceAddress = getBufferDeviceAddress(device, scratchBuffer);
buildInfo.triangleArray.deviceAddress = getBufferDeviceAddress(device, triangleArrayBuffer);
buildInfo.triangleArrayStride = sizeof(VkMicromapTriangleEXT);
vkCmdBuildMicromapsEXT(commandBuffer, 1, &buildInfo);
// バリア挿入(マイクロマップ→BLAS構築の依存性)
VkMemoryBarrier2 barrier = {};
barrier.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER_2;
barrier.srcStageMask = VK_PIPELINE_STAGE_2_MICROMAP_BUILD_BIT_EXT;
barrier.srcAccessMask = VK_ACCESS_2_MICROMAP_WRITE_BIT_EXT;
barrier.dstStageMask = VK_PIPELINE_STAGE_2_ACCELERATION_STRUCTURE_BUILD_BIT_KHR;
barrier.dstAccessMask = VK_ACCESS_2_ACCELERATION_STRUCTURE_READ_BIT_KHR;
VkDependencyInfo depInfo = {};
depInfo.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
depInfo.memoryBarrierCount = 1;
depInfo.pMemoryBarriers = &barrier;
vkCmdPipelineBarrier2(commandBuffer, &depInfo);
return micromap;
}
重要な点はVK_OPACITY_MICROMAP_FORMAT_2_STATE_EXTとVK_OPACITY_MICROMAP_FORMAT_4_STATE_EXTの選択です。2状態モードは「透明/不透明」のみ、4状態モードは「透明/不透明/不確定/未使用」の4値を扱えます。フェンス・グリッドなど単純なアルファテストには2状態、植物など複雑な形状には4状態が適しています。
実装ステップ3:BLASへのバインドとレイトレーシングパイプライン統合
構築したOpacity MicromapをBottom-Level Acceleration Structure(BLAS)の各ジオメトリに関連付けます。
// BLAS構築時にMicromapをバインド
VkAccelerationStructureGeometryKHR geometry = {};
geometry.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR;
geometry.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR;
geometry.flags = VK_GEOMETRY_OPAQUE_BIT_KHR; // 重要:OPAQUEフラグは削除しない
geometry.geometry.triangles.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR;
geometry.geometry.triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT;
geometry.geometry.triangles.vertexData.deviceAddress = vertexBufferAddress;
geometry.geometry.triangles.vertexStride = sizeof(Vertex);
geometry.geometry.triangles.indexType = VK_INDEX_TYPE_UINT32;
geometry.geometry.triangles.indexData.deviceAddress = indexBufferAddress;
// Opacity Micromap拡張構造体をチェーン
VkAccelerationStructureTrianglesOpacityMicromapEXT opacityMicromap = {};
opacityMicromap.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_TRIANGLES_OPACITY_MICROMAP_EXT;
opacityMicromap.micromap = micromapObject;
opacityMicromap.indexType = VK_INDEX_TYPE_UINT32;
opacityMicromap.indexBuffer.deviceAddress = micromapIndexBufferAddress; // 三角形→Micromapエントリのマッピング
opacityMicromap.indexStride = sizeof(uint32_t);
geometry.geometry.triangles.pNext = &opacityMicromap;
// BLAS構築
VkAccelerationStructureBuildGeometryInfoKHR buildInfo = {};
buildInfo.sType = VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_BUILD_GEOMETRY_INFO_KHR;
buildInfo.type = VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_KHR;
buildInfo.flags = VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR |
VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_OPACITY_MICROMAP_UPDATE_EXT; // 動的更新許可
buildInfo.mode = VK_BUILD_ACCELERATION_STRUCTURE_MODE_BUILD_KHR;
buildInfo.geometryCount = 1;
buildInfo.pGeometries = &geometry;
vkCmdBuildAccelerationStructuresKHR(commandBuffer, 1, &buildInfo, &rangeInfo);
レイトレーシングシェーダー側では特別な変更は不要です。rayQueryEXTやtraceRayEXTは自動的にマイクロマップを参照します。ただしAny-Hitシェーダーは「不確定」状態のマイクロトライアングルでのみ実行されます。
// Closest-Hitシェーダー(変更不要)
#version 460
#extension GL_EXT_ray_tracing : require
layout(location = 0) rayPayloadInEXT vec3 hitValue;
void main() {
// Opacity Micromapで「不透明」判定された場合のみここに到達
// アルファテストコードは不要(マイクロマップで事前判定済み)
hitValue = vec3(1.0, 0.0, 0.0);
}
パフォーマンスモニタリングでは、VK_QUERY_TYPE_ACCELERATION_STRUCTURE_COMPACTED_SIZE_KHRに加えて、VK_QUERY_TYPE_MICROMAP_SERIALIZATION_SIZE_EXTでマイクロマップのメモリ使用量を追跡できます。
以下のダイアグラムはレイトレーシング実行時の処理フローを示しています。
stateDiagram-v2
[*] --> RayIntersection: レイキャスト開始
RayIntersection --> MicromapLookup: 三角形交差
MicromapLookup --> Opaque: 完全不透明(0b00)
MicromapLookup --> Transparent: 完全透明(0b01)
MicromapLookup --> Uncertain: 不確定(0b10)
Opaque --> ClosestHit: 即座にヒット報告
Transparent --> Continue: レイ継続(ミス)
Uncertain --> AnyHitShader: シェーダー実行
AnyHitShader --> AcceptHit: アルファテスト成功
AnyHitShader --> IgnoreHit: アルファテスト失敗
AcceptHit --> ClosestHit
IgnoreHit --> Continue
ClosestHit --> [*]
Continue --> [*]
この最適化により、従来は全交差でシェーダー実行していた処理が、マイクロマップによる事前判定で大幅に削減されます。
パフォーマンス測定と最適化戦略
NVIDIAの公式ベンチマーク(2026年5月公開)では、VK_EXT_opacity_micromapの効果が以下のシーンで測定されています。
森林シーン(10万本の木、各100枚の葉ポリゴン)
- 従来手法: 187ms/frame @ 4K解像度
- Micromap有効化: 108ms/frame(42%削減)
- サブディビジョンレベル: 4(256マイクロ三角形/三角形)
都市シーン(フェンス・看板など透明オブジェクト5万個)
- 従来手法: 93ms/frame @ 4K解像度
- Micromap有効化: 58ms/frame(38%削減)
- サブディビジョンレベル: 3(64マイクロ三角形/三角形)
メモリオーバーヘッドは、森林シーンで従来のBLASに対して+18%(マイクロマップデータ175MB)、都市シーンで+12%(85MB)でした。
最適化のベストプラクティス:
- サブディビジョンレベルの動的選択: カメラ距離に応じてレベル3~5を切り替え。遠方オブジェクトは低レベルで十分。
- 4状態モードの活用: 複雑な植物テクスチャには4状態モード、単純なグリッドには2状態モードを使用。
- 非同期構築: マイクロマップ構築をCompute Queue、BLAS構築をGraphics Queueで並列実行。
- インデックスバッファの最適化:
micromapIndexBufferは連続アクセスになるようソート。
AMD Radeon RX 8000シリーズ(RDNA 4アーキテクチャ、2026年3月発売)でも本拡張をサポートしており、森林シーンで35%の性能向上が報告されています。
出典: Wikimedia Commons / CC0
まとめ
VK_EXT_opacity_micromapは、レイトレーシングにおける透明度処理の根本的な最適化を実現します。
- マイクロマップによる事前判定: 三角形を微細分割し、不透明度状態を事前計算することで、シェーダー実行回数を40%削減
- 3段階の実装: マイクロマップデータ生成 → VkMicromapEXT構築 → BLASバインド
- メモリコストとのトレードオフ: BLAS比+12~18%のメモリで大幅な性能向上を実現
- サブディビジョンレベルの調整: レベル3(64マイクロ三角形)~レベル5(1024マイクロ三角形)でメモリと精度のバランス調整
- ハードウェアサポート: NVIDIA RTX 50シリーズ、AMD Radeon RX 8000シリーズで完全サポート
植物が密集した環境・建築物の多いシーン・大量の透明オブジェクトを含むゲームで、本拡張は必須の最適化技術となります。Vulkan 1.4の普及とともに、今後のレイトレーシングタイトルでの標準実装が期待されます。