Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
2fbd1b9
draft wip
markschlosseratbentley Sep 11, 2025
2b24930
First draft
eringram Dec 9, 2025
b8f7a36
Updates
eringram Dec 11, 2025
6bc221c
WIP implementation notes
eringram Dec 11, 2025
71be55d
Fix implementation notes formula
eringram Dec 12, 2025
73c0a10
Clarify usage
eringram Dec 12, 2025
7b0ac09
Fix comments
eringram Dec 15, 2025
32d7a8c
Improve spec
eringram Dec 15, 2025
89f1652
Sentence update
eringram Dec 23, 2025
e425416
Update extensions/2.0/Vendor/EXT_textureInfo_constant_lod/README.md
eringram Jan 7, 2026
f8157de
Update extensions/2.0/Vendor/EXT_textureInfo_constant_lod/README.md
eringram Jan 7, 2026
0476ac3
Improve wording
eringram Jan 7, 2026
84cdd58
Merge branch 'EXT_textureInfo_constant_lod' of https://github.com/Ces…
eringram Jan 7, 2026
f886f2a
Update extensions/2.0/Vendor/EXT_textureInfo_constant_lod/README.md
eringram Jan 8, 2026
f3ca499
Mention texCoord fallback
eringram Jan 8, 2026
5de23e0
Wording
eringram Jan 8, 2026
3b03d91
contributors
markschlosseratbentley Jan 8, 2026
4dd2548
Update README.md
danielzhong Jan 13, 2026
2713715
Allow repetititons to be <1, rm divide by 0 for clamp result
eringram Jan 14, 2026
c3ef591
Mention sync base color and normal texture props
eringram Jan 16, 2026
e1ee91e
Update schema version
eringram Jan 16, 2026
befe6b8
Fix latex formatting
eringram Jan 20, 2026
d9d5197
Make texCoord fallback UVs required
eringram Jan 22, 2026
e846ce8
Fix newlines in latex
eringram Jan 22, 2026
2310619
Another newline formatting test
eringram Jan 22, 2026
0a07ef7
Format test
eringram Jan 22, 2026
37772e2
Format test
eringram Jan 22, 2026
f365724
Formatting, use gather instead of align
eringram Jan 22, 2026
795ff6c
Improve formula readability
eringram Jan 23, 2026
94be333
Update extensions/2.0/Vendor/EXT_textureInfo_constant_lod/README.md
eringram Jan 23, 2026
26cec29
Fix note & mult. symbols
eringram Jan 23, 2026
6843a26
Add known implementations
eringram Jan 23, 2026
745a68e
Update extensions/2.0/Vendor/EXT_textureInfo_constant_lod/README.md
eringram Jan 28, 2026
86c83b7
Update extensions/2.0/Vendor/EXT_textureInfo_constant_lod/README.md
eringram Jan 28, 2026
45aef50
Update extensions/2.0/Vendor/EXT_textureInfo_constant_lod/README.md
eringram Jan 28, 2026
7467e5a
Remove line
eringram Jan 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
136 changes: 136 additions & 0 deletions extensions/2.0/Vendor/EXT_textureInfo_constant_lod/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<!--
Copyright 2025 Bentley Systems, Incorporated
SPDX-License-Identifier: CC-BY-4.0
-->
Comment thread
eringram marked this conversation as resolved.

# EXT_textureInfo_constant_lod

## Contributors

