Skip to content

Use IOR for dielectric specular reflectance - #26044

Open
stuartparmenter wants to merge 15 commits into
bevyengine:mainfrom
stuartparmenter:ior-dielectric-reflectance
Open

stuartparmenter wants to merge 15 commits into
bevyengine:mainfrom
stuartparmenter:ior-dielectric-reflectance

Conversation

@stuartparmenter

@stuartparmenter stuartparmenter commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Objective

Bevy's StandardMaterial sets dielectric reflectance with Filament's reflectance parameter, where F0 = 0.16 × reflectance². glTF defines it differently:

  • KHR_materials_ior derives F0 from the index of refraction.
  • KHR_materials_specular scales the specular response linearly with specularFactor and tints F0 with specularColorFactor.

Filament's reflectance can't express glTF's IOR-driven F0 or its linear specular factor, so Bevy approximates them. It ignores the glTF IOR for reflection, squares the specular factors and textures, and drops the tint in deferred. As a result, glTF assets render differently in Bevy than in other glTF viewers. Every reflectance value maps to an IOR with the same F0, so this PR switches to glTF's material parameters and keeps the shared GGX BRDF.

It builds on Christophe Dehais's PR #24552.

Solution

  • Dielectric F0 is ((ior - 1) / (ior + 1))² tinted by specular_tint, as KHR_materials_ior and KHR_materials_specular define it. StandardMaterial::reflectance is replaced by ior and a specular weight. glTF factors and textures map onto these fields without conversion.
  • specular scales the whole dielectric specular lobe, both F0 and grazing reflectance, as specularFactor does in the spec. Every lighting path applies it. For image-based and baked lighting, the energy it removes goes to diffuse, as in the spec's dielectric BRDF.
  • A clearcoat leaves the base layer's IOR unchanged, as KHR_materials_clearcoat defines. (This differs from the original PR which adjusted it relative to the coat, as Filament does.)
  • Deferred stores the tint in a second G-buffer target, so deferred matches forward. Devices without room for that target, including WebGL2, fall back to a white tint. On WebGL2, deferred also stores the specular weight only as on or off.
  • Solari uses the same parameters and textures.
  • Fixes bugs in the original PR, for example an SSR shader compile error, WebGL2 G-buffer packing, and IOR 0, which the spec uses for spec-gloss conversion.
  • Adds a migration guide.

Testing

Tested with my game, the affected examples, and headless renders in forward, deferred, deferred with SSR, and Solari. Not tested on WebGL2.


Showcase

Main and this branch were rendered with the same scenes. The glTF sample models use the HDRIs that Khronos's reference screenshots used, the Sample Viewer's exposure, and the dfg_lut feature. Without dfg_lut, Bevy's default polynomial approximation of the environment BRDF leaves a faint reflection at IOR 1.0.

F0. Main uses 4% for every IOR and squares specularFactor. This branch follows the spec.

F0 against IOR and against specularFactor

IORTestGrid. Reflection now follows the IOR, as in the reference.

IORTestGrid on main, this branch, and the Khronos reference

SpecularTest. This branch is linear in both factors and caps F0 at 100%, as in the reference.

SpecularTest on main, this branch, and the Khronos reference

Specular tint in deferred. Main's deferred renderer drops the tint. On this branch deferred matches forward.

Specular tint in forward and deferred on main and this branch

White furnace. Every sphere matches the background on both, so the specular weight moves energy between lobes without adding or losing any. The outlines mark the spheres.

White furnace on main and this branch

Solari path tracer. Left to right: IOR 1.0, 1.5 and 2.42, a specular texture with alpha 0, and a red tint texture. Main ignores all of them.

Solari path tracer on main and this branch


I used Claude and Opus 5.5 and Fable 5.1 to help put this together and to help put together some of the comparison images. I stumbled on to the core issue here in my game where I was using Houdini MaterialX materials on my trees, converting to gltf, and loading in to Bevy and was noticing they did not look the same. I had built a smaller fix first, before stumbling on to the PR referenced above. It was the better direction, so I evaluated what was was needed/missing from the other PR and decided that there was enough still to do, and the PR had bitrotted signifigantly, that it made more sense to open this one instead. I spent a while trying to understand the differences between gltf, openpbr and filament here, and decided that embracing the gltf spec made the most sense to me. After many iterations of making sure I got everything and many iterations of testing, I cleaned up everything, reviewed it carefully, and put this PR together!

@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

Your PR caused a change in the graphical output of an example or rendering test. This might be intentional, but it could also mean that something broke!
You can review it at https://pixel-eagle.com/project/B04F67C0-C054-4A6F-92EC-F599FEC2FD1D?filter=PR-26044

If it's expected, please add the M-Deliberate-Rendering-Change label.

If this change seems unrelated to your PR, you can consider updating your PR to target the latest main branch, either by rebasing or merging main into it.

@stuartparmenter stuartparmenter added A-Rendering Drawing game state to the screen S-Needs-Review Needs reviewer attention (from anyone!) to move forward A-glTF Related to the glTF 3D scene/model format labels Oct 7, 2026
@alice-i-cecile alice-i-cecile added C-Refinement Improves output quality, without fixing a clear bug or adding new functionality. C-Bug An unexpected or incorrect behavior labels Oct 7, 2026
@alice-i-cecile

Copy link
Copy Markdown
Member

@goodartistscopy, your review here would be very welcome.

@goodartistscopy

Copy link
Copy Markdown
Contributor

Glad you took it up, I'll have a look !

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-glTF Related to the glTF 3D scene/model format A-Rendering Drawing game state to the screen C-Bug An unexpected or incorrect behavior C-Refinement Improves output quality, without fixing a clear bug or adding new functionality. S-Needs-Review Needs reviewer attention (from anyone!) to move forward

Projects

Status: Needs SME Triage

Development

Successfully merging this pull request may close these issues.

3 participants