From 9b3503eda0b21acfa8340a86224f333ce6e9c8b8 Mon Sep 17 00:00:00 2001 From: Christoph Kubisch Date: Tue, 9 Jun 2026 15:26:39 +0200 Subject: [PATCH 01/12] add KHR_mesh_opacity_micromap Co-authored-by: Nia Bickford Co-authored-by: Arseny Kapoulkine Co-authored-by: Pyarelal Knowles --- .../KHR_mesh_opacity_micromap/README.md | 432 ++++++++++++++++++ .../figures/micromap-subdivision.svg | 74 +++ ...glTF.KHR_mesh_opacity_micromap.schema.json | 67 +++ ...tive.KHR_mesh_opacity_micromap.schema.json | 29 ++ 4 files changed, 602 insertions(+) create mode 100644 extensions/2.0/Khronos/KHR_mesh_opacity_micromap/README.md create mode 100644 extensions/2.0/Khronos/KHR_mesh_opacity_micromap/figures/micromap-subdivision.svg create mode 100644 extensions/2.0/Khronos/KHR_mesh_opacity_micromap/schema/glTF.KHR_mesh_opacity_micromap.schema.json create mode 100644 extensions/2.0/Khronos/KHR_mesh_opacity_micromap/schema/mesh.primitive.KHR_mesh_opacity_micromap.schema.json diff --git a/extensions/2.0/Khronos/KHR_mesh_opacity_micromap/README.md b/extensions/2.0/Khronos/KHR_mesh_opacity_micromap/README.md new file mode 100644 index 0000000000..6d62fd61f0 --- /dev/null +++ b/extensions/2.0/Khronos/KHR_mesh_opacity_micromap/README.md @@ -0,0 +1,432 @@ + + +# KHR\_mesh\_opacity\_micromap + +## Contributors + +- Christoph Kubisch, NVIDIA, [@pixeljetstream](https://github.com/pixeljetstream) +- Nia Bickford, NVIDIA, [@NBickford-NV](https://github.com/NBickford-NV) +- Arseny Kapoulkine, Independent, [@zeuxcg](https://zeux.io/) +- Pyarelal Knowles, NVIDIA, [pknowlesnv](https://github.com/pknowlesnv) + +Copyright 2018-2026 The Khronos Group Inc. All Rights Reserved. glTF is a trademark of The Khronos Group Inc. +See [Appendix](#appendix-full-khronos-copyright-statement) for full Khronos Copyright Statement. + +## Status + +Draft + +## Dependencies + +Written against the glTF 2.0 spec. + +## Overview + +This extension stores **pre-build source data** for opacity micromaps in a glTF asset. It does **not** store serialized micromap acceleration structures produced by a graphics API after build. An opacity micromap compactly encodes per-microtriangle opacity for each base triangle in a mesh. The on-disk representation defined here is layout-compatible with the build inputs of Vulkan `VK_KHR_opacity_micromap` and DirectX 12 `D3D12_RAYTRACING_TIER_1_2` opacity micromaps, so content can be baked once and uploaded when building opacity micromaps and bottom-level ray-tracing acceleration structures. + +The extension defines: + +1. A root-level `micromaps` array describing micromap build inputs (`data`, `triangles`, and usage summary arrays), compatible with `VkAccelerationStructureGeometryMicromapDataKHR` and `D3D12_RAYTRACING_OPACITY_MICROMAP_ARRAY_DESC`. +2. A per-`mesh.primitive` extension describing how geometry triangles map into a micromap (`micromap`, `micromapIndices`, optional `micromapBaseTriangle`), compatible with `VkAccelerationStructureTrianglesOpacityMicromapKHR` and `D3D12_RAYTRACING_GEOMETRY_OMM_LINKAGE_DESC`. + +This extension normatively defines glTF JSON structure and stored buffer layouts. Runtime micromap build options, device limits, and ray-traversal behavior are out of scope and are defined by the target graphics API. + +## Extending the glTF root + +The glTF root object **MAY** define `extensions.KHR_mesh_opacity_micromap` with a `micromaps` array. + +```json +{ + "extensions": { + "KHR_mesh_opacity_micromap": { + "micromaps": [ + { + "data": 0, + "triangles": 1, + "usageCounts": [32, 12], + "usageLevels": [8, 5], + "usageFormats": [1, 1] + } + ] + } + } +} +``` + +Each element of `micromaps` describes one opacity micromap's pre-build source inputs (`data`, `triangles`, and usage summary arrays). + +### Properties (`micromaps[]`) + +| Property | Type | Description | Required | +|----------|------|-------------|----------| +| **data** | `integer` | Index of the `bufferView` containing packed opacity micromap bits. | Yes | +| **triangles** | `integer` | Index of the `bufferView` containing [micromap triangle records](#micromap-triangle-records). | Yes | +| **usageCounts** | `integer[]` | Number of micromap triangles per `(subdivisionLevel, format)` bucket. Parallel to `usageLevels` and `usageFormats`. | Yes | +| **usageLevels** | `integer[]` | Subdivision level per bucket. Parallel to `usageCounts` and `usageFormats`. | Yes | +| **usageFormats** | `integer[]` | Opacity format per bucket (`1` or `2`). Parallel to `usageCounts` and `usageLevels`. | Yes | + +### Validity (root `micromaps[]`) + +For each micromap object to be valid, the following **MUST** hold: + +- `usageCounts`, `usageLevels`, and `usageFormats` **MUST** have equal length. +- Every `usageFormats[i]` **MUST** be `1` or `2`. +- Every `usageCounts[i]` **MUST** be greater than or equal to `1`. +- Every `usageLevels[i]` **MUST** be greater than or equal to `0`. +- Multiple entries with the same `(usageLevels[i], usageFormats[i])` **MAY** appear; validators **MUST** treat the effective count for each distinct pair as the sum of matching `usageCounts` values. +- The sum of all effective `usageCounts` values **MUST** equal the number of 8-byte records in the referenced `triangles` `bufferView` (see [Micromap triangle records](#micromap-triangle-records)). +- For each micromap triangle record, `dataOffset` and the record's `subdivisionLevel` and `format` **MUST** describe a region within the `data` `bufferView` that satisfies [bit packing rules](#bit-packing-in-data), and regions **MUST NOT** overlap. +- Referenced `bufferView` indices **MUST** be valid glTF `bufferView` indices. + +The `triangles` `bufferView` **MUST** contain a tightly packed array of micromap triangle records. If `bufferView.byteStride` is omitted, it **MUST** be treated as `8`. If `byteStride` is present, it **MUST** be greater than or equal to `8` and a multiple of `4`. + +## Extending mesh primitives + +Each `mesh.primitive` **MAY** define `extensions.KHR_mesh_opacity_micromap`. + +```json +{ + "meshes": [ + { + "primitives": [ + { + "attributes": { "POSITION": 0 }, + "indices": 1, + "mode": 4, + "extensions": { + "KHR_mesh_opacity_micromap": { + "micromap": 0, + "micromapIndices": 2, + "micromapBaseTriangle": 0 + } + } + } + ] + } + ] +} +``` + +### Properties + +| Property | Type | Description | Required | +|----------|------|-------------|----------| +| **micromap** | `integer` | Index into root `extensions.KHR_mesh_opacity_micromap.micromaps`. | Yes | +| **micromapIndices** | `integer` | Accessor index for per-geometry-triangle micromap lookup values. | No | +| **micromapBaseTriangle** | `integer` | Offset added to each non-special lookup value. Default: `0`. | No | + +See [Per-primitive micromap indices](#per-primitive-micromap-indices) for accessor and lookup rules. + +## Opacity micromap encoding + +This section defines the stored pre-build micromap representation referenced by root `micromaps` objects. It does not define post-build serialized micromap acceleration structure contents. + +### Subdivision levels + +For subdivision level \(L\), a base triangle is subdivided into \(4^L\) microtriangles. + +Each micromap triangle record specifies its own subdivision level and format. The `usageCounts`, `usageLevels`, and `usageFormats` arrays summarize how many micromap triangles exist for each `(subdivisionLevel, format)` pair in a micromap. + +### Microtriangle ordering + +Microtriangle indices within a micromap triangle **MUST** use the recursive space-filling curve ordering illustrated in [Appendix A](#appendix-a-microtriangle-indexing). Authoring tools **MUST** encode `data` bits in that linear index order. + +At runtime, graphics APIs map intersection barycentrics to a microtriangle index using the same ordering. + +### Formats and state values + +`usageFormats` values and micromap triangle record `format` fields **MUST** be one of: + +| Value | Meaning | +|------:|---------| +| `1` | Two-state format: one bit per microtriangle (opaque or transparent). | +| `2` | Four-state format: two bits per microtriangle. | + +For four-state data, each two-bit microtriangle value **MUST** be one of: + +| Value | Meaning | +|------:|---------| +| `0` | Transparent | +| `1` | Opaque | +| `2` | Unknown transparent | +| `3` | Unknown opaque | + +For two-state data, each one-bit microtriangle value **MUST** be interpreted as: + +| Value | Meaning | +|------:|---------| +| `0` | Transparent | +| `1` | Opaque | + +How these stored values affect ray traversal is defined by the target graphics API and is not specified by this extension. + +### Bit packing in `data` + +The `data` `bufferView` contains packed microtriangle state bits for all micromap triangles. + +- Two-state (`format` `1`) data **MUST** use one bit per microtriangle. +- Four-state (`format` `2`) data **MUST** use two bits per microtriangle. +- Bits **MUST** be packed from least significant bit to most significant bit within each byte. +- For a micromap triangle with subdivision level \(L\) and format \(F\), the number of bytes required at `dataOffset` **MUST** be: + +\[ +\left\lceil \frac{4^L \cdot b}{8} \right\rceil +\] + +where \(b\) is `1` for format `1` and `2` for format `2`. + +Unused bits in the final byte of a micromap triangle's region **MUST** be ignored. + +### Micromap triangle records + +Each element of the `triangles` `bufferView` **MUST** be an 8-byte record with the following layout: + +| Offset (bytes) | Type | Field | +|---------------:|------|-------| +| `0` | `uint32` | `dataOffset` | +| `4` | `uint16` | `subdivisionLevel` | +| `6` | `uint16` | `format` | + +- `dataOffset` **MUST** be a byte offset relative to the start of the `data` `bufferView`. +- `subdivisionLevel` **MUST** be greater than or equal to `0`. +- `format` **MUST** be `1` or `2`. + +The number of records in `triangles` **MUST** equal the sum of all `usageCounts` values for that micromap. + +This record layout is compatible with `VkMicromapTriangleKHR` in Vulkan `VK_KHR_opacity_micromap` and `D3D12_RAYTRACING_OPACITY_MICROMAP_DESC` in DirectX 12 when consumed as micromap build input. + +## Per-primitive micromap indices + +Primitives using this extension **MUST** have `mode` `TRIANGLES` (`4`). + +The extension `micromapIndices` accessor is separate from the geometry `indices` accessor. It stores one lookup value per geometry triangle. +If provided, the accessor **MUST** have a `count` equal to the primitive's triangle count and element `t` of the `micromapIndices` accessor **MUST** correspond to geometry triangle `t`. + +### Lookup resolution + +For geometry triangle `t`, let `v` be the accessor value at element `t`. If no accessor is provided `v` equals `t`. + +- If `v` is a [special index](#special-indices), no micromap triangle fetch is performed. +- Otherwise, the micromap triangle index **MUST** be computed as `v + micromapBaseTriangle` and **MUST** reference a record in the micromap's `triangles` array. + +This lookup resolution is compatible with `VkAccelerationStructureTrianglesOpacityMicromapKHR` in Vulkan `VK_KHR_opacity_micromap` and `D3D12_RAYTRACING_GEOMETRY_OMM_LINKAGE_DESC` in DirectX 12. + +### Special indices + +The following signed lookup values **MUST** be supported: + +| Signed value | Meaning | +|-------------:|---------| +| `-1` | Fully transparent | +| `-2` | Fully opaque | +| `-3` | Fully unknown transparent | +| `-4` | Fully unknown opaque | + +### Encoding in accessors + +If `micromapIndices` is provided, the accessor **MUST** have `type` `SCALAR` and `componentType` one of `5121` (`UNSIGNED_BYTE`), `5123` (`UNSIGNED_SHORT`), `5125` (`UNSIGNED_INT`), `5122` (`SHORT`), or `5124` (`INT`). + +When `componentType` is `5121` (`UNSIGNED_BYTE`), `5123` (`UNSIGNED_SHORT`), or `5125` (`UNSIGNED_INT`): + +- Non-special micromap triangle indices **MUST** be non-negative. +- Special indices **MUST** be stored as the two's-complement bit pattern of the signed value in the accessor's component type (for example, `4294967295` represents `-1` with `UNSIGNED_INT`). + +When `componentType` is `5122` (`SHORT`) or `5124` (`INT`): + +- Lookup values **MAY** be stored directly as signed integers, including `-1` through `-4`. + +## Schema + +- [glTF.KHR_mesh_opacity_micromap.schema.json](schema/glTF.KHR_mesh_opacity_micromap.schema.json) +- [mesh.primitive.KHR_mesh_opacity_micromap.schema.json](schema/mesh.primitive.KHR_mesh_opacity_micromap.schema.json) + +## Reference + +### Normative external references + +When mapping stored glTF data to graphics API micromap build inputs, implementations **MUST** preserve compatibility with the field correspondences below. + +| glTF | Vulkan (`VK_KHR_opacity_micromap`) | DirectX 12 | +|------|-------------------------------------|------------| +| | `VkMicromapUsageKHR` | `D3D12_RAYTRACING_OPACITY_MICROMAP_HISTOGRAM_ENTRY` | +| `micromaps[].usageCounts[i]` | `count` | `Count` | +| `micromaps[].usageLevels[i]` | `subdivisionLevel` | `SubdivisionLevel` | +| `micromaps[].usageFormats[i]` | `format` | `Format` | +| | | | +| | `VkMicromapTriangleKHR` | `D3D12_RAYTRACING_OPACITY_MICROMAP_DESC` | +| `micromaps[].triangles[t].dataOffset` | `dataOffset` | `ByteOffset` | +| `micromaps[].triangles[t].subdivisionLevel` | `subdivisionLevel` | `SubdivisionLevel` | +| `micromaps[].triangles[t].format` | `format` | `Format` | +| | | | +| global micromap | `VkAccelerationStructureGeometryMicromapDataKHR` | `D3D12_RAYTRACING_OPACITY_MICROMAP_ARRAY_DESC` | +| `micromaps[].data` | `data` | `InputBuffer` | +| `micromaps[].triangles` | `triangleArray` | `PerOmmDescs` | +| `micromaps[].triangles` `bufferView.byteStride` | `triangleArrayStride` | `PerOmmDescs` stride | +| `usageCounts` length | `usageCountsCount` | `NumOmmHistogramEntries` | +| `usageCounts` / `usageLevels` / `usageFormats` | `pUsageCounts` | `pOmmHistogram` | +| | | | +| mesh primitive `KHR_mesh_opacity_micromap` | `VkAccelerationStructureTrianglesOpacityMicromapKHR` | `D3D12_RAYTRACING_GEOMETRY_OMM_LINKAGE_DESC` | +| `micromap` | `micromap` | `OpacityMicromapArray` | +| `micromapIndices` (accessor buffer) | `indexBuffer` | `OpacityMicromapIndexBuffer` | +| `micromapIndices` (`componentType`) | `indexType` | `OpacityMicromapIndexFormat` | +| `micromapIndices` (`byteStride`) | `indexStride` | `OpacityMicromapIndexBuffer` stride | +| `micromapBaseTriangle` | `baseTriangle` | `OpacityMicromapBaseLocation` | + +References: + +- [Vulkan `VK_KHR_opacity_micromap`](https://registry.khronos.org/vulkan/specs/latest/man/html/VK_KHR_opacity_micromap.html) +- [Microsoft DirectX Raytracing — opacity micromaps](https://microsoft.github.io/DirectX-Specs/d3d/Raytracing.html#opacity-micromaps) + +### Informative references + +- [Vulkan proposals index](https://docs.vulkan.org/proposals/) — design rationale for opacity micromaps. +- [SPIR-V `SPV_KHR_opacity_micromap`](https://github.khronos.org/SPIRV-Registry/extensions/KHR/SPV_KHR_opacity_micromap.html) +- [NVIDIA Opacity Micromap SDK](https://developer.nvidia.com/rtx/ray-tracing/opacity-micromap) + +## Appendix A: Microtriangle indexing + +Opacity micromap bits in the `data` buffer are indexed by a hierarchical space-filling curve over recursively subdivided microtriangles. + +### Recursive subdivision + +A base triangle with vertices `v0`, `v1`, and `v2` is split into four congruent microtriangles by connecting edge midpoints. This split is applied recursively: subdivision level `1` yields `4` microtriangles, and each additional level multiplies the count by `4` (level `L` yields `4^L` microtriangles). + +The figure below shows level `1` (left) and level `2` (right). Microtriangle labels `0` … `3` at level `1` and `0` … `15` at level `2` are the linear indices used when packing opacity bits in `data` (least significant bit first within each byte). + +Recursive micromap subdivision and microtriangle index ordering at levels 1 and 2 + +In the diagram: + +- Each inner triangle is one microtriangle at the shown subdivision level. +- The numbered labels are the linear microtriangle indices at that level. +- The dot in each level-`1` microtriangle marks the entry point of the curve into that sub-triangle. +- Blue arrows show the default vertex winding used when traversing a sub-triangle's children. +- Red arrows show sub-triangles whose child winding is flipped at the next subdivision level. + +### Curve traversal order + +Within a microtriangle, child sub-triangles are visited in this order: + +1. The sub-triangle nearest vertex `v0`. +2. The middle sub-triangle, using flipped child ordering. +3. The sub-triangle nearest vertex `v1`. +4. The sub-triangle nearest vertex `v2`, using flipped child ordering. + +This traversal is applied recursively. The resulting linear index is the position of a microtriangle's opacity value in the packed `data` bitstream for that micromap triangle record. + +At intersection time, graphics APIs quantize barycentric coordinates \((u, v)\) and map them to the same linear index. + +### Reference function + +The following function is a reference implementation that maps quantized barycentric coordinates \((u, v)\) inside a base triangle to a microtriangle index for subdivision level `level`, using the recursive splitting order described above. It is reproduced from the Vulkan `VK_KHR_opacity_micromap` specification reference code. + +```cpp +uint32_t BarycentricsToSpaceFillingCurveIndex(float u, float v, uint32_t level) +{ + u = clamp(u, 0.0f, 1.0f); + v = clamp(v, 0.0f, 1.0f); + + uint32_t iu, iv, iw; + + // Quantize barycentric coordinates + float fu = u * (1u << level); + float fv = v * (1u << level); + + iu = (uint32_t)fu; + iv = (uint32_t)fv; + + float uf = fu - float(iu); + float vf = fv - float(iv); + + if (iu >= (1u << level)) iu = (1u << level) - 1u; + if (iv >= (1u << level)) iv = (1u << level) - 1u; + + uint32_t iuv = iu + iv; + + if (iuv >= (1u << level)) + iu -= iuv - (1u << level) + 1u; + + iw = ~(iu + iv); + + if (uf + vf >= 1.0f && iuv < (1u << level) - 1u) --iw; + + uint32_t b0 = ~(iu ^ iw); + b0 &= ((1u << level) - 1u); + uint32_t t = (iu ^ iv) & b0; + + uint32_t f = t; + f ^= f >> 1u; + f ^= f >> 2u; + f ^= f >> 4u; + f ^= f >> 8u; + uint32_t b1 = ((f ^ iu) & ~b0) | t; + + // Interleave bits + b0 = (b0 | (b0 << 8u)) & 0x00ff00ffu; + b0 = (b0 | (b0 << 4u)) & 0x0f0f0f0fu; + b0 = (b0 | (b0 << 2u)) & 0x33333333u; + b0 = (b0 | (b0 << 1u)) & 0x55555555u; + b1 = (b1 | (b1 << 8u)) & 0x00ff00ffu; + b1 = (b1 | (b1 << 4u)) & 0x0f0f0f0fu; + b1 = (b1 | (b1 << 2u)) & 0x33333333u; + b1 = (b1 | (b1 << 1u)) & 0x55555555u; + + return b0 | (b1 << 1u); +} +``` + +## Appendix: Full Khronos Copyright Statement + +Copyright 2018-2026 The Khronos Group Inc. + +Some parts of this Specification are purely informative and do not define requirements +necessary for compliance and so are outside the Scope of this Specification. These +parts of the Specification are marked as being non-normative, or identified as +**Implementation Notes**. + +Where this Specification includes normative references to external documents, only the +specifically identified sections and functionality of those external documents are in +Scope. Requirements defined by external documents not created by Khronos may contain +contributions from non-members of Khronos not covered by the Khronos Intellectual +Property Rights Policy. + +This specification is protected by copyright laws and contains material proprietary +to Khronos. Except as described by these terms, it or any components +may not be reproduced, republished, distributed, transmitted, displayed, broadcast +or otherwise exploited in any manner without the express prior written permission +of Khronos. + +This specification has been created under the Khronos Intellectual Property Rights +Policy, which is Attachment A of the Khronos Group Membership Agreement available at +www.khronos.org/files/member_agreement.pdf. Khronos grants a conditional +copyright license to use and reproduce the unmodified specification for any purpose, +without fee or royalty, EXCEPT no licenses to any patent, trademark or other +intellectual property rights are granted under these terms. Parties desiring to +implement the specification and make use of Khronos trademarks in relation to that +implementation, and receive reciprocal patent license protection under the Khronos +IP Policy must become Adopters and confirm the implementation as conformant under +the process defined by Khronos for this specification; +see https://www.khronos.org/adopters. + +Khronos makes no, and expressly disclaims any, representations or warranties, +express or implied, regarding this specification, including, without limitation: +merchantability, fitness for a particular purpose, non-infringement of any +intellectual property, correctness, accuracy, completeness, timeliness, and +reliability. Under no circumstances will Khronos, or any of its Promoters, +Contributors or Members, or their respective partners, officers, directors, +employees, agents or representatives be liable for any damages, whether direct, +indirect, special or consequential damages for lost revenues, lost profits, or +otherwise, arising from or in connection with these materials. + +Vulkan is a registered trademark and Khronos, OpenXR, SPIR, SPIR-V, SYCL, WebGL, +WebCL, OpenVX, OpenVG, EGL, COLLADA, glTF, NNEF, OpenKODE, OpenKCAM, StreamInput, +OpenWF, OpenSL ES, OpenMAX, OpenMAX AL, OpenMAX IL, OpenMAX DL, OpenML and DevU are +trademarks of The Khronos Group Inc. ASTC is a trademark of ARM Holdings PLC, +OpenCL is a trademark of Apple Inc. and OpenGL and OpenML are registered trademarks +and the OpenGL ES and OpenGL SC logos are trademarks of Silicon Graphics +International used under license by Khronos. All other product names, trademarks, +and/or company names are used solely for identification and belong to their +respective owners. diff --git a/extensions/2.0/Khronos/KHR_mesh_opacity_micromap/figures/micromap-subdivision.svg b/extensions/2.0/Khronos/KHR_mesh_opacity_micromap/figures/micromap-subdivision.svg new file mode 100644 index 0000000000..922a6fde74 --- /dev/null +++ b/extensions/2.0/Khronos/KHR_mesh_opacity_micromap/figures/micromap-subdivision.svg @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + v0 + v1 + v2 + 0 + 1 + 2 + 3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + v0 + v1 + v2 + + + + + 4 + 5 + 6 + 7 + 0 + 1 + 2 + 3 + 8 + 9 + 10 + 11 + 12 + 13 + 14 + 15 + Level 1 + Level 2​ + + diff --git a/extensions/2.0/Khronos/KHR_mesh_opacity_micromap/schema/glTF.KHR_mesh_opacity_micromap.schema.json b/extensions/2.0/Khronos/KHR_mesh_opacity_micromap/schema/glTF.KHR_mesh_opacity_micromap.schema.json new file mode 100644 index 0000000000..8c3171dbf1 --- /dev/null +++ b/extensions/2.0/Khronos/KHR_mesh_opacity_micromap/schema/glTF.KHR_mesh_opacity_micromap.schema.json @@ -0,0 +1,67 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "title": "KHR_mesh_opacity_micromap glTF Document Extension", + "type": "object", + "description": "Root-level opacity micromap build data.", + "allOf": [ { "$ref": "glTFProperty.schema.json" } ], + "properties": { + "micromaps": { + "type": "array", + "description": "An array of opacity micromap build data objects.", + "gltf_detailedDescription": "Each element describes one opacity micromap's packed data, triangle records, and usage summary arrays.", + "items": { + "type": "object", + "allOf": [ { "$ref": "glTFProperty.schema.json" } ], + "properties": { + "data": { + "allOf": [ { "$ref": "glTFid.schema.json" } ], + "description": "The index of the bufferView containing packed opacity micromap data bits.", + "gltf_detailedDescription": "Packed microtriangle opacity bits in LSB-to-MSB order within each byte." + }, + "triangles": { + "allOf": [ { "$ref": "glTFid.schema.json" } ], + "description": "The index of the bufferView containing micromap triangle records.", + "gltf_detailedDescription": "An array of 8-byte records: uint32 dataOffset, uint16 subdivisionLevel, uint16 format." + }, + "usageCounts": { + "type": "array", + "description": "Per-bucket micromap triangle counts.", + "gltf_detailedDescription": "Parallel to usageLevels and usageFormats. The sum of all values must equal the number of records in triangles.", + "items": { + "type": "integer", + "minimum": 1 + }, + "minItems": 1 + }, + "usageLevels": { + "type": "array", + "description": "Per-bucket subdivision levels.", + "gltf_detailedDescription": "Parallel to usageCounts and usageFormats.", + "items": { + "type": "integer", + "minimum": 0 + }, + "minItems": 1 + }, + "usageFormats": { + "type": "array", + "description": "Per-bucket opacity formats.", + "gltf_detailedDescription": "Parallel to usageCounts and usageLevels. Values must be 1 (two-state) or 2 (four-state).", + "items": { + "type": "integer", + "enum": [ 1, 2 ] + }, + "minItems": 1 + }, + "extensions": { }, + "extras": { } + }, + "required": [ "data", "triangles", "usageCounts", "usageLevels", "usageFormats" ] + }, + "minItems": 1 + }, + "extensions": { }, + "extras": { } + }, + "required": [ "micromaps" ] +} diff --git a/extensions/2.0/Khronos/KHR_mesh_opacity_micromap/schema/mesh.primitive.KHR_mesh_opacity_micromap.schema.json b/extensions/2.0/Khronos/KHR_mesh_opacity_micromap/schema/mesh.primitive.KHR_mesh_opacity_micromap.schema.json new file mode 100644 index 0000000000..0218483ecb --- /dev/null +++ b/extensions/2.0/Khronos/KHR_mesh_opacity_micromap/schema/mesh.primitive.KHR_mesh_opacity_micromap.schema.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema", + "title": "KHR_mesh_opacity_micromap glTF Mesh Primitive Extension", + "type": "object", + "description": "Per-primitive opacity micromap mapping data.", + "allOf": [ { "$ref": "glTFProperty.schema.json" } ], + "properties": { + "micromap": { + "allOf": [ { "$ref": "glTFid.schema.json" } ], + "description": "Index into the root-level KHR_mesh_opacity_micromap.micromaps array.", + "gltf_detailedDescription": "References the micromap build data used when attaching opacity micromaps to this primitive's triangles." + }, + "micromapIndices": { + "allOf": [ { "$ref": "glTFid.schema.json" } ], + "description": "Accessor index for per-geometry-triangle micromap lookup values.", + "gltf_detailedDescription": "Optional accessor with one scalar per geometry triangle. If omitted, geometry triangle t maps to micromap triangle t plus micromapBaseTriangle. Values are either special indices (-1 through -4) or non-negative micromap triangle indices. When provided, the accessor count must equal the primitive triangle count." + }, + "micromapBaseTriangle": { + "type": "integer", + "description": "Offset added to non-special micromap indices before lookup.", + "gltf_detailedDescription": "Added to each non-special lookup value before indexing into the micromap triangles array. Defaults to 0.", + "minimum": 0, + "default": 0 + }, + "extensions": { }, + "extras": { } + }, + "required": [ "micromap" ] +} From cf45ae0784130b4fd073f03bd85b660deaeee8f7 Mon Sep 17 00:00:00 2001 From: Christoph Kubisch Date: Tue, 7 Jul 2026 15:23:24 +0200 Subject: [PATCH 02/12] rename to EXT --- .../EXT_mesh_opacity_micromap}/README.md | 18 +++++++++--------- .../figures/micromap-subdivision.svg | 0 ...glTF.EXT_mesh_opacity_micromap.schema.json} | 2 +- ...tive.EXT_mesh_opacity_micromap.schema.json} | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) rename extensions/2.0/{Khronos/KHR_mesh_opacity_micromap => Vendor/EXT_mesh_opacity_micromap}/README.md (97%) rename extensions/2.0/{Khronos/KHR_mesh_opacity_micromap => Vendor/EXT_mesh_opacity_micromap}/figures/micromap-subdivision.svg (100%) rename extensions/2.0/{Khronos/KHR_mesh_opacity_micromap/schema/glTF.KHR_mesh_opacity_micromap.schema.json => Vendor/EXT_mesh_opacity_micromap/schema/glTF.EXT_mesh_opacity_micromap.schema.json} (98%) rename extensions/2.0/{Khronos/KHR_mesh_opacity_micromap/schema/mesh.primitive.KHR_mesh_opacity_micromap.schema.json => Vendor/EXT_mesh_opacity_micromap/schema/mesh.primitive.EXT_mesh_opacity_micromap.schema.json} (92%) diff --git a/extensions/2.0/Khronos/KHR_mesh_opacity_micromap/README.md b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md similarity index 97% rename from extensions/2.0/Khronos/KHR_mesh_opacity_micromap/README.md rename to extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md index 6d62fd61f0..9f6d73692c 100644 --- a/extensions/2.0/Khronos/KHR_mesh_opacity_micromap/README.md +++ b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md @@ -3,7 +3,7 @@ Copyright 2018-2026 The Khronos Group Inc. SPDX-License-Identifier: LicenseRef-KhronosSpecCopyright --> -# KHR\_mesh\_opacity\_micromap +# EXT\_mesh\_opacity\_micromap ## Contributors @@ -36,12 +36,12 @@ This extension normatively defines glTF JSON structure and stored buffer layouts ## Extending the glTF root -The glTF root object **MAY** define `extensions.KHR_mesh_opacity_micromap` with a `micromaps` array. +The glTF root object **MAY** define `extensions.EXT_mesh_opacity_micromap` with a `micromaps` array. ```json { "extensions": { - "KHR_mesh_opacity_micromap": { + "EXT_mesh_opacity_micromap": { "micromaps": [ { "data": 0, @@ -85,7 +85,7 @@ The `triangles` `bufferView` **MUST** contain a tightly packed array of micromap ## Extending mesh primitives -Each `mesh.primitive` **MAY** define `extensions.KHR_mesh_opacity_micromap`. +Each `mesh.primitive` **MAY** define `extensions.EXT_mesh_opacity_micromap`. ```json { @@ -97,7 +97,7 @@ Each `mesh.primitive` **MAY** define `extensions.KHR_mesh_opacity_micromap`. "indices": 1, "mode": 4, "extensions": { - "KHR_mesh_opacity_micromap": { + "EXT_mesh_opacity_micromap": { "micromap": 0, "micromapIndices": 2, "micromapBaseTriangle": 0 @@ -114,7 +114,7 @@ Each `mesh.primitive` **MAY** define `extensions.KHR_mesh_opacity_micromap`. | Property | Type | Description | Required | |----------|------|-------------|----------| -| **micromap** | `integer` | Index into root `extensions.KHR_mesh_opacity_micromap.micromaps`. | Yes | +| **micromap** | `integer` | Index into root `extensions.EXT_mesh_opacity_micromap.micromaps`. | Yes | | **micromapIndices** | `integer` | Accessor index for per-geometry-triangle micromap lookup values. | No | | **micromapBaseTriangle** | `integer` | Offset added to each non-special lookup value. Default: `0`. | No | @@ -240,8 +240,8 @@ When `componentType` is `5122` (`SHORT`) or `5124` (`INT`): ## Schema -- [glTF.KHR_mesh_opacity_micromap.schema.json](schema/glTF.KHR_mesh_opacity_micromap.schema.json) -- [mesh.primitive.KHR_mesh_opacity_micromap.schema.json](schema/mesh.primitive.KHR_mesh_opacity_micromap.schema.json) +- [glTF.EXT_mesh_opacity_micromap.schema.json](schema/glTF.EXT_mesh_opacity_micromap.schema.json) +- [mesh.primitive.EXT_mesh_opacity_micromap.schema.json](schema/mesh.primitive.EXT_mesh_opacity_micromap.schema.json) ## Reference @@ -268,7 +268,7 @@ When mapping stored glTF data to graphics API micromap build inputs, implementat | `usageCounts` length | `usageCountsCount` | `NumOmmHistogramEntries` | | `usageCounts` / `usageLevels` / `usageFormats` | `pUsageCounts` | `pOmmHistogram` | | | | | -| mesh primitive `KHR_mesh_opacity_micromap` | `VkAccelerationStructureTrianglesOpacityMicromapKHR` | `D3D12_RAYTRACING_GEOMETRY_OMM_LINKAGE_DESC` | +| mesh primitive `EXT_mesh_opacity_micromap` | `VkAccelerationStructureTrianglesOpacityMicromapKHR` | `D3D12_RAYTRACING_GEOMETRY_OMM_LINKAGE_DESC` | | `micromap` | `micromap` | `OpacityMicromapArray` | | `micromapIndices` (accessor buffer) | `indexBuffer` | `OpacityMicromapIndexBuffer` | | `micromapIndices` (`componentType`) | `indexType` | `OpacityMicromapIndexFormat` | diff --git a/extensions/2.0/Khronos/KHR_mesh_opacity_micromap/figures/micromap-subdivision.svg b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/figures/micromap-subdivision.svg similarity index 100% rename from extensions/2.0/Khronos/KHR_mesh_opacity_micromap/figures/micromap-subdivision.svg rename to extensions/2.0/Vendor/EXT_mesh_opacity_micromap/figures/micromap-subdivision.svg diff --git a/extensions/2.0/Khronos/KHR_mesh_opacity_micromap/schema/glTF.KHR_mesh_opacity_micromap.schema.json b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/schema/glTF.EXT_mesh_opacity_micromap.schema.json similarity index 98% rename from extensions/2.0/Khronos/KHR_mesh_opacity_micromap/schema/glTF.KHR_mesh_opacity_micromap.schema.json rename to extensions/2.0/Vendor/EXT_mesh_opacity_micromap/schema/glTF.EXT_mesh_opacity_micromap.schema.json index 8c3171dbf1..1d0a1a92f4 100644 --- a/extensions/2.0/Khronos/KHR_mesh_opacity_micromap/schema/glTF.KHR_mesh_opacity_micromap.schema.json +++ b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/schema/glTF.EXT_mesh_opacity_micromap.schema.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/draft-04/schema", - "title": "KHR_mesh_opacity_micromap glTF Document Extension", + "title": "EXT_mesh_opacity_micromap glTF Document Extension", "type": "object", "description": "Root-level opacity micromap build data.", "allOf": [ { "$ref": "glTFProperty.schema.json" } ], diff --git a/extensions/2.0/Khronos/KHR_mesh_opacity_micromap/schema/mesh.primitive.KHR_mesh_opacity_micromap.schema.json b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/schema/mesh.primitive.EXT_mesh_opacity_micromap.schema.json similarity index 92% rename from extensions/2.0/Khronos/KHR_mesh_opacity_micromap/schema/mesh.primitive.KHR_mesh_opacity_micromap.schema.json rename to extensions/2.0/Vendor/EXT_mesh_opacity_micromap/schema/mesh.primitive.EXT_mesh_opacity_micromap.schema.json index 0218483ecb..81e6b41a5b 100644 --- a/extensions/2.0/Khronos/KHR_mesh_opacity_micromap/schema/mesh.primitive.KHR_mesh_opacity_micromap.schema.json +++ b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/schema/mesh.primitive.EXT_mesh_opacity_micromap.schema.json @@ -1,13 +1,13 @@ { "$schema": "http://json-schema.org/draft-04/schema", - "title": "KHR_mesh_opacity_micromap glTF Mesh Primitive Extension", + "title": "EXT_mesh_opacity_micromap glTF Mesh Primitive Extension", "type": "object", "description": "Per-primitive opacity micromap mapping data.", "allOf": [ { "$ref": "glTFProperty.schema.json" } ], "properties": { "micromap": { "allOf": [ { "$ref": "glTFid.schema.json" } ], - "description": "Index into the root-level KHR_mesh_opacity_micromap.micromaps array.", + "description": "Index into the root-level EXT_mesh_opacity_micromap.micromaps array.", "gltf_detailedDescription": "References the micromap build data used when attaching opacity micromaps to this primitive's triangles." }, "micromapIndices": { From 1ac6c75731a547478622d610fb288dc4dbf588c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin-Karl=20Lefran=C3=A7ois?= Date: Mon, 5 Oct 2026 13:13:50 +0200 Subject: [PATCH 03/12] EXT_mesh_opacity_micromap: editorial fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Use GitHub-renderable math delimiters ($...$, $$...$$) instead of \(...\) and \[...\] - Fix @pknowlesnv contributor handle; add Martin-Karl Lefrançois as contributor - Refer to the core glTF definition of triangle count for micromapIndices - Add a Known Implementations section - Clarify that API micromap fields in the mapping table refer to the built micromap object - Mark informative references as non-normative; link the OMM SDK on GitHub --- .../EXT_mesh_opacity_micromap/README.md | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md index 9f6d73692c..75fe3ba2bc 100644 --- a/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md +++ b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md @@ -10,7 +10,8 @@ SPDX-License-Identifier: LicenseRef-KhronosSpecCopyright - Christoph Kubisch, NVIDIA, [@pixeljetstream](https://github.com/pixeljetstream) - Nia Bickford, NVIDIA, [@NBickford-NV](https://github.com/NBickford-NV) - Arseny Kapoulkine, Independent, [@zeuxcg](https://zeux.io/) -- Pyarelal Knowles, NVIDIA, [pknowlesnv](https://github.com/pknowlesnv) +- Pyarelal Knowles, NVIDIA, [@pknowlesnv](https://github.com/pknowlesnv) +- Martin-Karl Lefrançois, NVIDIA, [@mklefrancois](https://github.com/mklefrancois) Copyright 2018-2026 The Khronos Group Inc. All Rights Reserved. glTF is a trademark of The Khronos Group Inc. See [Appendix](#appendix-full-khronos-copyright-statement) for full Khronos Copyright Statement. @@ -126,7 +127,7 @@ This section defines the stored pre-build micromap representation referenced by ### Subdivision levels -For subdivision level \(L\), a base triangle is subdivided into \(4^L\) microtriangles. +For subdivision level $L$, a base triangle is subdivided into $4^L$ microtriangles. Each micromap triangle record specifies its own subdivision level and format. The `usageCounts`, `usageLevels`, and `usageFormats` arrays summarize how many micromap triangles exist for each `(subdivisionLevel, format)` pair in a micromap. @@ -170,13 +171,13 @@ The `data` `bufferView` contains packed microtriangle state bits for all microma - Two-state (`format` `1`) data **MUST** use one bit per microtriangle. - Four-state (`format` `2`) data **MUST** use two bits per microtriangle. - Bits **MUST** be packed from least significant bit to most significant bit within each byte. -- For a micromap triangle with subdivision level \(L\) and format \(F\), the number of bytes required at `dataOffset` **MUST** be: +- For a micromap triangle with subdivision level $L$ and format $F$, the number of bytes required at `dataOffset` **MUST** be: -\[ +$$ \left\lceil \frac{4^L \cdot b}{8} \right\rceil -\] +$$ -where \(b\) is `1` for format `1` and `2` for format `2`. +where $b$ is `1` for format `1` and `2` for format `2`. Unused bits in the final byte of a micromap triangle's region **MUST** be ignored. @@ -203,7 +204,7 @@ This record layout is compatible with `VkMicromapTriangleKHR` in Vulkan `VK_KHR_ Primitives using this extension **MUST** have `mode` `TRIANGLES` (`4`). The extension `micromapIndices` accessor is separate from the geometry `indices` accessor. It stores one lookup value per geometry triangle. -If provided, the accessor **MUST** have a `count` equal to the primitive's triangle count and element `t` of the `micromapIndices` accessor **MUST** correspond to geometry triangle `t`. +If provided, the accessor **MUST** have a `count` equal to the primitive's triangle count (as defined by the glTF 2.0 specification for `TRIANGLES` mode: the `indices` accessor `count` divided by `3`, or the `POSITION` accessor `count` divided by `3` when `indices` is undefined) and element `t` of the `micromapIndices` accessor **MUST** correspond to geometry triangle `t`. ### Lookup resolution @@ -243,6 +244,11 @@ When `componentType` is `5122` (`SHORT`) or `5124` (`INT`): - [glTF.EXT_mesh_opacity_micromap.schema.json](schema/glTF.EXT_mesh_opacity_micromap.schema.json) - [mesh.primitive.EXT_mesh_opacity_micromap.schema.json](schema/mesh.primitive.EXT_mesh_opacity_micromap.schema.json) +## Known Implementations + +- [nvpro-samples/vk_gltf_renderer](https://github.com/nvpro-samples/vk_gltf_renderer): loads the extension and builds opacity micromaps for ray tracing with Vulkan `VK_KHR_opacity_micromap`. +- [nvpro-samples/gltf_omm_baker](https://github.com/nvpro-samples/gltf_omm_baker): bakes opacity micromaps from material alpha coverage and writes them using this extension. + ## Reference ### Normative external references @@ -275,6 +281,8 @@ When mapping stored glTF data to graphics API micromap build inputs, implementat | `micromapIndices` (`byteStride`) | `indexStride` | `OpacityMicromapIndexBuffer` stride | | `micromapBaseTriangle` | `baseTriangle` | `OpacityMicromapBaseLocation` | +In the mesh primitive rows, the Vulkan `micromap` and DirectX 12 `OpacityMicromapArray` fields refer to the micromap object built from the referenced `micromaps` element, not to the stored glTF data itself. + References: - [Vulkan `VK_KHR_opacity_micromap`](https://registry.khronos.org/vulkan/specs/latest/man/html/VK_KHR_opacity_micromap.html) @@ -282,9 +290,11 @@ References: ### Informative references +*This section is non-normative.* + - [Vulkan proposals index](https://docs.vulkan.org/proposals/) — design rationale for opacity micromaps. - [SPIR-V `SPV_KHR_opacity_micromap`](https://github.khronos.org/SPIRV-Registry/extensions/KHR/SPV_KHR_opacity_micromap.html) -- [NVIDIA Opacity Micromap SDK](https://developer.nvidia.com/rtx/ray-tracing/opacity-micromap) +- [NVIDIA Opacity Micromap SDK](https://github.com/NVIDIA-RTX/OMM) ## Appendix A: Microtriangle indexing @@ -317,11 +327,11 @@ Within a microtriangle, child sub-triangles are visited in this order: This traversal is applied recursively. The resulting linear index is the position of a microtriangle's opacity value in the packed `data` bitstream for that micromap triangle record. -At intersection time, graphics APIs quantize barycentric coordinates \((u, v)\) and map them to the same linear index. +At intersection time, graphics APIs quantize barycentric coordinates $(u, v)$ and map them to the same linear index. ### Reference function -The following function is a reference implementation that maps quantized barycentric coordinates \((u, v)\) inside a base triangle to a microtriangle index for subdivision level `level`, using the recursive splitting order described above. It is reproduced from the Vulkan `VK_KHR_opacity_micromap` specification reference code. +The following function is a reference implementation that maps quantized barycentric coordinates $(u, v)$ inside a base triangle to a microtriangle index for subdivision level `level`, using the recursive splitting order described above. It is reproduced from the Vulkan `VK_KHR_opacity_micromap` specification reference code. ```cpp uint32_t BarycentricsToSpaceFillingCurveIndex(float u, float v, uint32_t level) From b2c0bb5f325cd7698e04fa3c12430dab5b24f3bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin-Karl=20Lefran=C3=A7ois?= Date: Mon, 5 Oct 2026 15:43:39 +0200 Subject: [PATCH 04/12] EXT_mesh_opacity_micromap: restructure spec for review README - Define base triangle vertices v0/v1/v2 from TRIANGLES index order and the (u, v) barycentric convention; note that reordering or rotating triangles invalidates the association - Limit subdivision levels to [0, 12] (DirectX 12 maximum) - Define valid lookup values: signed elements >= -4, unsigned special-index bit patterns, and the record range including the no-accessor case - Remove byteStride support for data, triangles and micromapIndices (core glTF only allows it for vertex attributes); triangles byteLength is a multiple of 8 - Require normalized == false on micromapIndices - State usage entries per (level, format) pair as a data constraint - Add Interaction with Materials (non-normative), Interaction with Other Extensions, and Optional vs. Required sections - Make the reference function the normative ordering definition; keep the traversal description and figure as non-normative illustration - Rework the API mapping into a non-normative Graphics API Correspondence table; use docs.vulkan.org links - Use Khronos-style property tables and section order Schema - Move micromap objects to micromap.schema.json based on glTFChildOfRootProperty (adds name) - Add maximum 12 to usageLevels; tighten descriptions --- .../EXT_mesh_opacity_micromap/README.md | 358 ++++++++---------- ...glTF.EXT_mesh_opacity_micromap.schema.json | 8 +- ...tive.EXT_mesh_opacity_micromap.schema.json | 6 +- 3 files changed, 172 insertions(+), 200 deletions(-) diff --git a/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md index 75fe3ba2bc..ad9ad8be0f 100644 --- a/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md +++ b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md @@ -26,18 +26,20 @@ Written against the glTF 2.0 spec. ## Overview -This extension stores **pre-build source data** for opacity micromaps in a glTF asset. It does **not** store serialized micromap acceleration structures produced by a graphics API after build. An opacity micromap compactly encodes per-microtriangle opacity for each base triangle in a mesh. The on-disk representation defined here is layout-compatible with the build inputs of Vulkan `VK_KHR_opacity_micromap` and DirectX 12 `D3D12_RAYTRACING_TIER_1_2` opacity micromaps, so content can be baked once and uploaded when building opacity micromaps and bottom-level ray-tracing acceleration structures. +An *opacity micromap* stores a coarse, pre-computed opacity classification for the surface of a triangle. The triangle is uniformly subdivided into *microtriangles*, and each microtriangle stores one of up to four opacity states (transparent, opaque, unknown-transparent, unknown-opaque). Ray-tracing hardware uses these states to accept or reject ray-triangle intersections without invoking an any-hit shader, which greatly reduces the cost of tracing rays through alpha-tested geometry such as foliage. -The extension defines: +This extension stores opacity micromaps in a glTF asset in the form of **build inputs**: the packed microtriangle states, one record per micromap triangle, and a usage summary. The stored layout is binary-compatible with the build inputs of Vulkan [`VK_KHR_opacity_micromap`](https://docs.vulkan.org/refpages/latest/refpages/source/VK_KHR_opacity_micromap.html) and of DirectX 12 opacity micromaps ([`D3D12_RAYTRACING_TIER_1_2`](https://microsoft.github.io/DirectX-Specs/d3d/Raytracing.html#opacity-micromaps)), so that the data can be passed to the graphics API without conversion. This extension does not store the opaque, implementation-specific micromap objects that a graphics API produces from these inputs. -1. A root-level `micromaps` array describing micromap build inputs (`data`, `triangles`, and usage summary arrays), compatible with `VkAccelerationStructureGeometryMicromapDataKHR` and `D3D12_RAYTRACING_OPACITY_MICROMAP_ARRAY_DESC`. -2. A per-`mesh.primitive` extension describing how geometry triangles map into a micromap (`micromap`, `micromapIndices`, optional `micromapBaseTriangle`), compatible with `VkAccelerationStructureTrianglesOpacityMicromapKHR` and `D3D12_RAYTRACING_GEOMETRY_OMM_LINKAGE_DESC`. +The extension consists of two parts: -This extension normatively defines glTF JSON structure and stored buffer layouts. Runtime micromap build options, device limits, and ray-traversal behavior are out of scope and are defined by the target graphics API. +1. An array of `micromaps` in the glTF root extension object. Each micromap references the buffer views that hold its data. +2. A mesh primitive extension object that associates each triangle of the primitive with a micromap triangle, or with a uniform opacity state. -## Extending the glTF root +How opacity states affect ray traversal (for example, when an any-hit shader is invoked for unknown states) is defined by the graphics API and is outside the scope of this extension. -The glTF root object **MAY** define `extensions.EXT_mesh_opacity_micromap` with a `micromaps` array. +## Extending the glTF Root + +Micromaps are defined in the `micromaps` array of the `EXT_mesh_opacity_micromap` extension object in the glTF root. ```json { @@ -45,48 +47,46 @@ The glTF root object **MAY** define `extensions.EXT_mesh_opacity_micromap` with "EXT_mesh_opacity_micromap": { "micromaps": [ { - "data": 0, - "triangles": 1, - "usageCounts": [32, 12], - "usageLevels": [8, 5], - "usageFormats": [1, 1] + "data": 8, + "triangles": 9, + "usageCounts": [4, 42, 84, 26], + "usageLevels": [4, 5, 6, 7], + "usageFormats": [2, 2, 2, 2] } ] } - } + }, + "extensionsUsed": [ "EXT_mesh_opacity_micromap" ] } ``` -Each element of `micromaps` describes one opacity micromap's pre-build source inputs (`data`, `triangles`, and usage summary arrays). - -### Properties (`micromaps[]`) +Each micromap object has the following properties: -| Property | Type | Description | Required | -|----------|------|-------------|----------| -| **data** | `integer` | Index of the `bufferView` containing packed opacity micromap bits. | Yes | -| **triangles** | `integer` | Index of the `bufferView` containing [micromap triangle records](#micromap-triangle-records). | Yes | -| **usageCounts** | `integer[]` | Number of micromap triangles per `(subdivisionLevel, format)` bucket. Parallel to `usageLevels` and `usageFormats`. | Yes | -| **usageLevels** | `integer[]` | Subdivision level per bucket. Parallel to `usageCounts` and `usageFormats`. | Yes | -| **usageFormats** | `integer[]` | Opacity format per bucket (`1` or `2`). Parallel to `usageCounts` and `usageLevels`. | Yes | +| | Type | Description | Required | +|-|------|-------------|----------| +| **data** | `integer` | The index of the buffer view containing the packed microtriangle states. See [Microtriangle Data](#microtriangle-data). | :white_check_mark: Yes | +| **triangles** | `integer` | The index of the buffer view containing the micromap triangle records. See [Micromap Triangle Records](#micromap-triangle-records). | :white_check_mark: Yes | +| **usageCounts** | `integer[1-*]` | The number of micromap triangles for each usage entry. | :white_check_mark: Yes | +| **usageLevels** | `integer[1-*]` | The subdivision level of each usage entry. | :white_check_mark: Yes | +| **usageFormats** | `integer[1-*]` | The opacity format of each usage entry. | :white_check_mark: Yes | +| **name** | `string` | The user-defined name of this object. | No | -### Validity (root `micromaps[]`) +The `usageCounts`, `usageLevels`, and `usageFormats` arrays together form a list of *usage entries*: entry `i` states that `usageCounts[i]` micromap triangles of the micromap have subdivision level `usageLevels[i]` and format `usageFormats[i]`. The following requirements apply: -For each micromap object to be valid, the following **MUST** hold: +- `usageCounts`, `usageLevels`, and `usageFormats` **MUST** have the same length. +- Each element of `usageCounts` **MUST** be greater than or equal to `1`. +- Each element of `usageLevels` **MUST** be in the range `[0, 12]`. +- Each element of `usageFormats` **MUST** be `1` or `2`. +- For each distinct pair of subdivision level and format, the sum of `usageCounts[i]` over all entries `i` with that pair **MUST** equal the number of micromap triangle records with that subdivision level and format. -- `usageCounts`, `usageLevels`, and `usageFormats` **MUST** have equal length. -- Every `usageFormats[i]` **MUST** be `1` or `2`. -- Every `usageCounts[i]` **MUST** be greater than or equal to `1`. -- Every `usageLevels[i]` **MUST** be greater than or equal to `0`. -- Multiple entries with the same `(usageLevels[i], usageFormats[i])` **MAY** appear; validators **MUST** treat the effective count for each distinct pair as the sum of matching `usageCounts` values. -- The sum of all effective `usageCounts` values **MUST** equal the number of 8-byte records in the referenced `triangles` `bufferView` (see [Micromap triangle records](#micromap-triangle-records)). -- For each micromap triangle record, `dataOffset` and the record's `subdivisionLevel` and `format` **MUST** describe a region within the `data` `bufferView` that satisfies [bit packing rules](#bit-packing-in-data), and regions **MUST NOT** overlap. -- Referenced `bufferView` indices **MUST** be valid glTF `bufferView` indices. +> [!NOTE] +> As a consequence, the sum of all `usageCounts` elements equals the number of micromap triangle records. The usage entries let an implementation compute the memory required to build a micromap without reading the records. -The `triangles` `bufferView` **MUST** contain a tightly packed array of micromap triangle records. If `bufferView.byteStride` is omitted, it **MUST** be treated as `8`. If `byteStride` is present, it **MUST** be greater than or equal to `8` and a multiple of `4`. +The buffer views referenced by `data` and `triangles` **MUST NOT** define `byteStride`. -## Extending mesh primitives +## Extending Mesh Primitives -Each `mesh.primitive` **MAY** define `extensions.EXT_mesh_opacity_micromap`. +A mesh primitive references a micromap through the `EXT_mesh_opacity_micromap` extension object. ```json { @@ -94,14 +94,13 @@ Each `mesh.primitive` **MAY** define `extensions.EXT_mesh_opacity_micromap`. { "primitives": [ { - "attributes": { "POSITION": 0 }, - "indices": 1, - "mode": 4, + "attributes": { "POSITION": 0, "TEXCOORD_0": 1 }, + "indices": 2, + "material": 0, "extensions": { "EXT_mesh_opacity_micromap": { "micromap": 0, - "micromapIndices": 2, - "micromapBaseTriangle": 0 + "micromapIndices": 3 } } } @@ -111,227 +110,183 @@ Each `mesh.primitive` **MAY** define `extensions.EXT_mesh_opacity_micromap`. } ``` -### Properties - -| Property | Type | Description | Required | -|----------|------|-------------|----------| -| **micromap** | `integer` | Index into root `extensions.EXT_mesh_opacity_micromap.micromaps`. | Yes | -| **micromapIndices** | `integer` | Accessor index for per-geometry-triangle micromap lookup values. | No | -| **micromapBaseTriangle** | `integer` | Offset added to each non-special lookup value. Default: `0`. | No | - -See [Per-primitive micromap indices](#per-primitive-micromap-indices) for accessor and lookup rules. - -## Opacity micromap encoding +The extension object has the following properties: -This section defines the stored pre-build micromap representation referenced by root `micromaps` objects. It does not define post-build serialized micromap acceleration structure contents. +| | Type | Description | Required | +|-|------|-------------|----------| +| **micromap** | `integer` | The index of the micromap in the root `micromaps` array. | :white_check_mark: Yes | +| **micromapIndices** | `integer` | The index of the accessor containing one micromap index per triangle of the primitive. See [Micromap Indices](#micromap-indices). | No | +| **micromapBaseTriangle** | `integer` | The offset added to each micromap index that is not a special index. | No, default: `0` | -### Subdivision levels +A primitive using this extension **MUST** have a `mode` of `4` (TRIANGLES). -For subdivision level $L$, a base triangle is subdivided into $4^L$ microtriangles. +## Micromap Data -Each micromap triangle record specifies its own subdivision level and format. The `usageCounts`, `usageLevels`, and `usageFormats` arrays summarize how many micromap triangles exist for each `(subdivisionLevel, format)` pair in a micromap. +### Base Triangles and Barycentric Coordinates -### Microtriangle ordering +For each triangle `t` of a mesh primitive, the *base triangle* vertices `v0`, `v1`, and `v2` are the vertices of that triangle in the order defined by the glTF 2.0 specification for the TRIANGLES topology: when the primitive defines `indices`, they are the vertices referenced by index elements `3t`, `3t + 1`, and `3t + 2`; otherwise, they are vertices `3t`, `3t + 1`, and `3t + 2`. -Microtriangle indices within a micromap triangle **MUST** use the recursive space-filling curve ordering illustrated in [Appendix A](#appendix-a-microtriangle-indexing). Authoring tools **MUST** encode `data` bits in that linear index order. +A point `p` on the base triangle has barycentric coordinates $(u, v)$ such that -At runtime, graphics APIs map intersection barycentrics to a microtriangle index using the same ordering. - -### Formats and state values +$$ +p = (1 - u - v) \cdot v_0 + u \cdot v_1 + v \cdot v_2 +$$ -`usageFormats` values and micromap triangle record `format` fields **MUST** be one of: +All microtriangle positions and orderings in this extension are defined in terms of $(u, v)$. -| Value | Meaning | -|------:|---------| -| `1` | Two-state format: one bit per microtriangle (opaque or transparent). | -| `2` | Four-state format: two bits per microtriangle. | +> [!NOTE] +> This is the barycentric convention used by ray-tracing hit attributes in Vulkan and DirectX 12. Because the association between geometry and micromap data depends on the triangle order and on the order of the vertices within each triangle, any processing that reorders triangles, rotates the vertices of a triangle, or changes its winding invalidates the association. Such processing has to update `micromapIndices` (and the micromap data, when the vertex order changes) or remove this extension from the primitive. -For four-state data, each two-bit microtriangle value **MUST** be one of: +### Subdivision and Microtriangle Order -| Value | Meaning | -|------:|---------| -| `0` | Transparent | -| `1` | Opaque | -| `2` | Unknown transparent | -| `3` | Unknown opaque | +A micromap triangle with subdivision level $L$ divides its base triangle into $4^L$ congruent microtriangles by recursively connecting edge midpoints. The subdivision level **MUST** be in the range `[0, 12]`. -For two-state data, each one-bit microtriangle value **MUST** be interpreted as: +Microtriangles are numbered from `0` to $4^L - 1$ along a hierarchical space-filling curve. The index of the microtriangle containing barycentric coordinates $(u, v)$ is the value returned by the reference function in [Appendix A](#appendix-a-microtriangle-indexing). -| Value | Meaning | -|------:|---------| -| `0` | Transparent | -| `1` | Opaque | +### Opacity Formats and States -How these stored values affect ray traversal is defined by the target graphics API and is not specified by this extension. +Each micromap triangle uses one of the following formats: -### Bit packing in `data` +| Format | Bits per microtriangle | Allowed states | +|-------:|-----------------------:|----------------| +| `1` | 1 | `0` (transparent), `1` (opaque) | +| `2` | 2 | `0` (transparent), `1` (opaque), `2` (unknown-transparent), `3` (unknown-opaque) | -The `data` `bufferView` contains packed microtriangle state bits for all micromap triangles. +### Microtriangle Data -- Two-state (`format` `1`) data **MUST** use one bit per microtriangle. -- Four-state (`format` `2`) data **MUST** use two bits per microtriangle. -- Bits **MUST** be packed from least significant bit to most significant bit within each byte. -- For a micromap triangle with subdivision level $L$ and format $F$, the number of bytes required at `dataOffset` **MUST** be: +The buffer view referenced by `data` contains the packed microtriangle states of all micromap triangles of the micromap. The states of a micromap triangle with subdivision level $L$ and format $F$ occupy $$ \left\lceil \frac{4^L \cdot b}{8} \right\rceil $$ -where $b$ is `1` for format `1` and `2` for format `2`. +bytes, starting at the byte offset `dataOffset` of its [record](#micromap-triangle-records), where $b$ is `1` for format `1` and `2` for format `2`. -Unused bits in the final byte of a micromap triangle's region **MUST** be ignored. +Within this region, the state of microtriangle `i` occupies bits `i * b` to `i * b + b - 1`, where bit `0` is the least significant bit of the first byte. Bits in the final byte beyond the last microtriangle are unused, and their values **MUST** be ignored. -### Micromap triangle records +The region of each micromap triangle **MUST** lie entirely within the `data` buffer view, and regions of different micromap triangles **MUST NOT** overlap. -Each element of the `triangles` `bufferView` **MUST** be an 8-byte record with the following layout: +### Micromap Triangle Records -| Offset (bytes) | Type | Field | -|---------------:|------|-------| -| `0` | `uint32` | `dataOffset` | -| `4` | `uint16` | `subdivisionLevel` | -| `6` | `uint16` | `format` | +The buffer view referenced by `triangles` contains a tightly packed array of 8-byte records, one per micromap triangle. Its `byteLength` **MUST** be a multiple of `8`. Micromap triangles are numbered by their position in this array, starting at `0`. -- `dataOffset` **MUST** be a byte offset relative to the start of the `data` `bufferView`. -- `subdivisionLevel` **MUST** be greater than or equal to `0`. -- `format` **MUST** be `1` or `2`. +| Byte offset | Type | Name | Description | +|------------:|------|------|-------------| +| `0` | `uint32` | `dataOffset` | Byte offset of the micromap triangle's states, relative to the start of the `data` buffer view. | +| `4` | `uint16` | `subdivisionLevel` | Subdivision level. **MUST** be in the range `[0, 12]`. | +| `6` | `uint16` | `format` | Opacity format. **MUST** be `1` or `2`. | -The number of records in `triangles` **MUST** equal the sum of all `usageCounts` values for that micromap. +## Micromap Indices -This record layout is compatible with `VkMicromapTriangleKHR` in Vulkan `VK_KHR_opacity_micromap` and `D3D12_RAYTRACING_OPACITY_MICROMAP_DESC` in DirectX 12 when consumed as micromap build input. +### Lookup -## Per-primitive micromap indices +Each triangle `t` of a primitive is associated with a lookup value `m`: -Primitives using this extension **MUST** have `mode` `TRIANGLES` (`4`). +- When `micromapIndices` is defined, `m` is element `t` of the accessor. +- When `micromapIndices` is undefined, `m` is equal to `t`. -The extension `micromapIndices` accessor is separate from the geometry `indices` accessor. It stores one lookup value per geometry triangle. -If provided, the accessor **MUST** have a `count` equal to the primitive's triangle count (as defined by the glTF 2.0 specification for `TRIANGLES` mode: the `indices` accessor `count` divided by `3`, or the `POSITION` accessor `count` divided by `3` when `indices` is undefined) and element `t` of the `micromapIndices` accessor **MUST** correspond to geometry triangle `t`. +If `m` is a [special index](#special-indices), triangle `t` has the uniform opacity state given by that index and does not reference a micromap triangle. Otherwise, triangle `t` uses micromap triangle `m + micromapBaseTriangle` of the referenced micromap. This value **MUST** be less than the number of micromap triangle records of the micromap. -### Lookup resolution +> [!NOTE] +> When `micromapIndices` is undefined, this requires the number of micromap triangle records to be at least the primitive's triangle count plus `micromapBaseTriangle`. -For geometry triangle `t`, let `v` be the accessor value at element `t`. If no accessor is provided `v` equals `t`. +Several triangles, including triangles of different primitives, **MAY** use the same micromap triangle. -- If `v` is a [special index](#special-indices), no micromap triangle fetch is performed. -- Otherwise, the micromap triangle index **MUST** be computed as `v + micromapBaseTriangle` and **MUST** reference a record in the micromap's `triangles` array. +### Special Indices -This lookup resolution is compatible with `VkAccelerationStructureTrianglesOpacityMicromapKHR` in Vulkan `VK_KHR_opacity_micromap` and `D3D12_RAYTRACING_GEOMETRY_OMM_LINKAGE_DESC` in DirectX 12. +| Special index | Uniform state | +|--------------:|---------------| +| `-1` | Transparent | +| `-2` | Opaque | +| `-3` | Unknown-transparent | +| `-4` | Unknown-opaque | -### Special indices +### Accessor Requirements -The following signed lookup values **MUST** be supported: +When defined, the `micromapIndices` accessor **MUST** meet the following requirements: -| Signed value | Meaning | -|-------------:|---------| -| `-1` | Fully transparent | -| `-2` | Fully opaque | -| `-3` | Fully unknown transparent | -| `-4` | Fully unknown opaque | +- Its `type` **MUST** be `SCALAR`. +- Its `componentType` **MUST** be `5121` (UNSIGNED_BYTE), `5123` (UNSIGNED_SHORT), `5125` (UNSIGNED_INT), `5122` (SHORT), or `5124` (INT). +- Its `normalized` property **MUST NOT** be set to `true`. +- Its `count` **MUST** be equal to the number of triangles of the primitive. -### Encoding in accessors +As for all accessors that do not contain vertex attributes, the elements of this accessor are tightly packed and its buffer view **MUST NOT** define `byteStride`. -If `micromapIndices` is provided, the accessor **MUST** have `type` `SCALAR` and `componentType` one of `5121` (`UNSIGNED_BYTE`), `5123` (`UNSIGNED_SHORT`), `5125` (`UNSIGNED_INT`), `5122` (`SHORT`), or `5124` (`INT`). +For signed component types, special indices are stored as their signed values, and every element **MUST** be greater than or equal to `-4`. -When `componentType` is `5121` (`UNSIGNED_BYTE`), `5123` (`UNSIGNED_SHORT`), or `5125` (`UNSIGNED_INT`): +For unsigned component types, special indices are stored as the two's-complement bit patterns of their signed values (for example, `-1` is stored as `255`, `65535`, or `4294967295` for UNSIGNED_BYTE, UNSIGNED_SHORT, or UNSIGNED_INT, respectively). Elements that are not special indices are therefore less than $2^n - 4$, where $n$ is the bit width of the component type. -- Non-special micromap triangle indices **MUST** be non-negative. -- Special indices **MUST** be stored as the two's-complement bit pattern of the signed value in the accessor's component type (for example, `4294967295` represents `-1` with `UNSIGNED_INT`). +> [!NOTE] +> Signed and unsigned component types of the same width have identical bit patterns for all valid values, so the data can be passed to a graphics API as an unsigned index buffer of that width. -When `componentType` is `5122` (`SHORT`) or `5124` (`INT`): +## Interaction with Materials -- Lookup values **MAY** be stored directly as signed integers, including `-1` through `-4`. +*This section is non-normative.* -## Schema +This extension does not define a relationship between the micromap and the primitive's material. Opacity micromaps are typically created by baking the alpha coverage of the material (its base color alpha, `alphaMode`, and `alphaCutoff`) using format `2`, so that microtriangles straddling an alpha edge are marked as unknown and resolved by the renderer's alpha test, and the result matches rendering without the extension. -- [glTF.EXT_mesh_opacity_micromap.schema.json](schema/glTF.EXT_mesh_opacity_micromap.schema.json) -- [mesh.primitive.EXT_mesh_opacity_micromap.schema.json](schema/mesh.primitive.EXT_mesh_opacity_micromap.schema.json) +Renderers that do not support this extension, including rasterizers, render the primitive using its material alone. A micromap that does not match the material's coverage therefore produces different results across renderers. -## Known Implementations +Data that changes the material's coverage after baking, such as `KHR_materials_variants`, `KHR_texture_transform`, or animations of texture coordinates or `alphaCutoff` through `KHR_animation_pointer`, is not reflected in the micromap. -- [nvpro-samples/vk_gltf_renderer](https://github.com/nvpro-samples/vk_gltf_renderer): loads the extension and builds opacity micromaps for ray tracing with Vulkan `VK_KHR_opacity_micromap`. -- [nvpro-samples/gltf_omm_baker](https://github.com/nvpro-samples/gltf_omm_baker): bakes opacity micromaps from material alpha coverage and writes them using this extension. +## Interaction with Other Extensions -## Reference - -### Normative external references - -When mapping stored glTF data to graphics API micromap build inputs, implementations **MUST** preserve compatibility with the field correspondences below. - -| glTF | Vulkan (`VK_KHR_opacity_micromap`) | DirectX 12 | -|------|-------------------------------------|------------| -| | `VkMicromapUsageKHR` | `D3D12_RAYTRACING_OPACITY_MICROMAP_HISTOGRAM_ENTRY` | -| `micromaps[].usageCounts[i]` | `count` | `Count` | -| `micromaps[].usageLevels[i]` | `subdivisionLevel` | `SubdivisionLevel` | -| `micromaps[].usageFormats[i]` | `format` | `Format` | -| | | | -| | `VkMicromapTriangleKHR` | `D3D12_RAYTRACING_OPACITY_MICROMAP_DESC` | -| `micromaps[].triangles[t].dataOffset` | `dataOffset` | `ByteOffset` | -| `micromaps[].triangles[t].subdivisionLevel` | `subdivisionLevel` | `SubdivisionLevel` | -| `micromaps[].triangles[t].format` | `format` | `Format` | -| | | | -| global micromap | `VkAccelerationStructureGeometryMicromapDataKHR` | `D3D12_RAYTRACING_OPACITY_MICROMAP_ARRAY_DESC` | -| `micromaps[].data` | `data` | `InputBuffer` | -| `micromaps[].triangles` | `triangleArray` | `PerOmmDescs` | -| `micromaps[].triangles` `bufferView.byteStride` | `triangleArrayStride` | `PerOmmDescs` stride | -| `usageCounts` length | `usageCountsCount` | `NumOmmHistogramEntries` | -| `usageCounts` / `usageLevels` / `usageFormats` | `pUsageCounts` | `pOmmHistogram` | -| | | | -| mesh primitive `EXT_mesh_opacity_micromap` | `VkAccelerationStructureTrianglesOpacityMicromapKHR` | `D3D12_RAYTRACING_GEOMETRY_OMM_LINKAGE_DESC` | -| `micromap` | `micromap` | `OpacityMicromapArray` | -| `micromapIndices` (accessor buffer) | `indexBuffer` | `OpacityMicromapIndexBuffer` | -| `micromapIndices` (`componentType`) | `indexType` | `OpacityMicromapIndexFormat` | -| `micromapIndices` (`byteStride`) | `indexStride` | `OpacityMicromapIndexBuffer` stride | -| `micromapBaseTriangle` | `baseTriangle` | `OpacityMicromapBaseLocation` | +When a primitive uses a mesh compression extension, such as `KHR_draco_mesh_compression` or `EXT_meshopt_compression`, triangles and their vertex order are defined by the decompressed data. Encoders that reorder triangles or rotate triangle vertices have to update the micromap association accordingly (see [Base Triangles and Barycentric Coordinates](#base-triangles-and-barycentric-coordinates)). -In the mesh primitive rows, the Vulkan `micromap` and DirectX 12 `OpacityMicromapArray` fields refer to the micromap object built from the referenced `micromaps` element, not to the stored glTF data itself. +Because micromaps are defined in barycentric space, they remain associated with the same surface under skinning, morph targets, and instancing with `EXT_mesh_gpu_instancing`. -References: +## Optional vs. Required -- [Vulkan `VK_KHR_opacity_micromap`](https://registry.khronos.org/vulkan/specs/latest/man/html/VK_KHR_opacity_micromap.html) -- [Microsoft DirectX Raytracing — opacity micromaps](https://microsoft.github.io/DirectX-Specs/d3d/Raytracing.html#opacity-micromaps) +This extension **SHOULD NOT** be listed in `extensionsRequired`. The primitive's geometry and material remain a complete description of the asset when the extension is ignored. -### Informative references +## Graphics API Correspondence *This section is non-normative.* -- [Vulkan proposals index](https://docs.vulkan.org/proposals/) — design rationale for opacity micromaps. -- [SPIR-V `SPV_KHR_opacity_micromap`](https://github.khronos.org/SPIRV-Registry/extensions/KHR/SPV_KHR_opacity_micromap.html) -- [NVIDIA Opacity Micromap SDK](https://github.com/NVIDIA-RTX/OMM) - -## Appendix A: Microtriangle indexing - -Opacity micromap bits in the `data` buffer are indexed by a hierarchical space-filling curve over recursively subdivided microtriangles. - -### Recursive subdivision - -A base triangle with vertices `v0`, `v1`, and `v2` is split into four congruent microtriangles by connecting edge midpoints. This split is applied recursively: subdivision level `1` yields `4` microtriangles, and each additional level multiplies the count by `4` (level `L` yields `4^L` microtriangles). +The following table lists the graphics API inputs that correspond to the data defined by this extension. + +| glTF | Vulkan `VK_KHR_opacity_micromap` | DirectX 12 | +|------|----------------------------------|------------| +| Micromap | `VkAccelerationStructureGeometryMicromapDataKHR` | `D3D12_RAYTRACING_OPACITY_MICROMAP_ARRAY_DESC` | +| `data` | `data` | `InputBuffer` | +| `triangles` | `triangleArray` (`triangleArrayStride` = `8`) | `PerOmmDescs` (stride `8`) | +| Usage entries | `pUsageCounts` (`VkMicromapUsageKHR`) | `pOmmHistogram` (`D3D12_RAYTRACING_OPACITY_MICROMAP_HISTOGRAM_ENTRY`) | +| `usageCounts[i]`, `usageLevels[i]`, `usageFormats[i]` | `count`, `subdivisionLevel`, `format` | `Count`, `SubdivisionLevel`, `Format` | +| Micromap triangle record | `VkMicromapTriangleKHR` | `D3D12_RAYTRACING_OPACITY_MICROMAP_DESC` | +| Mesh primitive extension | `VkAccelerationStructureTrianglesOpacityMicromapKHR` | `D3D12_RAYTRACING_GEOMETRY_OMM_LINKAGE_DESC` | +| `micromap` | `micromap` | `OpacityMicromapArray` | +| `micromapIndices` | `indexBuffer`, `indexType`, `indexStride` | `OpacityMicromapIndexBuffer`, `OpacityMicromapIndexFormat` | +| `micromapIndices` undefined | `indexType` = `VK_INDEX_TYPE_NONE_KHR` | `OpacityMicromapIndexFormat` = `DXGI_FORMAT_UNKNOWN` | +| `micromapBaseTriangle` | `baseTriangle` | `OpacityMicromapBaseLocation` | +| Format `1`, `2` | `VK_OPACITY_MICROMAP_FORMAT_2_STATE_KHR`, `VK_OPACITY_MICROMAP_FORMAT_4_STATE_KHR` | `D3D12_RAYTRACING_OPACITY_MICROMAP_FORMAT_OC1_2_STATE`, `D3D12_RAYTRACING_OPACITY_MICROMAP_FORMAT_OC1_4_STATE` | +| Special indices `-1` to `-4` | `VkOpacityMicromapSpecialIndexKHR` | `D3D12_RAYTRACING_OPACITY_MICROMAP_SPECIAL_INDEX` | -The figure below shows level `1` (left) and level `2` (right). Microtriangle labels `0` … `3` at level `1` and `0` … `15` at level `2` are the linear indices used when packing opacity bits in `data` (least significant bit first within each byte). +The `micromap` and `OpacityMicromapArray` fields refer to the micromap object that the application builds from the referenced micromap, not to the stored data. Graphics APIs may support lower maximum subdivision levels than this extension allows; Vulkan reports them in `VkPhysicalDeviceOpacityMicromapPropertiesKHR`. -Recursive micromap subdivision and microtriangle index ordering at levels 1 and 2 +## Schema -In the diagram: +- [glTF.EXT_mesh_opacity_micromap.schema.json](schema/glTF.EXT_mesh_opacity_micromap.schema.json) +- [mesh.primitive.EXT_mesh_opacity_micromap.schema.json](schema/mesh.primitive.EXT_mesh_opacity_micromap.schema.json) -- Each inner triangle is one microtriangle at the shown subdivision level. -- The numbered labels are the linear microtriangle indices at that level. -- The dot in each level-`1` microtriangle marks the entry point of the curve into that sub-triangle. -- Blue arrows show the default vertex winding used when traversing a sub-triangle's children. -- Red arrows show sub-triangles whose child winding is flipped at the next subdivision level. +## Known Implementations -### Curve traversal order +- [nvpro-samples/vk_gltf_renderer](https://github.com/nvpro-samples/vk_gltf_renderer): loads the extension and builds opacity micromaps for Vulkan ray tracing. +- [nvpro-samples/gltf_omm_baker](https://github.com/nvpro-samples/gltf_omm_baker): bakes opacity micromaps from material alpha coverage and writes them using this extension. -Within a microtriangle, child sub-triangles are visited in this order: +## Resources -1. The sub-triangle nearest vertex `v0`. -2. The middle sub-triangle, using flipped child ordering. -3. The sub-triangle nearest vertex `v1`. -4. The sub-triangle nearest vertex `v2`, using flipped child ordering. +*This section is non-normative.* -This traversal is applied recursively. The resulting linear index is the position of a microtriangle's opacity value in the packed `data` bitstream for that micromap triangle record. +- [Vulkan `VK_KHR_opacity_micromap`](https://docs.vulkan.org/refpages/latest/refpages/source/VK_KHR_opacity_micromap.html) +- [DirectX Raytracing: Opacity Micromaps](https://microsoft.github.io/DirectX-Specs/d3d/Raytracing.html#opacity-micromaps) +- [SPIR-V `SPV_KHR_opacity_micromap`](https://github.khronos.org/SPIRV-Registry/extensions/KHR/SPV_KHR_opacity_micromap.html) +- [NVIDIA Opacity Micromap SDK](https://github.com/NVIDIA-RTX/OMM) -At intersection time, graphics APIs quantize barycentric coordinates $(u, v)$ and map them to the same linear index. +## Appendix A: Microtriangle Indexing -### Reference function +### Reference Function -The following function is a reference implementation that maps quantized barycentric coordinates $(u, v)$ inside a base triangle to a microtriangle index for subdivision level `level`, using the recursive splitting order described above. It is reproduced from the Vulkan `VK_KHR_opacity_micromap` specification reference code. +The following function maps barycentric coordinates $(u, v)$ on a base triangle to the index of the microtriangle containing them, for subdivision level `level`. It is reproduced from the reference code of the Vulkan `VK_KHR_opacity_micromap` specification and is normative for this extension. ```cpp uint32_t BarycentricsToSpaceFillingCurveIndex(float u, float v, uint32_t level) @@ -388,6 +343,21 @@ uint32_t BarycentricsToSpaceFillingCurveIndex(float u, float v, uint32_t level) } ``` +### Illustration + +*This section is non-normative.* + +The resulting order follows a recursive space-filling curve. Each triangle is split into four sub-triangles, which are visited in the following order: + +1. The sub-triangle nearest `v0`. +2. The middle sub-triangle, with its child order flipped. +3. The sub-triangle nearest `v1`. +4. The sub-triangle nearest `v2`, with its child order flipped. + +The figure below shows the resulting microtriangle indices for subdivision levels 1 (left) and 2 (right), with `v0` at the bottom left, `v1` at the bottom right, and `v2` at the top. Blue arrows show the default child order and red arrows show flipped child order. + +Microtriangle indices at subdivision levels 1 and 2 + ## Appendix: Full Khronos Copyright Statement Copyright 2018-2026 The Khronos Group Inc. diff --git a/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/schema/glTF.EXT_mesh_opacity_micromap.schema.json b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/schema/glTF.EXT_mesh_opacity_micromap.schema.json index 1d0a1a92f4..b23d0a2bf8 100644 --- a/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/schema/glTF.EXT_mesh_opacity_micromap.schema.json +++ b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/schema/glTF.EXT_mesh_opacity_micromap.schema.json @@ -11,7 +11,7 @@ "gltf_detailedDescription": "Each element describes one opacity micromap's packed data, triangle records, and usage summary arrays.", "items": { "type": "object", - "allOf": [ { "$ref": "glTFProperty.schema.json" } ], + "allOf": [ { "$ref": "glTFChildOfRootProperty.schema.json" } ], "properties": { "data": { "allOf": [ { "$ref": "glTFid.schema.json" } ], @@ -21,7 +21,7 @@ "triangles": { "allOf": [ { "$ref": "glTFid.schema.json" } ], "description": "The index of the bufferView containing micromap triangle records.", - "gltf_detailedDescription": "An array of 8-byte records: uint32 dataOffset, uint16 subdivisionLevel, uint16 format." + "gltf_detailedDescription": "A tightly packed array of 8-byte records: uint32 dataOffset, uint16 subdivisionLevel, uint16 format. The buffer view must not define byteStride." }, "usageCounts": { "type": "array", @@ -39,7 +39,8 @@ "gltf_detailedDescription": "Parallel to usageCounts and usageFormats.", "items": { "type": "integer", - "minimum": 0 + "minimum": 0, + "maximum": 12 }, "minItems": 1 }, @@ -53,6 +54,7 @@ }, "minItems": 1 }, + "name": { }, "extensions": { }, "extras": { } }, diff --git a/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/schema/mesh.primitive.EXT_mesh_opacity_micromap.schema.json b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/schema/mesh.primitive.EXT_mesh_opacity_micromap.schema.json index 81e6b41a5b..bef8bcdce7 100644 --- a/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/schema/mesh.primitive.EXT_mesh_opacity_micromap.schema.json +++ b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/schema/mesh.primitive.EXT_mesh_opacity_micromap.schema.json @@ -7,13 +7,13 @@ "properties": { "micromap": { "allOf": [ { "$ref": "glTFid.schema.json" } ], - "description": "Index into the root-level EXT_mesh_opacity_micromap.micromaps array.", + "description": "The index of the micromap in the root `micromaps` array.", "gltf_detailedDescription": "References the micromap build data used when attaching opacity micromaps to this primitive's triangles." }, "micromapIndices": { "allOf": [ { "$ref": "glTFid.schema.json" } ], - "description": "Accessor index for per-geometry-triangle micromap lookup values.", - "gltf_detailedDescription": "Optional accessor with one scalar per geometry triangle. If omitted, geometry triangle t maps to micromap triangle t plus micromapBaseTriangle. Values are either special indices (-1 through -4) or non-negative micromap triangle indices. When provided, the accessor count must equal the primitive triangle count." + "description": "The index of the accessor containing one micromap index per triangle.", + "gltf_detailedDescription": "The index of a `SCALAR` accessor with one element per triangle of the primitive. Each element is either a micromap triangle index or a special index (-1 to -4, stored as two's-complement bit patterns for unsigned component types). When undefined, triangle `t` uses micromap triangle `t + micromapBaseTriangle`." }, "micromapBaseTriangle": { "type": "integer", From 786821d715bcdd1dc729ef02a93bab765904d9ee Mon Sep 17 00:00:00 2001 From: Nia Bickford Date: Sat, 24 Oct 2026 05:21:00 +0100 Subject: [PATCH 05/12] Edit pass: overview and space-filling curve - Overview: - Made it clearer that micromaps contain micromap triangles - Reduced some repetition between paragraphs 2 and 3, and cleaned up some clunkiness when we mention how this works well with graphics APIs - Don't claim binary-compatibility, because JSON will treat things like floats - Hardware can use OMMs; it's not required to (although it should, it helps quite a bit) - Reference function now uses pseudocode that is in no particular language; additional code around clamping to the triangle edges has been removed; added more comments, although the magic of this function is still rather unexplained here. - More links - More LaTeX - Moved the graphical description of the space-filling curve up to Subdivision and Micromap Order - Changed 'typically created' to 'often created'; we do see format 1 and it's not that atypical. - Some 'have to' -> 'must' replacements for concision --- .../EXT_mesh_opacity_micromap/README.md | 156 ++++++++---------- 1 file changed, 73 insertions(+), 83 deletions(-) diff --git a/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md index ad9ad8be0f..5f4f6c8268 100644 --- a/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md +++ b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md @@ -26,14 +26,17 @@ Written against the glTF 2.0 spec. ## Overview -An *opacity micromap* stores a coarse, pre-computed opacity classification for the surface of a triangle. The triangle is uniformly subdivided into *microtriangles*, and each microtriangle stores one of up to four opacity states (transparent, opaque, unknown-transparent, unknown-opaque). Ray-tracing hardware uses these states to accept or reject ray-triangle intersections without invoking an any-hit shader, which greatly reduces the cost of tracing rays through alpha-tested geometry such as foliage. +An *opacity micromap* stores a coarse, pre-computed opacity classification for the surface of a triangle. The triangle is uniformly subdivided into *microtriangles*, and each microtriangle stores one of up to four opacity states (transparent, opaque, unknown-transparent, unknown-opaque). Ray-tracing hardware can use these states to accept or reject ray-triangle intersections without invoking an any-hit shader, which greatly reduces the cost of tracing rays through alpha-tested geometry such as foliage. -This extension stores opacity micromaps in a glTF asset in the form of **build inputs**: the packed microtriangle states, one record per micromap triangle, and a usage summary. The stored layout is binary-compatible with the build inputs of Vulkan [`VK_KHR_opacity_micromap`](https://docs.vulkan.org/refpages/latest/refpages/source/VK_KHR_opacity_micromap.html) and of DirectX 12 opacity micromaps ([`D3D12_RAYTRACING_TIER_1_2`](https://microsoft.github.io/DirectX-Specs/d3d/Raytracing.html#opacity-micromaps)), so that the data can be passed to the graphics API without conversion. This extension does not store the opaque, implementation-specific micromap objects that a graphics API produces from these inputs. +This extension stores opacity micromaps in a glTF asset in the form of **build inputs**: for efficiency, its layout closely matches the inputs of Vulkan's [`VK_KHR_opacity_micromap`](https://docs.vulkan.org/refpages/latest/refpages/source/VK_KHR_opacity_micromap.html) and DirectX 12's [opacity micromaps](https://microsoft.github.io/DirectX-Specs/d3d/Raytracing.html#opacity-micromaps) build APIs, but it does not store the opaque, implementation-specific micromap objects that a graphics API produces from these inputs. -The extension consists of two parts: +More specifically, this extension consists of two parts within the glTF object structure: -1. An array of `micromaps` in the glTF root extension object. Each micromap references the buffer views that hold its data. -2. A mesh primitive extension object that associates each triangle of the primitive with a micromap triangle, or with a uniform opacity state. +1. [An array of `micromaps`](#extending-the-gltf-root) in the glTF root extension object. Each element of this array contains data for a number of *micromap triangles*: + * the index of a buffer view of [packed microtriangle states](#microtriangle-data), + * the index of a buffer view of [triangle records](#micromap-triangle-records), + * and counts of the used [subdivision levels](#subdivision-and-microtriangle-order) and [formats](#opacity-formats-and-states). +2. [A mesh primitive extension object](#extending-mesh-primitives) that associates each triangle of the primitive with a micromap triangle, or with a uniform opacity state. How opacity states affect ray traversal (for example, when an any-hit shader is invoked for unknown states) is defined by the graphics API and is outside the scope of this extension. @@ -71,7 +74,7 @@ Each micromap object has the following properties: | **usageFormats** | `integer[1-*]` | The opacity format of each usage entry. | :white_check_mark: Yes | | **name** | `string` | The user-defined name of this object. | No | -The `usageCounts`, `usageLevels`, and `usageFormats` arrays together form a list of *usage entries*: entry `i` states that `usageCounts[i]` micromap triangles of the micromap have subdivision level `usageLevels[i]` and format `usageFormats[i]`. The following requirements apply: +The `usageCounts`, `usageLevels`, and `usageFormats` arrays together form a list of *usage entries*: entry `i` states that `usageCounts[i]` micromap triangles of the micromap have [subdivision level](#subdivision-and-microtriangle-order) `usageLevels[i]` and [format](#opacity-formats-and-states) `usageFormats[i]`. The following requirements apply: - `usageCounts`, `usageLevels`, and `usageFormats` **MUST** have the same length. - Each element of `usageCounts` **MUST** be greater than or equal to `1`. @@ -124,7 +127,7 @@ A primitive using this extension **MUST** have a `mode` of `4` (TRIANGLES). ### Base Triangles and Barycentric Coordinates -For each triangle `t` of a mesh primitive, the *base triangle* vertices `v0`, `v1`, and `v2` are the vertices of that triangle in the order defined by the glTF 2.0 specification for the TRIANGLES topology: when the primitive defines `indices`, they are the vertices referenced by index elements `3t`, `3t + 1`, and `3t + 2`; otherwise, they are vertices `3t`, `3t + 1`, and `3t + 2`. +For each triangle $t$ of a mesh primitive, the *base triangle* vertices $v_0$, $v_1$, and $v_2$ are the vertices of that triangle in the order defined by the glTF 2.0 specification for the TRIANGLES topology: when the primitive defines `indices`, they are the vertices referenced by index elements $3t$, $3t+1$, and $3t+2$; otherwise, they are vertices $3t$, $3t+1$, and $3t+2$. A point `p` on the base triangle has barycentric coordinates $(u, v)$ such that @@ -135,13 +138,25 @@ $$ All microtriangle positions and orderings in this extension are defined in terms of $(u, v)$. > [!NOTE] -> This is the barycentric convention used by ray-tracing hit attributes in Vulkan and DirectX 12. Because the association between geometry and micromap data depends on the triangle order and on the order of the vertices within each triangle, any processing that reorders triangles, rotates the vertices of a triangle, or changes its winding invalidates the association. Such processing has to update `micromapIndices` (and the micromap data, when the vertex order changes) or remove this extension from the primitive. +> This is the barycentric convention used by ray-tracing hit attributes in Vulkan and DirectX 12. Because the association between geometry and micromap data depends on the triangle order and on the order of the vertices within each triangle, any processing that reorders triangles, rotates the vertices of a triangle, or changes its winding invalidates the association. Such processing must update `micromapIndices` (and the micromap data, when the vertex order changes) or remove this extension from the primitive. ### Subdivision and Microtriangle Order A micromap triangle with subdivision level $L$ divides its base triangle into $4^L$ congruent microtriangles by recursively connecting edge midpoints. The subdivision level **MUST** be in the range `[0, 12]`. -Microtriangles are numbered from `0` to $4^L - 1$ along a hierarchical space-filling curve. The index of the microtriangle containing barycentric coordinates $(u, v)$ is the value returned by the reference function in [Appendix A](#appendix-a-microtriangle-indexing). +Microtriangles are numbered from $0$ to $4^L - 1$ along a hierarchical space-filling curve. + +Microtriangle indices at subdivision levels 1 and 2 + +The diagram above shows the microtriangle indices for subdivision levels 1 and 2, as well as the indexing used to recursively construct the space-filling curve. + +To construct the microtriangle indices for level $L+1$ from the microtriangle indices for level $L$, replace each microtriangle in level 1 with a copy of the subdivided triangle for level $L$, concatenated in the following order: +1. The sub-triangle nearest `v0`. +2. The middle sub-triangle, with its child order flipped. +3. The sub-triangle nearest `v1`. +4. The sub-triangle nearest `v2`, with its child order flipped. + +The index of the microtriangle containing barycentric coordinates $(u, v)$ can also be computed by the reference function in [Appendix A](#appendix-a-microtriangle-indexing). ### Opacity Formats and States @@ -223,7 +238,7 @@ For unsigned component types, special indices are stored as the two's-complement *This section is non-normative.* -This extension does not define a relationship between the micromap and the primitive's material. Opacity micromaps are typically created by baking the alpha coverage of the material (its base color alpha, `alphaMode`, and `alphaCutoff`) using format `2`, so that microtriangles straddling an alpha edge are marked as unknown and resolved by the renderer's alpha test, and the result matches rendering without the extension. +This extension does not define a relationship between the micromap and the primitive's material. Opacity micromaps are often created by baking the alpha coverage of the material (its base color alpha, `alphaMode`, and `alphaCutoff`) using format `2`, so that microtriangles straddling an alpha edge are marked as unknown and resolved by the renderer's alpha test, and the result matches rendering without the extension. Renderers that do not support this extension, including rasterizers, render the primitive using its material alone. A micromap that does not match the material's coverage therefore produces different results across renderers. @@ -231,7 +246,7 @@ Data that changes the material's coverage after baking, such as `KHR_materials_v ## Interaction with Other Extensions -When a primitive uses a mesh compression extension, such as `KHR_draco_mesh_compression` or `EXT_meshopt_compression`, triangles and their vertex order are defined by the decompressed data. Encoders that reorder triangles or rotate triangle vertices have to update the micromap association accordingly (see [Base Triangles and Barycentric Coordinates](#base-triangles-and-barycentric-coordinates)). +When a primitive uses a mesh compression extension, such as `KHR_draco_mesh_compression` or `EXT_meshopt_compression`, triangles and their vertex order are defined by the decompressed data. Encoders that reorder triangles or rotate triangle vertices must update the micromap association accordingly (see [Base Triangles and Barycentric Coordinates](#base-triangles-and-barycentric-coordinates)). Because micromaps are defined in barycentric space, they remain associated with the same surface under skinning, morph targets, and instancing with `EXT_mesh_gpu_instancing`. @@ -284,81 +299,56 @@ The `micromap` and `OpacityMicromapArray` fields refer to the micromap object th ## Appendix A: Microtriangle Indexing -### Reference Function - -The following function maps barycentric coordinates $(u, v)$ on a base triangle to the index of the microtriangle containing them, for subdivision level `level`. It is reproduced from the reference code of the Vulkan `VK_KHR_opacity_micromap` specification and is normative for this extension. - -```cpp -uint32_t BarycentricsToSpaceFillingCurveIndex(float u, float v, uint32_t level) -{ - u = clamp(u, 0.0f, 1.0f); - v = clamp(v, 0.0f, 1.0f); - - uint32_t iu, iv, iw; - - // Quantize barycentric coordinates - float fu = u * (1u << level); - float fv = v * (1u << level); - - iu = (uint32_t)fu; - iv = (uint32_t)fv; - - float uf = fu - float(iu); - float vf = fv - float(iv); - - if (iu >= (1u << level)) iu = (1u << level) - 1u; - if (iv >= (1u << level)) iv = (1u << level) - 1u; - - uint32_t iuv = iu + iv; - - if (iuv >= (1u << level)) - iu -= iuv - (1u << level) + 1u; - - iw = ~(iu + iv); - - if (uf + vf >= 1.0f && iuv < (1u << level) - 1u) --iw; - - uint32_t b0 = ~(iu ^ iw); - b0 &= ((1u << level) - 1u); - uint32_t t = (iu ^ iv) & b0; - - uint32_t f = t; - f ^= f >> 1u; - f ^= f >> 2u; - f ^= f >> 4u; - f ^= f >> 8u; - uint32_t b1 = ((f ^ iu) & ~b0) | t; - - // Interleave bits - b0 = (b0 | (b0 << 8u)) & 0x00ff00ffu; - b0 = (b0 | (b0 << 4u)) & 0x0f0f0f0fu; - b0 = (b0 | (b0 << 2u)) & 0x33333333u; - b0 = (b0 | (b0 << 1u)) & 0x55555555u; - b1 = (b1 | (b1 << 8u)) & 0x00ff00ffu; - b1 = (b1 | (b1 << 4u)) & 0x0f0f0f0fu; - b1 = (b1 | (b1 << 2u)) & 0x33333333u; - b1 = (b1 | (b1 << 1u)) & 0x55555555u; +The following pseudocode function takes a barycentric coordinate $(u, v), u+v<1$ on a base triangle with subdivision level $L \leq 12$, and returns the index of the microtriangle containing that coordinate. `f32` denotes a 32-bit floating-point number, `u32` denotes a 32-bit unsigned integer, `exp2(x)` is $2^x$, `floor(x)` is $\lfloor x \rfloor$, `~` is bitwise NOT, `^` is bitwise XOR, `&` is bitwise AND, `|` is bitwise OR, and `>>` is a bitwise shift right. + +```python +function BarycentricsToSpaceFillingCurveIndex(u : f32, v : f32, L : u32) : u32 + # Scale up coordinates so each microtriangle has a side length of 1. + fu : f32 = u * exp2(L) + fv : f32 = v * exp2(L) + + # Assign integer coordinates iu, iv, and iw to the microtriangle so that: + iu : u32 = floor(fu) + iv : u32 = floor(fv) + # and so that either iu + iv + iw == 2^32 - 1, in which case the + # microtriangle has the same orientation as the one nearest v0, + # or iu + iv + iw == 2^32 - 2, in which case it has the other orientation. + iw : u32 = ~(iu + iv) + if ((fu - f32(iu)) + (fv - f32(iv)) >= 1.0) + { + iw = iw - 1 + } - return b0 | (b1 << 1u); -} + # Construct two integers b0 and b1; counting from the least + # significant bit, b0 will contain the even bits of the result, + # while b1 will contain the odd bits of the result. + b0 : u32 = ~(iu ^ iw) + # Mask it to L bits: + b0 = b0 & (exp2(L) - 1) + + temp1 : u32 = (iu ^ iv) & b0 + temp2 : u32 = temp1 + temp2 = temp2 ^ temp2 >> 1 + temp2 = temp2 ^ temp2 >> 2 + temp2 = temp2 ^ temp2 >> 4 + temp2 = temp2 ^ temp2 >> 8 + b1 : u32 = ((temp2 ^ iu) & ~b0) | temp1 + + # Interleave the bits of b0 and b1. + b0 = (b0 | (b0 << 8)) & 0x00ff00ff + b0 = (b0 | (b0 << 4)) & 0x0f0f0f0f + b0 = (b0 | (b0 << 2)) & 0x33333333 + b0 = (b0 | (b0 << 1)) & 0x55555555 + + b1 = (b1 | (b1 << 8)) & 0x00ff00ff + b1 = (b1 | (b1 << 4)) & 0x0f0f0f0f + b1 = (b1 | (b1 << 2)) & 0x33333333 + b1 = (b1 | (b1 << 1)) & 0x55555555 + + return b0 | (b1 << 1) ``` -### Illustration - -*This section is non-normative.* - -The resulting order follows a recursive space-filling curve. Each triangle is split into four sub-triangles, which are visited in the following order: - -1. The sub-triangle nearest `v0`. -2. The middle sub-triangle, with its child order flipped. -3. The sub-triangle nearest `v1`. -4. The sub-triangle nearest `v2`, with its child order flipped. - -The figure below shows the resulting microtriangle indices for subdivision levels 1 (left) and 2 (right), with `v0` at the bottom left, `v1` at the bottom right, and `v2` at the top. Blue arrows show the default child order and red arrows show flipped child order. - -Microtriangle indices at subdivision levels 1 and 2 - -## Appendix: Full Khronos Copyright Statement +## Appendix B: Full Khronos Copyright Statement Copyright 2018-2026 The Khronos Group Inc. From e61809def140b07a407746e481aabece01f67847 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin-Karl=20Lefran=C3=A7ois?= Date: Mon, 26 Oct 2026 13:02:00 +0100 Subject: [PATCH 06/12] EXT_mesh_opacity_micromap: normative microtriangle order without code - Define the space-filling curve order normatively as a recursion with explicit vertex order for each of the four sub-triangles, so the definition no longer depends on the reference function or the figure. - Add figures/micromap-split-rule.svg illustrating the split rule (vertex order of each sub-triangle, flipped winding marked), redrawn in spec notation from the "Bird curve recursive splitting rule" slide of NVIDIA's Micro-Mesh Basics deck. - Restore the subdivision figure legend (vertex placement, blue/red arrows). - Mark Appendix A non-normative and note equivalence with the VK_KHR_opacity_micromap reference code. - Restore clamping in the pseudocode so points on the triangle edges and slightly outside return a valid index (matches the Vulkan reference). - Pseudocode: parenthesize shifts inside XOR, define << and u32 wrap, use (1 << L) instead of exp2(L) for the integer mask. - Fix the copyright appendix link after the rename to Appendix B. - Overview: usage arrays count triangles per level/format combination. --- .../EXT_mesh_opacity_micromap/README.md | 76 +++++++++++++------ .../figures/micromap-split-rule.svg | 41 ++++++++++ 2 files changed, 92 insertions(+), 25 deletions(-) create mode 100644 extensions/2.0/Vendor/EXT_mesh_opacity_micromap/figures/micromap-split-rule.svg diff --git a/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md index 5f4f6c8268..135c6ca5fb 100644 --- a/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md +++ b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md @@ -14,7 +14,7 @@ SPDX-License-Identifier: LicenseRef-KhronosSpecCopyright - Martin-Karl Lefrançois, NVIDIA, [@mklefrancois](https://github.com/mklefrancois) Copyright 2018-2026 The Khronos Group Inc. All Rights Reserved. glTF is a trademark of The Khronos Group Inc. -See [Appendix](#appendix-full-khronos-copyright-statement) for full Khronos Copyright Statement. +See [Appendix B](#appendix-b-full-khronos-copyright-statement) for full Khronos Copyright Statement. ## Status @@ -35,7 +35,7 @@ More specifically, this extension consists of two parts within the glTF object s 1. [An array of `micromaps`](#extending-the-gltf-root) in the glTF root extension object. Each element of this array contains data for a number of *micromap triangles*: * the index of a buffer view of [packed microtriangle states](#microtriangle-data), * the index of a buffer view of [triangle records](#micromap-triangle-records), - * and counts of the used [subdivision levels](#subdivision-and-microtriangle-order) and [formats](#opacity-formats-and-states). + * and the number of micromap triangles for each combination of [subdivision level](#subdivision-and-microtriangle-order) and [format](#opacity-formats-and-states). 2. [A mesh primitive extension object](#extending-mesh-primitives) that associates each triangle of the primitive with a micromap triangle, or with a uniform opacity state. How opacity states affect ray traversal (for example, when an any-hit shader is invoked for unknown states) is defined by the graphics API and is outside the scope of this extension. @@ -144,19 +144,33 @@ All microtriangle positions and orderings in this extension are defined in terms A micromap triangle with subdivision level $L$ divides its base triangle into $4^L$ congruent microtriangles by recursively connecting edge midpoints. The subdivision level **MUST** be in the range `[0, 12]`. -Microtriangles are numbered from $0$ to $4^L - 1$ along a hierarchical space-filling curve. +Microtriangles are numbered from $0$ to $4^L - 1$ along a hierarchical space-filling curve, defined recursively as follows. -Microtriangle indices at subdivision levels 1 and 2 +For a triangle with vertices $(a_0, a_1, a_2)$ and $L = 0$, the triangle itself is the only microtriangle and has index $0$. For $L \geq 1$, let $m_{01}$, $m_{12}$, and $m_{02}$ be the midpoints of the edges $a_0 a_1$, $a_1 a_2$, and $a_0 a_2$. Connecting these midpoints splits the triangle into four sub-triangles $k = 0, 1, 2, 3$, each with the vertex order given in the following table: + +| $k$ | Sub-triangle | $(a_0', a_1', a_2')$ | +|-----|--------------|----------------------| +| $0$ | Nearest $a_0$ | $(a_0, m_{01}, m_{02})$ | +| $1$ | Middle | $(m_{02}, m_{12}, m_{01})$ | +| $2$ | Nearest $a_1$ | $(m_{01}, a_1, m_{12})$ | +| $3$ | Nearest $a_2$ | $(m_{12}, m_{02}, a_2)$ | + +A microtriangle that lies in sub-triangle $k$ and has index $j$ within that sub-triangle at subdivision level $L - 1$, using the vertex order $(a_0', a_1', a_2')$, has index $k \cdot 4^{L-1} + j$ in the triangle. + +The microtriangle indices of a micromap triangle are those obtained by applying this definition to its base triangle $(v_0, v_1, v_2)$ at its subdivision level. + +The figure below illustrates this rule for a base triangle. Each path starts at the first vertex $a_0'$ of a sub-triangle and visits its vertices in order. Sub-triangles $1$ and $3$ have the opposite winding from their parent. -The diagram above shows the microtriangle indices for subdivision levels 1 and 2, as well as the indexing used to recursively construct the space-filling curve. +Vertex order of the four sub-triangles of a subdivided triangle -To construct the microtriangle indices for level $L+1$ from the microtriangle indices for level $L$, replace each microtriangle in level 1 with a copy of the subdivided triangle for level $L$, concatenated in the following order: -1. The sub-triangle nearest `v0`. -2. The middle sub-triangle, with its child order flipped. -3. The sub-triangle nearest `v1`. -4. The sub-triangle nearest `v2`, with its child order flipped. +The figure below shows the resulting microtriangle indices for subdivision levels 1 (left) and 2 (right), with $v_0$ at the bottom left, $v_1$ at the bottom right, and $v_2$ at the top. Blue arrows show the default child order and red arrows show flipped child order. -The index of the microtriangle containing barycentric coordinates $(u, v)$ can also be computed by the reference function in [Appendix A](#appendix-a-microtriangle-indexing). +Microtriangle indices at subdivision levels 1 and 2 + +[Appendix A](#appendix-a-microtriangle-indexing) gives a function that computes the index of the microtriangle containing barycentric coordinates $(u, v)$ directly. + +> [!NOTE] +> This is the same microtriangle order as the one used by `VK_KHR_opacity_micromap` and by DirectX 12 opacity micromaps. ### Opacity Formats and States @@ -299,39 +313,51 @@ The `micromap` and `OpacityMicromapArray` fields refer to the micromap object th ## Appendix A: Microtriangle Indexing -The following pseudocode function takes a barycentric coordinate $(u, v), u+v<1$ on a base triangle with subdivision level $L \leq 12$, and returns the index of the microtriangle containing that coordinate. `f32` denotes a 32-bit floating-point number, `u32` denotes a 32-bit unsigned integer, `exp2(x)` is $2^x$, `floor(x)` is $\lfloor x \rfloor$, `~` is bitwise NOT, `^` is bitwise XOR, `&` is bitwise AND, `|` is bitwise OR, and `>>` is a bitwise shift right. +*This section is non-normative.* + +The following pseudocode function takes barycentric coordinates $(u, v)$ on a base triangle and a subdivision level $L \leq 12$, and returns the index of the microtriangle containing that point, as defined in [Subdivision and Microtriangle Order](#subdivision-and-microtriangle-order). It is equivalent to the reference code of the Vulkan [`VK_KHR_opacity_micromap`](https://docs.vulkan.org/refpages/latest/refpages/source/VK_KHR_opacity_micromap.html) extension. Inputs slightly outside the triangle, for example due to floating-point error, are mapped to a microtriangle on its boundary; for points on an edge shared by two microtriangles, the function returns one of them. + +`f32` denotes a 32-bit floating-point number and `u32` denotes a 32-bit unsigned integer; all `u32` arithmetic wraps modulo $2^{32}$. `clamp(x, lo, hi)` is $\min(\max(x, lo), hi)$, `exp2(x)` is $2^x$, `floor(x)` is $\lfloor x \rfloor$, `~` is bitwise NOT, `^` is bitwise XOR, `&` is bitwise AND, `|` is bitwise OR, and `<<` and `>>` are bitwise shifts left and right. ```python function BarycentricsToSpaceFillingCurveIndex(u : f32, v : f32, L : u32) : u32 + # n is the number of microtriangles along each edge. + n : u32 = 1 << L + + u = clamp(u, 0.0, 1.0) + v = clamp(v, 0.0, 1.0) + # Scale up coordinates so each microtriangle has a side length of 1. fu : f32 = u * exp2(L) fv : f32 = v * exp2(L) - # Assign integer coordinates iu, iv, and iw to the microtriangle so that: + # Assign integer coordinates iu and iv to the microtriangle, + # keeping them inside the triangle. iu : u32 = floor(fu) iv : u32 = floor(fv) - # and so that either iu + iv + iw == 2^32 - 1, in which case the + upperHalf : bool = (fu - f32(iu)) + (fv - f32(iv)) >= 1.0 + if (iu >= n) { iu = n - 1 } + if (iv >= n) { iv = n - 1 } + iuv : u32 = iu + iv + if (iuv >= n) { iu = iu - (iuv - n + 1) } + + # Assign iw so that either iu + iv + iw == 2^32 - 1, in which case the # microtriangle has the same orientation as the one nearest v0, # or iu + iv + iw == 2^32 - 2, in which case it has the other orientation. iw : u32 = ~(iu + iv) - if ((fu - f32(iu)) + (fv - f32(iv)) >= 1.0) - { - iw = iw - 1 - } + if (upperHalf and iuv < n - 1) { iw = iw - 1 } # Construct two integers b0 and b1; counting from the least # significant bit, b0 will contain the even bits of the result, # while b1 will contain the odd bits of the result. - b0 : u32 = ~(iu ^ iw) - # Mask it to L bits: - b0 = b0 & (exp2(L) - 1) + b0 : u32 = (~(iu ^ iw)) & (n - 1) temp1 : u32 = (iu ^ iv) & b0 temp2 : u32 = temp1 - temp2 = temp2 ^ temp2 >> 1 - temp2 = temp2 ^ temp2 >> 2 - temp2 = temp2 ^ temp2 >> 4 - temp2 = temp2 ^ temp2 >> 8 + temp2 = temp2 ^ (temp2 >> 1) + temp2 = temp2 ^ (temp2 >> 2) + temp2 = temp2 ^ (temp2 >> 4) + temp2 = temp2 ^ (temp2 >> 8) b1 : u32 = ((temp2 ^ iu) & ~b0) | temp1 # Interleave the bits of b0 and b1. diff --git a/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/figures/micromap-split-rule.svg b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/figures/micromap-split-rule.svg new file mode 100644 index 0000000000..fb0af75b4a --- /dev/null +++ b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/figures/micromap-split-rule.svg @@ -0,0 +1,41 @@ + + + + + + + + + 0 + + + + 1 + + + + 2 + + + + 3 + v + 0 + v + 1 + v + 2 + m + 01 + m + 12 + m + 02 + + + same winding as parent + + + flipped winding + Each path starts at the sub-triangle's first vertex and visits its vertices in order. + From 3a131ea9ddd23f07df0336b1f2d0a46c3058421d Mon Sep 17 00:00:00 2001 From: Nia Bickford Date: Mon, 26 Oct 2026 16:31:43 +0100 Subject: [PATCH 07/12] Remove some extra operations in the pseudocode since we can assume (u, v) is within the closed base triangle --- .../EXT_mesh_opacity_micromap/README.md | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md index 135c6ca5fb..2e4c6d922e 100644 --- a/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md +++ b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md @@ -315,37 +315,38 @@ The `micromap` and `OpacityMicromapArray` fields refer to the micromap object th *This section is non-normative.* -The following pseudocode function takes barycentric coordinates $(u, v)$ on a base triangle and a subdivision level $L \leq 12$, and returns the index of the microtriangle containing that point, as defined in [Subdivision and Microtriangle Order](#subdivision-and-microtriangle-order). It is equivalent to the reference code of the Vulkan [`VK_KHR_opacity_micromap`](https://docs.vulkan.org/refpages/latest/refpages/source/VK_KHR_opacity_micromap.html) extension. Inputs slightly outside the triangle, for example due to floating-point error, are mapped to a microtriangle on its boundary; for points on an edge shared by two microtriangles, the function returns one of them. +The following pseudocode function takes barycentric coordinates $(u, v)$ on a base triangle ($u \ge 0, v \ge 0, u+v \le 1$) and a subdivision level $L \leq 12$, and returns the index of the microtriangle containing that point, as defined in [Subdivision and Microtriangle Order](#subdivision-and-microtriangle-order). It is equivalent to the reference code of the Vulkan [`VK_KHR_opacity_micromap`](https://docs.vulkan.org/refpages/latest/refpages/source/VK_KHR_opacity_micromap.html) extension. Inputs slightly outside the triangle, for example due to floating-point error, are mapped to a microtriangle on its boundary; for points on an edge shared by two microtriangles, the function returns one of them. -`f32` denotes a 32-bit floating-point number and `u32` denotes a 32-bit unsigned integer; all `u32` arithmetic wraps modulo $2^{32}$. `clamp(x, lo, hi)` is $\min(\max(x, lo), hi)$, `exp2(x)` is $2^x$, `floor(x)` is $\lfloor x \rfloor$, `~` is bitwise NOT, `^` is bitwise XOR, `&` is bitwise AND, `|` is bitwise OR, and `<<` and `>>` are bitwise shifts left and right. +`f32` denotes a 32-bit floating-point number and `u32` denotes a 32-bit unsigned integer; all `u32` arithmetic wraps modulo $2^{32}$. `exp2(x)` is $2^x$, `floor(x)` is $\lfloor x \rfloor$, `~` is bitwise NOT, `^` is bitwise XOR, `&` is bitwise AND, `|` is bitwise OR, and `<<` and `>>` are bitwise shifts left and right respectively. ```python function BarycentricsToSpaceFillingCurveIndex(u : f32, v : f32, L : u32) : u32 # n is the number of microtriangles along each edge. n : u32 = 1 << L - u = clamp(u, 0.0, 1.0) - v = clamp(v, 0.0, 1.0) - # Scale up coordinates so each microtriangle has a side length of 1. - fu : f32 = u * exp2(L) - fv : f32 = v * exp2(L) + fu : f32 = u * n + fv : f32 = v * n # Assign integer coordinates iu and iv to the microtriangle, # keeping them inside the triangle. iu : u32 = floor(fu) iv : u32 = floor(fv) - upperHalf : bool = (fu - f32(iu)) + (fv - f32(iv)) >= 1.0 + # Handle the cases at (u, v) == (1, 0) and (0, 1). if (iu >= n) { iu = n - 1 } if (iv >= n) { iv = n - 1 } + # Handle the u + v == 1 edge. iuv : u32 = iu + iv - if (iuv >= n) { iu = iu - (iuv - n + 1) } + if (iuv >= n) { iu = (n - 1) - iv } # Assign iw so that either iu + iv + iw == 2^32 - 1, in which case the # microtriangle has the same orientation as the one nearest v0, # or iu + iv + iw == 2^32 - 2, in which case it has the other orientation. iw : u32 = ~(iu + iv) - if (upperHalf and iuv < n - 1) { iw = iw - 1 } + if (((fu - f32(iu)) + (fv - f32(iv)) >= 1.0) and iuv < n - 1) + { + iw = iw - 1 + } # Construct two integers b0 and b1; counting from the least # significant bit, b0 will contain the even bits of the result, From 1fe90ca1111bb5ec8f9f2038b95476904cca31a7 Mon Sep 17 00:00:00 2001 From: Nia Bickford Date: Mon, 26 Oct 2026 16:37:25 +0100 Subject: [PATCH 08/12] Fix sentence about edge case --- extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md index 2e4c6d922e..abcd829f4f 100644 --- a/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md +++ b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md @@ -315,7 +315,7 @@ The `micromap` and `OpacityMicromapArray` fields refer to the micromap object th *This section is non-normative.* -The following pseudocode function takes barycentric coordinates $(u, v)$ on a base triangle ($u \ge 0, v \ge 0, u+v \le 1$) and a subdivision level $L \leq 12$, and returns the index of the microtriangle containing that point, as defined in [Subdivision and Microtriangle Order](#subdivision-and-microtriangle-order). It is equivalent to the reference code of the Vulkan [`VK_KHR_opacity_micromap`](https://docs.vulkan.org/refpages/latest/refpages/source/VK_KHR_opacity_micromap.html) extension. Inputs slightly outside the triangle, for example due to floating-point error, are mapped to a microtriangle on its boundary; for points on an edge shared by two microtriangles, the function returns one of them. +The following pseudocode function takes barycentric coordinates $(u, v)$ on a base triangle ($u \ge 0, v \ge 0, u+v \le 1$) and a subdivision level $L \leq 12$, and returns the index of the microtriangle containing that point, as defined in [Subdivision and Microtriangle Order](#subdivision-and-microtriangle-order). It produces the same results as the reference code of the Vulkan [`VK_KHR_opacity_micromap`](https://docs.vulkan.org/refpages/latest/refpages/source/VK_KHR_opacity_micromap.html) extension. For points on an edge shared by two microtriangles, the function returns one of them. `f32` denotes a 32-bit floating-point number and `u32` denotes a 32-bit unsigned integer; all `u32` arithmetic wraps modulo $2^{32}$. `exp2(x)` is $2^x$, `floor(x)` is $\lfloor x \rfloor$, `~` is bitwise NOT, `^` is bitwise XOR, `&` is bitwise AND, `|` is bitwise OR, and `<<` and `>>` are bitwise shifts left and right respectively. From 8c97e7fb8882fdfb59af8ce85981ac2c3c377c9c Mon Sep 17 00:00:00 2001 From: Nia Bickford Date: Mon, 26 Oct 2026 16:45:02 +0100 Subject: [PATCH 09/12] Remove definition of exp2(), since we no longer use it --- extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md index abcd829f4f..c16010091c 100644 --- a/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md +++ b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md @@ -317,7 +317,7 @@ The `micromap` and `OpacityMicromapArray` fields refer to the micromap object th The following pseudocode function takes barycentric coordinates $(u, v)$ on a base triangle ($u \ge 0, v \ge 0, u+v \le 1$) and a subdivision level $L \leq 12$, and returns the index of the microtriangle containing that point, as defined in [Subdivision and Microtriangle Order](#subdivision-and-microtriangle-order). It produces the same results as the reference code of the Vulkan [`VK_KHR_opacity_micromap`](https://docs.vulkan.org/refpages/latest/refpages/source/VK_KHR_opacity_micromap.html) extension. For points on an edge shared by two microtriangles, the function returns one of them. -`f32` denotes a 32-bit floating-point number and `u32` denotes a 32-bit unsigned integer; all `u32` arithmetic wraps modulo $2^{32}$. `exp2(x)` is $2^x$, `floor(x)` is $\lfloor x \rfloor$, `~` is bitwise NOT, `^` is bitwise XOR, `&` is bitwise AND, `|` is bitwise OR, and `<<` and `>>` are bitwise shifts left and right respectively. +`f32` denotes a 32-bit floating-point number and `u32` denotes a 32-bit unsigned integer; all `u32` arithmetic wraps modulo $2^{32}$. `floor(x)` is $\lfloor x \rfloor$, `~` is bitwise NOT, `^` is bitwise XOR, `&` is bitwise AND, `|` is bitwise OR, and `<<` and `>>` are bitwise shifts left and right respectively. ```python function BarycentricsToSpaceFillingCurveIndex(u : f32, v : f32, L : u32) : u32 From 254d96022eac02e1f1044e8c25b21582e7cb8eee Mon Sep 17 00:00:00 2001 From: Nia Bickford Date: Mon, 26 Oct 2026 16:48:40 +0100 Subject: [PATCH 10/12] Switch code highlighting to Typescript --- .../EXT_mesh_opacity_micromap/README.md | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md index c16010091c..4b7444f511 100644 --- a/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md +++ b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md @@ -319,38 +319,39 @@ The following pseudocode function takes barycentric coordinates $(u, v)$ on a ba `f32` denotes a 32-bit floating-point number and `u32` denotes a 32-bit unsigned integer; all `u32` arithmetic wraps modulo $2^{32}$. `floor(x)` is $\lfloor x \rfloor$, `~` is bitwise NOT, `^` is bitwise XOR, `&` is bitwise AND, `|` is bitwise OR, and `<<` and `>>` are bitwise shifts left and right respectively. -```python +```typescript function BarycentricsToSpaceFillingCurveIndex(u : f32, v : f32, L : u32) : u32 - # n is the number of microtriangles along each edge. +{ + // n is the number of microtriangles along each edge. n : u32 = 1 << L - # Scale up coordinates so each microtriangle has a side length of 1. + // Scale up coordinates so each microtriangle has a side length of 1. fu : f32 = u * n fv : f32 = v * n - # Assign integer coordinates iu and iv to the microtriangle, - # keeping them inside the triangle. + // Assign integer coordinates iu and iv to the microtriangle, + // keeping them inside the triangle. iu : u32 = floor(fu) iv : u32 = floor(fv) - # Handle the cases at (u, v) == (1, 0) and (0, 1). + // Handle the cases at (u, v) == (1, 0) and (0, 1). if (iu >= n) { iu = n - 1 } if (iv >= n) { iv = n - 1 } - # Handle the u + v == 1 edge. + // Handle the u + v == 1 edge. iuv : u32 = iu + iv if (iuv >= n) { iu = (n - 1) - iv } - # Assign iw so that either iu + iv + iw == 2^32 - 1, in which case the - # microtriangle has the same orientation as the one nearest v0, - # or iu + iv + iw == 2^32 - 2, in which case it has the other orientation. + // Assign iw so that either iu + iv + iw == 2^32 - 1, in which case the + // microtriangle has the same orientation as the one nearest v0, + // or iu + iv + iw == 2^32 - 2, in which case it has the other orientation. iw : u32 = ~(iu + iv) - if (((fu - f32(iu)) + (fv - f32(iv)) >= 1.0) and iuv < n - 1) + if (((fu - f32(iu)) + (fv - f32(iv)) >= 1.0) && iuv < n - 1) { iw = iw - 1 } - # Construct two integers b0 and b1; counting from the least - # significant bit, b0 will contain the even bits of the result, - # while b1 will contain the odd bits of the result. + // Construct two integers b0 and b1; counting from the least + // significant bit, b0 will contain the even bits of the result, + // while b1 will contain the odd bits of the result. b0 : u32 = (~(iu ^ iw)) & (n - 1) temp1 : u32 = (iu ^ iv) & b0 @@ -361,7 +362,7 @@ function BarycentricsToSpaceFillingCurveIndex(u : f32, v : f32, L : u32) : u32 temp2 = temp2 ^ (temp2 >> 8) b1 : u32 = ((temp2 ^ iu) & ~b0) | temp1 - # Interleave the bits of b0 and b1. + // Interleave the bits of b0 and b1. b0 = (b0 | (b0 << 8)) & 0x00ff00ff b0 = (b0 | (b0 << 4)) & 0x0f0f0f0f b0 = (b0 | (b0 << 2)) & 0x33333333 @@ -373,6 +374,7 @@ function BarycentricsToSpaceFillingCurveIndex(u : f32, v : f32, L : u32) : u32 b1 = (b1 | (b1 << 1)) & 0x55555555 return b0 | (b1 << 1) +} ``` ## Appendix B: Full Khronos Copyright Statement From e215f1dbdbb6ada24f628cba889539c144a2a5c8 Mon Sep 17 00:00:00 2001 From: Nia Bickford Date: Mon, 26 Oct 2026 16:51:44 +0100 Subject: [PATCH 11/12] Appendix A: change `L` to `level` to work better with GitHub syntax highlighting --- extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md index 4b7444f511..9d83fb259b 100644 --- a/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md +++ b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md @@ -315,15 +315,15 @@ The `micromap` and `OpacityMicromapArray` fields refer to the micromap object th *This section is non-normative.* -The following pseudocode function takes barycentric coordinates $(u, v)$ on a base triangle ($u \ge 0, v \ge 0, u+v \le 1$) and a subdivision level $L \leq 12$, and returns the index of the microtriangle containing that point, as defined in [Subdivision and Microtriangle Order](#subdivision-and-microtriangle-order). It produces the same results as the reference code of the Vulkan [`VK_KHR_opacity_micromap`](https://docs.vulkan.org/refpages/latest/refpages/source/VK_KHR_opacity_micromap.html) extension. For points on an edge shared by two microtriangles, the function returns one of them. +The following pseudocode function takes barycentric coordinates $(u, v)$ on a base triangle ($u \ge 0, v \ge 0, u+v \le 1$) and a subdivision `level` ≤ 12, and returns the index of the microtriangle containing that point, as defined in [Subdivision and Microtriangle Order](#subdivision-and-microtriangle-order). It produces the same results as the reference code of the Vulkan [`VK_KHR_opacity_micromap`](https://docs.vulkan.org/refpages/latest/refpages/source/VK_KHR_opacity_micromap.html) extension. For points on an edge shared by two microtriangles, the function returns one of them. `f32` denotes a 32-bit floating-point number and `u32` denotes a 32-bit unsigned integer; all `u32` arithmetic wraps modulo $2^{32}$. `floor(x)` is $\lfloor x \rfloor$, `~` is bitwise NOT, `^` is bitwise XOR, `&` is bitwise AND, `|` is bitwise OR, and `<<` and `>>` are bitwise shifts left and right respectively. ```typescript -function BarycentricsToSpaceFillingCurveIndex(u : f32, v : f32, L : u32) : u32 +function BarycentricsToSpaceFillingCurveIndex(u : f32, v : f32, level : u32) : u32 { // n is the number of microtriangles along each edge. - n : u32 = 1 << L + n : u32 = 1 << level // Scale up coordinates so each microtriangle has a side length of 1. fu : f32 = u * n From d605f10481fd6695996d7a0e09ae65da15df1030 Mon Sep 17 00:00:00 2001 From: Nia Bickford Date: Mon, 26 Oct 2026 22:42:48 +0100 Subject: [PATCH 12/12] Add Alexey Knyazev as a contributor for suggestion about syntax highlighting --- extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md index 9d83fb259b..72bfe78ab9 100644 --- a/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md +++ b/extensions/2.0/Vendor/EXT_mesh_opacity_micromap/README.md @@ -12,6 +12,7 @@ SPDX-License-Identifier: LicenseRef-KhronosSpecCopyright - Arseny Kapoulkine, Independent, [@zeuxcg](https://zeux.io/) - Pyarelal Knowles, NVIDIA, [@pknowlesnv](https://github.com/pknowlesnv) - Martin-Karl Lefrançois, NVIDIA, [@mklefrancois](https://github.com/mklefrancois) +- Alexey Knyazev, Individual Contributor, [@lexaknyazev](https://github.com/lexaknyazev) Copyright 2018-2026 The Khronos Group Inc. All Rights Reserved. glTF is a trademark of The Khronos Group Inc. See [Appendix B](#appendix-b-full-khronos-copyright-statement) for full Khronos Copyright Statement.