* Paul Connelly, Bentley Systems, [@pmconne](https://github.com/pmconne)
* Erin Ingram, Bentley Systems, [@eringram](https://github.com/eringram)
* Mark Schlosser, Bentley Systems, [@markschlosseratbentley](https://github.com/markschlosseratbentley)
* Daniel Zhong, Bentley Systems, [@danielzhong](https://github.com/danielzhong)

## Status

Complete

## Dependencies

Written against the glTF 2.0 spec.

## Overview

Constant level-of-detail ("LOD") is a technique of texture coordinate generation which dynamically calculates texture coordinates to maintain a consistent texel-to-pixel ratio on screen, regardless of camera distance. As the camera zooms in or out, the texture coordinates are recalculated so that the texture pattern remains at approximately the same visual scale. The transition between scale levels is smoothly blended to avoid abrupt changes.

The `EXT_textureInfo_constant_lod` extension defines properties needed to calculate these dynamic texture coordinates: the number of times the texture is repeated per meter, an offset to shift the texture, and the minimum and maximum distance for which to clamp the texture. The minimum and maximum clamp distance control at which point the texture sizes shall be blended. These images illustrate the textures blending to create the constant LOD effect:

![Constant LOD gif](./figures/constantlod.gif "Constant LOD gif")

![Constant LOD image](./figures/constantlod.jpg "Constant LOD image")

The extension specifies an alternative way of computing the texture coordinates, so if it is supported by the client then the `textureInfo`'s `texCoord` is not used. For maximum compatibility however, encoders still must provide `texCoord` UV coordinates so the texture renders even if the extension is not supported.

## Specifying Constant LOD Texture Mapping

The `EXT_textureInfo_constant_lod` extension is defined on `textureInfo` structures. When that `textureInfo` is used by a material, this extension applies the constant LOD technique to the specified texture.
Comment thread
markschlosseratbentley marked this conversation as resolved.

Constant LOD uses the following properties:

* `repetitions` - specifies the number of times the texture is repeated per meter in both the X and Y dimensions. Increasing this will make the texture pattern appear smaller; decreasing it will make it appear larger.
* `offset` - used to shift the texture, specified as a pair of numbers in meters in the format [X, Y].
* `minClampDistance` - specifies the minimum distance in meters from the camera to the surface at which to clamp the texture. This value must not be greater than `maxClampDistance`.
* `maxClampDistance` - specifies the maximum distance in meters from the camera to the surface at which to clamp the texture. This value must not be less than `minClampDistance`.

For example, the following JSON defines a material with a texture at index 0 that is modified by the `EXT_textureInfo_constant_lod` extension. The extension has a `repetitions` value of 2 causing it to be repeated twice per meter, making its pattern appear half the size. It is shifted by 1 meter in the X direction and 0 meters in the Y direction. It also has a minimum clamp distance of 0.5 meters, meaning the constant LOD effect stops being present when the camera is 0.5m away from the surface or closer. This is a closer distance than the default value of 1m; therefore, this material requires a closer zoom before the texture stops adapting its level of detail (and for it to appear magnified). The absence of the `maxClampDistance` property means it has a default value of $2^{32}$, so the constant LOD effect will occur until the camera is $2^{32}$ away from the surface.

```json
"materials": [
{
"pbrMetallicRoughness": {
"baseColorTexture": {
"index": 0,
"texCoord": 0,
"extensions": {
"EXT_textureInfo_constant_lod": {
"repetitions": 2,
"offset": [1, 0],
"minClampDistance": 0.5
}
}
},
"baseColorFactor": [1.0, 1.0, 1.0, 1.0],
"metallicFactor": 0.0,
"roughnessFactor": 1.0
}
}
],
```

## Implementation Notes

The `EXT_textureInfo_constant_lod` extension may be present on any `textureInfo` object, including those that extend `textureInfo` such as `normalTextureInfo`. Encoders and clients may decide to implement the extension such that the normal texture has identical constant LOD properties to the base color texture to produce a synchronized blending of levels-of-detail, although this is not required.

### Formulas

This section outlines the formulas that shall be used by implementations to calculate the dynamic texture coordinates for constant LOD.

In the vertex shader:

```math
\begin{aligned}
customUvCoords.xy &= worldPosition.xy + offset\\
customUvCoords.z &= \begin{cases}
-eyeSpace & \text{if } isPerspectiveProjection \\
frustrumWidth & \text{if } isOrthographicProjection \\
\end{cases}
\end{aligned}
```

Where $worldPosition$ is the vertex position in world coordinates, $eyeSpace$ is the vertex position in camera coordinates, and $frustumWidth$ is the frustum width in meters.

The resulting $customUvCoords.xy$ contain the vertex's X and Y position in world coordinates. $customUvCoords.z$ is the negative eye-space depth (z-coordinate) from the camera to the fragment when using perspective projection. Since all points are equidistant to the camera when using orthographic projection, this value shall be set to the frustum width as a proxy for zoom level. $customUvCoords$ should then be passed into the fragment shader as a `varying`.

In the fragment shader:

```math
\begin{aligned}
(x, y, z) &= customUvCoords\\
minDistance &= min(clampDistance) \\
maxDistance &= max(clampDistance) \\
logDepth &= \log_{2}{z}\\
textureCoordinates_1 &= \frac{xy}{clamp(2^{\lfloor logDepth \rfloor}, minDistance, maxDistance)} \cdot repetitions\\
textureCoordinates_2 &= \frac{xy}{clamp(2^{\lfloor logDepth \rfloor + 1}, minDistance, maxDistance)} \cdot repetitions\\
result &= mix(textureCoordinates_1, textureCoordinates_2, fract(logDepth))
\end{aligned}
```

*Non-normative note: the addition of $\lfloor logDepth \rfloor + 1$ when calculating $textureCoordinates_2$ allows the result of the $mix$ function to blend between two adjacent mipmap levels.*

Where $clamp(x, minVal, maxVal)$ constrains the value of $x$ to the range of $minVal$ to $maxVal$, defined by the [GLSL definition](https://registry.khronos.org/OpenGL-Refpages/gl4/html/clamp.xhtml) of:
Comment thread
markschlosseratbentley marked this conversation as resolved.

```math
\min(\max(x, minVal), maxVal)
```

$fract(x)$ returns the fractional part of $x$, defined by the [GLSL definition](https://registry.khronos.org/OpenGL-Refpages/gl4/html/fract.xhtml) of:

```math
x - \lfloor x \rfloor
```

and $mix(x, y, a)$ performs a linear interpolation between $x$ and $y$ using $a$ to weight between them, using the [GLSL definition](https://registry.khronos.org/OpenGL-Refpages/gl4/html/mix.xhtml) of:

```math
x \cdot (1 − a) + y \cdot a
```

Comment thread
eringram marked this conversation as resolved.
## JSON Schema

* [textureInfo.EXT_textureInfo_constant_lod.schema.json](schema/textureInfo.EXT_textureInfo_constant_lod.schema.json)

## Known Implementations

* [iTwin.js](https://github.com/iTwin/itwinjs-core/pull/8882)
* [CesiumJS](https://github.com/CesiumGS/cesium/pull/13121)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "EXT_textureInfo_constant_lod TextureInfo Extension",
"type": "object",
"description": "Enables dynamically calculated UV coordinates to keep the texture near a constant level of detail",
"allOf": [
{ "$ref": "glTFProperty.schema.json" }
],
"properties": {
"repetitions": {
"type": "number",
"description": "The number of times the texture is repeated per meter.",
"exclusiveMinimum": 0.0,
"default": 1.0
},
"offset": {
"type": "array",
"description": "The offset in meters used to shift the texture.",
"items": {
"type": "number"
},
"minItems": 2,
"maxItems": 2,
"default": [ 0.0, 0.0 ]
},
"minClampDistance": {
"type": "number",
"description": "The minimum distance in meters from the eye to the surface at which to clamp the texture.",
"minimum": 0.0,
"default": 1.0
},
"maxClampDistance": {
"type": "number",
"description": "The maximum distance in meters from the eye to the surface at which to clamp the texture.",
"exclusiveMinimum": 0.0,
"default": 4294967296.0
},
"extensions": { },
"extras": { }
Comment thread
pmconne marked this conversation as resolved.
}
}