Support refraction without the metalness workflow - #9147
Conversation
Refraction assumed the metalness / specular workflow throughout, so enabling it on a material without useMetalness generated an invalid shader (cube refraction) or read uninitialized shader globals (dynamic refraction). - include and evaluate ior in the refraction front-end block when the metalness path does not, and upload material_refractionIndex to match - gate specularity and gloss on refraction as well, as addRefraction uses both - include refractionCubePS based on the reflection source rather than the specular reflection path, which requires useSpecular - declare STD_SPECULAR_CONSTANT for refractive materials, so the fresnel uses the material specular color instead of defaulting to white and cancelling the refraction out - evaluate refraction outside of the lighting / reflections block, so it is not silently skipped in scenes with neither Adds a metalness toggle to the material refraction example to cover both workflows.
Build size reportThis PR changes the size of the minified bundles.
|
mvaligursky
left a comment
There was a problem hiding this comment.
Automated PR review — Codex (GPT-5)
No blocking findings.
I traced the full shader path from StandardMaterial option generation and uniform upload through fragment declarations, front-end argument initialization, shared lit defines, reflection-source chunk selection, and backend evaluation. The refraction-only path now initializes normal, IOR, gloss, and specularity consistently; cube refraction no longer depends on LIT_REFLECTIONS, and moving addRefraction outside the lighting/reflections block preserves its ordering relative to the existing area-light reset and AO processing. The GLSL and WGSL changes remain structurally matched, with no effect on non-refraction variants or public APIs.
Runtime validation on the deployed preview covered cube and dynamic refraction, metalness enabled and disabled, on both WebGL 2 and WebGPU. Every variant rendered correctly and produced no preview-origin warnings or shader compilation errors. All reported CI checks are also green.
Refraction (both cubemap and dynamic) could only be used together with
useMetalness. Without it, the generated shader either failed to compile or read uninitialized shader globals, as reported in #7514. This makes refraction work in both workflows.This is also the direction glTF specifies:
KHR_materials_transmissiondefines transmission on the dielectric part of the material and notes that formetallicFactor = 1.0the transmission factor no longer matters, so refraction was never conceptually tied to the metalness toggle - it needs the IOR-driven dielectric fresnel, which the engine only happened to compute in the metalness path.Changes:
iorin the refraction front end when the metalness path does not, and uploadmaterial_refractionIndexto matchaddRefractionconsumes bothrefractionCubePSbased on the reflection source instead of the specular reflection path, which additionally requiresuseSpecular. This is what produced the'addRefraction' : no matching overloaded function founderror in the issueSTD_SPECULAR_CONSTANTfor refractive materials, so the fresnel uses the material specular color rather than defaulting to white, which cancelled the refraction out entirelyneedsNormalnow accounts for refractionAll of the above are applied to both the GLSL and WGSL chunks.
Examples:
Notes:
Every shader variant touched here previously either failed to compile or read uninitialized values, so no working variant changes. Verified by hashing the generated fragment shaders against a build of the base commit: variants without refraction are byte identical, and the metalness refraction variants are identical modulo the whitespace of the moved
addRefractioncall.A follow up worth doing separately is giving the
litArgs_*globals default initializers, to rule out this class of uninitialized reads. It is kept out of this PR because it changes the generated source of every lit shader.