Skip to content

Support refraction without the metalness workflow - #9147

Merged
mvaligursky merged 1 commit into
mainfrom
mv-refraction-no-metalness
Jul 31, 2026
Merged

Support refraction without the metalness workflow#9147
mvaligursky merged 1 commit into
mainfrom
mv-refraction-no-metalness

Conversation

@mvaligursky

Copy link
Copy Markdown
Contributor

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_transmission defines transmission on the dielectric part of the material and notes that for metallicFactor = 1.0 the 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:

  • include and evaluate ior in the refraction front end when the metalness path does not, and upload material_refractionIndex to match
  • gate specularity and gloss on refraction as well, since addRefraction consumes both
  • include refractionCubePS based on the reflection source instead of the specular reflection path, which additionally requires useSpecular. This is what produced the 'addRefraction' : no matching overloaded function found error in the issue
  • declare STD_SPECULAR_CONSTANT for refractive materials, so the fresnel uses the material specular color rather than defaulting to white, which cancelled the refraction out entirely
  • evaluate refraction outside of the lighting / reflections block, so it is no longer silently skipped in scenes with neither
  • needsNormal now accounts for refraction

All of the above are applied to both the GLSL and WGSL chunks.

Examples:

  • Material Refraction: added a Metalness toggle (on by default) to exercise both workflows

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 addRefraction call.

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.

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.
@github-actions

Copy link
Copy Markdown

Build size report

This PR changes the size of the minified bundles.

Bundle Minified Gzip Brotli
playcanvas.min.js 2356.2 KB (+0.9 KB, +0.04%) 605.0 KB (+0.1 KB, +0.02%) 469.6 KB (−0.3 KB, −0.06%)
playcanvas.min.mjs 2353.6 KB (+0.9 KB, +0.04%) 604.0 KB (+0.1 KB, +0.02%) 469.4 KB (+0.1 KB, +0.02%)

@mvaligursky mvaligursky left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@mvaligursky
mvaligursky merged commit 7763f11 into main Jul 31, 2026
10 checks passed
@mvaligursky
mvaligursky deleted the mv-refraction-no-metalness branch July 31, 2026 09:50
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.

1 participant