Repository navigation
Conversation
…cademySoftwareFoundation#319) Clarify in the spec that specular_weight acts on the base beneath a thin film rather than scaling the film's reflection: the film is evaluated on a substrate whose reflectivity is modulated by specular_weight (the modulated IOR for dielectrics; the complex IOR of the w-scaled metal colors for metals, w = min(specular_weight, 1)), blended with the film-free Fresnel by thin_film_weight. As specular_weight -> 0 a free-standing film remains. In the reference graph the dielectric branch already behaves this way. The metal thin-film branch now evaluates the film on a conductor_bsdf whose ior/extinction come from artistic_ior of the w-scaled colors, instead of weighting the film-covered generalized_schlick_bsdf by specular_weight. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Update: this PR has now been revised to option C (description updated). It is fully consistent with #316; see the description. Following @sherholz's review in #319, the first version of this PR (option B) failed a basic requirement for metals: a film of vanishing thickness should look exactly like no film. When I've revised the metal part of this PR to "option C". Here
ChangesGraph (prototyped on
Spec
Graph diff (against
|
| 1 | 0.5 | 0.01 | 0 | |
|---|---|---|---|---|
| dev_1.2 graph | ![]() |
![]() |
![]() |
![]() |
| option B (first version) | ![]() |
![]() |
![]() |
![]() |
| option C (this PR) | ![]() |
![]() |
![]() |
![]() |
Zero-thickness film (thin_film_thickness = 0, thin_film_weight = 1), rendered in MaterialXView. This should look exactly like no film:
| no film | option B | option C | option B − no film (×4) | option C − no film (×4) | |
|---|---|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
![]() |
|
![]() |
![]() |
![]() |
![]() |
![]() |
Correction to the PR description
The first version of the description attributed the look change at
In mx_fresnel_airy, the Schlick branch (used by generalized_schlick_bsdf) sets R23p = R23s = 0.5 * f, where each should be f. The other branches use the full per-polarization reflectances. As a result, a film-covered generalized_schlick_bsdf renders about 40% too dark in MaterialXView, even at vanishing film thickness. For base_color 0.91, a 1 nm film gives 0.57 at normal incidence instead of about 0.91.
Arnold's own implementation of the closure doesn't show this. The bug is still present on MaterialX main and should be reported there separately. With option C, the metal film at dev_1.2.
…he film (AcademySoftwareFoundation#319) Replace the scaled-color conductor substrate with a coverage interpretation: the film covers a fraction w = min(specular_weight, 1) of metal and a fraction 1 - w index-matched to the surrounding medium (a free-standing film). A vanishingly thin film then has no effect for any specular_weight, while the film still remains at specular_weight = 0. Nothing changes at specular_weight >= 1 or without a film. Graph: the metal film lobes revert to the dev_1.2 film-covered generalized_schlick_bsdf, and a reflection-only dielectric_bsdf (ior 1, with the film), weighted by 1 - min(specular_weight, 1), is added to each. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…on#317) into thin_film_specular_weight Resolve conflicts with AcademySoftwareFoundation#316: keep its metal Fresnel text and refer the thin-film coverage sentence to equation metal_fresnel; the film-covered metal now uses the colors of equation metal_colors. In the graph, use AcademySoftwareFoundation#316's metal_weight as the film coverage instead of a duplicate min(specular_weight, 1) node. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dation#320 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Option D (for discussion):
|
specular_weight |
1 | 0.5 | 0.25 | 0.1 | 0 |
|---|---|---|---|---|---|
| Grey metal option C |
![]() |
![]() |
![]() |
![]() |
![]() |
| Grey metal option D |
![]() |
![]() |
![]() |
![]() |
![]() |
| Gold option C |
![]() |
![]() |
![]() |
![]() |
![]() |
| Gold option D |
![]() |
![]() |
![]() |
![]() |
![]() |
| Dielectric (current spec) |
![]() |
![]() |
![]() |
![]() |
![]() |
With film
specular_weight |
1 | 0.5 | 0.25 | 0.1 | 0 |
|---|---|---|---|---|---|
| Grey metal option C |
![]() |
![]() |
![]() |
![]() |
![]() |
| Grey metal option D |
![]() |
![]() |
![]() |
![]() |
![]() |
| Dielectric (current spec) |
![]() |
![]() |
![]() |
![]() |
![]() |
The grey metal has base_color 0.91 and a white specular_color. The gold is the repository's open_pbr_gold colors. The dielectric has a near-black base and specular_ior 1.5, and it is the same under both options.
Summary
| Option C (this PR) | Option D | |
|---|---|---|
| Rule | Metal: coverage of the metal beneath the film | Both bases: modulate the (complex) IOR |
| Metal without film, |
Unchanged (even fade) | Rim stays bright, like the dielectric |
| Metal at |
Unchanged | Conductor |
| Metal vanishes; with film, free-standing film | Same | |
| Zero-thickness film = no film | Yes | Yes |
|
|
Unchanged | Unchanged |
| Scope | Clarifies film behavior only | Changes the metal model |
Option D is the more consistent model, but it changes existing metals. Option C is the minimal change for 1.2. Opinions welcome.
|
@jamportz sounds like an interesting alternative. Could you add a column of weights closer to 0.0: like 0.025, 0.01, just to see if it fades out or makes a sudden disappearance. For the Fresnel plots it would be interesting to start with a F0 and F82 setup and also plot a version with wF0 and wF82 compared to the new proposal. That should strengthen the |
…on#320 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Here is GLSL for the The second function is the option D modulation of // Complex IOR (n, k) of the conductor whose reflectance is F0 at normal incidence and F82 at 82 degrees,
// at one wavelength or color channel. In OpenPBR terms, per channel:
// F0 = base_color
// F82 = specular_color * (F0 + (1 - F0) * pow(1 - cos(82 degrees), 5))
// Closed form in the good-conductor (surface-impedance) limit. It reproduces F0 exactly, and the rest of
// the Fresnel curve approximately. A few Newton steps make it exact.
vec2 conductor_ior_from_F0_F82(float F0, float F82)
{
const float mu = 0.1391731; // cos(82 degrees)
F0 = clamp(F0, 0.0, 0.9999);
float a = mu * (1.0 - F0) / (1.0 + F0);
float u = 1.0 - F82;
float b = 2.0*a*((1.0 + mu*mu)*F82 + a*(1.0 + F82));
float h = (b - (1.0 + mu*mu*mu*mu)*u) / min(2.0*mu*mu*u - b, -1e-9);
h = max(h, 1.0);
// |z|^2, where z = 1/(n + ik) is the surface impedance: the root of |z|^2 + 1/|z|^2 = 2h with |z| <= 1.
// It is clamped to the end of the range, the dielectric (k = 0) with this F0, which has the lowest F82.
float r = sqrt(F0);
float z2 = max(1.0 / (h + sqrt(h*h - 1.0)), (1.0 - r)*(1.0 - r) / ((1.0 + r)*(1.0 + r)));
float z = sqrt(z2);
float c = min(a * (1.0 + z2) / (2.0*mu*z), 1.0); // cosine of the phase of z
return vec2(c, sqrt(1.0 - c*c)) / z; // (n, k)
}
// Option D: specular_weight w in [0, 1] scales the normal-incidence amplitude r0 = (eta - 1)/(eta + 1) by sqrt(w),
// keeping its phase, as the spec's modulated IOR does for a dielectric.
vec2 cdiv(vec2 a, vec2 b) { return vec2(a.x*b.x + a.y*b.y, a.y*b.x - a.x*b.y) / dot(b, b); }
vec2 modulate_ior(vec2 eta, float w)
{
vec2 one = vec2(1.0, 0.0);
vec2 r = sqrt(w) * cdiv(eta - one, eta + one);
return cdiv(one + r, one - r);
}
|
































































Fixes #319.
Summary
The spec did not say how
specular_weightinteracts with thin-film iridescence, and the reference graph used different rules for metal and dielectric bases (see #319). This PR specifies thatspecular_weightmodulates the base beneath the film, rather than scaling the film's reflection, in the same way as it modulates the base without a film:modulated_ior). The graph already did this.Here$\tilde{\mathbf{n}}$ is the effective complex IOR of the (unscaled) metal, and $\mathbf{R}_\mathrm{film}(\mu;\, 1)$ is a free-standing film. With $T$ = $(1 - T)\, \mathbf{F}_\textrm{no film}(\mu) + T\, \mathbf{R}_\mathrm{film}(\mu)$ .
thin_film_weight, the Fresnel factor of the film-covered base isProperties:
specular_weightand both base types.specular_weight→ 0, both base types leave a free-standing film, still controlled bythin_film_weight.specular_weight≥ 1, nothing changes.specular_weightboost), which is now indev_1.2and merged into this branch. See Consistency with #316 below.This replaces the first version of this PR ("option B"), which evaluated the metal film on a conductor whose colors were scaled by$w$ . As @sherholz pointed out, that made a zero-thickness film look different from no film whenever
specular_weight< 1. The details are in this comment on #319.Changes
Spec (
index.html)thin_film_metal), and the blend equationthin_film_fresnel.Reference MaterialX graph (
reference/open_pbr_surface.mtlx)modulated_eta_s.dielectric_bsdfwithior= 1 carrying the film (i.e. a free-standing film) is added to the existing film-coveredgeneralized_schlick_bsdf. Its weight is1 - metal_weight, wheremetal_weight=min(specular_weight, 1)is the overall metal weight from #316. The new nodes aremetal_film_uncovered,metal_bsdf_tf_free_filmandmetal_bsdf_tf_covered, plus the haze equivalents.Consistency with #316
This PR is fully consistent with #316, which is now in
dev_1.2; I have mergeddev_1.2into this branch.metal_fresnel), with the colorsmetal_colors). The coverage1 - metal_weight, using #316's own node.Renders
The metal (
base_color0.91) and a near-black dielectric, each with a 0.5 µm film of IOR 1.4,specular_roughness0.1 andthin_film_weight= 1. Rendered in Arnold 7.5.4, from the graphs compiled to OSL.dev_1.2this PR
dev_1.2and this PRdev_1.2.dev_1.2the film vanished on the metal.Zero-thickness film (
thin_film_thickness= 0,thin_film_weight= 1). This should look exactly like no film. It is rendered in MaterialXView, because Arnold's OSLconductor_bsdf(used by option B) doesn't honorior/extinction.Notes
thin_film_weight> 0, since the film is still present.dev_1.2, the graph validates cleanly.generalized_schlick_bsdf. In MaterialXView, a film-coveredgeneralized_schlick_bsdfrenders about 40% too dark. Inmx_fresnel_airy, the Schlick branch setsR23p = R23s = 0.5 * fwhere each should bef. This affectsdev_1.2equally and is a separate MaterialX issue. Arnold's own closure is unaffected, which is why the main renders above use Arnold.🤖 Generated with Claude Code