Skip to content

glTF: texture transform, node visibility, animation pointer, and the rendering fixes they surfaced - #447

Merged
doranteseduardo merged 4 commits into
developfrom
feat/gltf-khr-extensions
Nov 8, 2026
Merged

doranteseduardo merged 4 commits into
developfrom
feat/gltf-khr-extensions

Conversation

@doranteseduardo

Copy link
Copy Markdown
Member

Summary

Viro now reads the glTF extensions most real-world GLBs ship with, and a set of loader and shader defects that a large corpus of production models exposed is fixed. Before this, every file using KHR_animation_pointer failed to load outright, emission never showed on PBR materials, opaque meshes went see-through wherever their texture's alpha was low, and on the web no skinned model drew at all.

Loader (VROGLTFLoader, tiny_gltf.h)

  • KHR_texture_transform and texture coordinate sets. Offset, rotation and scale apply when the material is drawn, and a texture can sample TEXCOORD_1 or later (texCoord, which the extension may override). Viro samples a material's textures through one set and one transform, chosen by the base color texture (else the first other texture); the emissive texture keeps its own transform.
  • KHR_node_visibility. Hidden nodes load hidden with their descendants.
  • KHR_animation_pointer. Node visibility, baseColorFactor, emissiveFactor and the base color texture's offset/rotation/scale are driven per glTF animation and play under its name alongside its node and skeletal channels. Pointers at a node's TRS or weights are rewritten into ordinary channels at parse time; other pointers are skipped with a warning naming them.
  • tinygltf follows glTF 2.0 where it was stricter: an animation channel's node is optional (this is what made pointer files fail), only asset is required at the top level, and a sampler's interpolation defaults to LINEAR (Khronos's Fox sample failed without it).
  • Alpha modes. OPAQUE mapped to RGBZero, which nothing implements, so the base color's, texture's and vertex colors' alpha leaked into blending. OPAQUE and MASK now ignore it (VROMaterial::setIgnoresDiffuseAlpha); the material's transparency and the node's opacity still apply, so fades keep working. BLEND no longer writes depth, as three.js, Babylon and Filament do.
  • Skins animated through nodes between their joints. A joint's parent was looked for only among direct children of joints, and an animation counted as skeletal only if it targeted a joint, so rigs with an animated node above each joint (Waist_tr → Waist) had a broken bone tree and played in bind pose. A joint's world transform is now the product of every node in its chain (animated or at rest) up to the skinned mesh's own ancestors, and an animation of any of those nodes drives the skin.
  • 8-bit indices are widened to 16 bits: GL drew them as 32-bit and Metal has no 8-bit index type.

Shaders and materials

  • material_texture_transform (GL, a surface modifier ahead of every texture sample) and texture_transform in VROMaterialUniforms (Metal, applied in the vertex stage).
  • Emissive textures are sampled (they were bound and never read), and the three PBR ambient modifiers keep the emissive term they dropped; visionOS's PBR path gains an emissive color.
  • Bone indices are uvec4 and every integer source is bound unsigned: WebGL refused glTF's unsigned 8/16-bit joints against ivec4, so skinned meshes never drew on the web.
  • GLSL changes are mirrored in wasm/preload and android/sharedCode/src/main/assets.

Culling (VRONode)

A subtree holding a skinned mesh is never culled whole: its bounds come from the bind pose, so a model posed away from it lost parts (a character's eyes) as the camera came close.

Notes

  • One transform and one UV set per material is a deliberate limit of Viro's material model; the corpus and the Khronos samples tested here fit it. Emissive textures are not sampled on visionOS yet (Metal draws emission as a color).
  • Draco (KHR_draco_mesh_compression) is still unsupported.
  • CHANGELOG.md and RELEASE_NOTES.md are updated under 3.1.0.

Testing

  • Web build rendered headless (Chromium) against three.js 0.170 as a glTF reference:
    • Khronos samples: TextureTransformMultiTest (every row passes but clearcoat, which is another extension), MultiUVTest, NodeVisibilityTest, CubeVisibility, AnimatedColorsCube, AnimationPointerUVs, PotOfCoalsAnimationPointer, CesiumMan, Fox, RiggedFigure; all load and animate.
    • A 74-model stratified sample of a large third-party corpus that uses these extensions heavily: 46 loaded before, with 17 raising WebGL errors; all 74 load now with none.
    • Every model in the showcase app (GLB, VRX, OBJ) before vs after: 10 are identical; the 5 that change all move toward the reference (skinned models that never drew on the web now draw; DamagedHelmet and a vehicle show their emissive parts; BLEND foliage no longer occludes itself).
  • Metal shaders compile for visionOS; the visionOS xcframework and the Android AAR build. iOS device and simulator builds are running.

@doranteseduardo
doranteseduardo merged commit 2a86e44 into develop Nov 8, 2026
4 checks passed
@doranteseduardo
doranteseduardo deleted the feat/gltf-khr-extensions branch November 8, 2026 21:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants