Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Coat roughening convolution formula #172

Merged

Conversation

portsmouth
Copy link
Contributor

@portsmouth portsmouth commented Apr 15, 2024

Essentially, an improved version of the Standard Surface roughening formula. We need to suggest some approximation, as implementations need to account for this somehow. As discussed on Slack, thinking about the roughening as a convolution suggests an "as simple as possible" physically-based formula for the effect. I assume this is closer to the ground truth than the ad-hoc Standard-Surface formula, but verifying that would require more work.

See the interactive graphs here for the difference between the Standard Surface (green) and convolution (blue) formulas: https://www.desmos.com/calculator/tvvzlmjqin

image

@portsmouth portsmouth changed the title Add suggested physically-based approximate formula for coat roughening. Coat roughening formula Apr 15, 2024
@portsmouth portsmouth changed the title Coat roughening formula Coat roughening proposed formula Apr 15, 2024
@portsmouth portsmouth changed the title Coat roughening proposed formula Coat roughening convolution formula Apr 15, 2024
@portsmouth
Copy link
Contributor Author

To be more clear, we should cross reference this formula in the dielectric and metal sections, to remind the reader to apply the roughening if the coat is present.

Copy link
Contributor

@AdrienHerubel AdrienHerubel left a comment

Choose a reason for hiding this comment

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

The phenomenon is already implemented in the MaterialX implementation using the standard surface formula, so describing it in the spec is important. We need to validate the new proposed formula and add a MaterialX implementation.

@fpsunflower
Copy link

Using (rb^4+rc^4)^(1/4) has to be clamped at 1.0 for implementations that have a limit (as @peterkutz mentioned, for example because of tabulated energy compensation tables). So you get something like this:

formula_a

If that discontinuity bothers you, the following formula is almost the same but is smooth everywhere:

(1-(1-rb^4)(1-rc^4))^(1/4)

formula_b

I think the current wording in the spec is totally fine, no need to change it. Just pointing out an alternative that would be very close.

Depending on where we end up with respect to anisotropy for coat vs base, we may need to generalize the formula to operate on the 2x2 matrix of alpha values (haven't thought about exactly what the math should be, but its probably a similar kind of formula that would come back to @portsmouth's one in the isotropic case).

@peterkutz
Copy link
Contributor

peterkutz commented Apr 17, 2024

Nice suggestion and visualization @fpsunflower! It's nice that your modified formula produces very similar values across most of the domain while avoiding the discontinuity. It also meets all of the other requirements such as producing an output at least as large as the inputs.

In case it's easier to read or reason about, here's a different way of writing it:

(rb^4 + rc^4 - rb^4 * rc^4)^(1/4)

$r'_B = ( r^4_B + r^4_C - r^4_B r^4_C)^\frac{1}{4}$

@portsmouth
Copy link
Contributor Author

portsmouth commented Apr 17, 2024

Thanks Chris, that's a nice formula. I plotted it here (where green is mine, dashed green is yours, blue is Standard Surface, red is max):

https://www.desmos.com/calculator/svkp9j7kwc

image

Your formula is very close to the original one, unless the base roughness is quite large. I think it would be fine to re-word the section to say, the convolution would give my formula, but we suggest your modification (or Peter's form of it) to ensure the curve is close to that but smooth between the [0,1] endpoints.

@portsmouth
Copy link
Contributor Author

portsmouth commented Apr 17, 2024

In case it's easier to read or reason about, here's a different way of writing it:

$$ \left( r^4_\mathrm{B} + r^4_\mathrm{C} - r^4_\mathrm{B} \, r^4_\mathrm{C} \right)^\frac{1}{4} $$

That has a somewhat nice similarity to the Standard Surface formula, which is the same but just removing the fourth powers:

$$ r_\mathrm{B} + r_\mathrm{C} - r_\mathrm{B} \, r_\mathrm{C} $$

@portsmouth
Copy link
Contributor Author

portsmouth commented Apr 18, 2024

Depending on where we end up with respect to anisotropy for coat vs base, we may need to generalize the formula to operate on the 2x2 matrix of alpha values (haven't thought about exactly what the math should be, but its probably a similar kind of formula that would come back to @portsmouth's one in the isotropic case).

I posted a possible sketch of the math for that on Slack:

One thought for a more principled approach is to approximate the NDFs of coat and base as 2d Gaussians in slope space, say Gc, Gb (with principle axes and std-devs given by the alphas and T, B for coat/base), which is the Beckmann model, then the roughened base could be taken to have the alphas of the Gaussian NDF given by their convolution Gc * Gb which is found (it turns out) by just adding the covariance matrices of coat and base. That would also take into account the anisotropy of coat or base.

Though I think that it is too much math for the spec. Ideally someone should investigate whether a scheme like that works, and verify with some Monte Carlo simulations. (Then we could just reference that).

Incidentally, the assumption that the NDF of the observed base lobe seen through a rough coat is given by convolving the NDFs of coat and base, is just a guess (loosely based on the Belcour paper where he does similar approximations with the variance of lobes, to account for layering, which possibly we should reference). The reality will obviously be more complex (the full BRDF of coat-on-base involving accounting for all possible scattering paths within the coat, and a term with the two NDFs being integrated together is just one lowest-order part of that).

Technically OpenPBR doesn't say this convolution formula must be used though, it's just a suggestion (the ground truth would be whatever happens in the full light transport).

@portsmouth
Copy link
Contributor Author

portsmouth commented Apr 19, 2024

The reality will obviously be more complex (the full BRDF of coat-on-base involving accounting for all possible scattering paths within the coat, and a term with the two NDFs being integrated together is just one lowest-order part of that.

Actually the NDF of the coat should presumably count more highly in the mix, since in the lowest-order path the ray has to pass through the coat boundary twice, and bounces only once on the base surface..

So a more realistic formula would involve doubling the variance contribution of the coat, i.e.

$$ \left( r^4_\mathrm{B} + 2r^4_\mathrm{C} \right)^\frac{1}{4} $$

Then the base roughness saturates to 1 at:

$$ r^*_\mathrm{C} = \Bigl( \frac{1 - r^4_B}{2} \Bigr)^\frac{1}{4} $$

e.g. a smooth base becomes maximally rough at $r^*_\mathrm{C}$ = 0.840 rather than when the coat is maximal roughness.

Below green is with the coat counting twice, and red the original formula. If you do this, I'm not sure it makes sense to try to smoothen out the discontinuity.

https://www.desmos.com/calculator/qizje7v8bx

image

Possibly you could still regard Chris's formula (dashed red) as a reasonable, smooth bounded approximation of that as well. This is all loose enough that perhaps it doesn't matter exactly what formula we recommend, if it has nice visual behavior reasonably similar to the expected ground truth (and as a bonus has some kind of theoretical rationalization).

It would probably help to see some renders with varying base and coat roughness, and the effect of the formula with the clamp in comparison to the smooth formula. (I'm expecting barely perceptible difference, but will see).

@portsmouth
Copy link
Contributor Author

Re-wording in 6c66e3b

image

@portsmouth
Copy link
Contributor Author

The MaterialX implementation of this is needed. (Just need to decide which formula..).

@portsmouth
Copy link
Contributor Author

portsmouth commented Apr 30, 2024

I tried rendering a model with a shiny metal base, covered by a coat with a textured roughness (driven by noise). The renders below show the result with both the formulas suggested above. This suggests at least that the difference between them is pretty negligible (so I’d propose to just use the more physically motivated “accurate” formula with the clamp):

Approx formula Accurate formula
openpbr_approx_coat_roughening openpbr_accurate_coat_roughening

@portsmouth
Copy link
Contributor Author

I added a simple MaterialX implementation of the more realistic coat roughening formula as in Equation 55 from the spec shown above, replacing the old Standard Surface one.

The clamped version seems fine to use, and more principled than the approximate version of Equation 56 (which I removed from the spec text as well).

@portsmouth
Copy link
Contributor Author

portsmouth commented May 11, 2024

Current PR adds roughening as a short sub-section of the coat discussion:

image

image

@portsmouth
Copy link
Contributor Author

Ready to merge.

Copy link
Contributor

@virtualzavie virtualzavie left a comment

Choose a reason for hiding this comment

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

LGTM


### Roughening

If the coat is rough, the microfacet BSDF lobes of the underlying base substrate (metal and dielectric) are also effectively roughened. If this is not otherwise accounted for by the light transport, it can instead be reasonably approximated by directly altering the NDF of the base BSDFs.
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

# Conflicts:
#	index.html
#	reference/open_pbr_surface.mtlx
@portsmouth
Copy link
Contributor Author

@virtualzavie suggested offline that this could benefit from a more detailed discussion of the derivation of the suggested formula, which I agree with. But I'd suggest for 1.0 we just get the existing PR in with the formula and the implementation, as adding a more detailed description is presumably allowable after "code complete".

@jstone-lucasfilm
Copy link
Member

This change looks good to me, @portsmouth, and I'm fine to merge this work once we've validated rendering of the updated implementation in MaterialX 1.39.

This changelist fixes a minor typo in the new implementation, updating a connection from "interfacename" to "nodename".

Signed-off-by: Jonathan Stone <jstone@lucasfilm.com>
This changelist updates a connection from "nodename" to "interfacename" for correctness.

Signed-off-by: Jonathan Stone <jstone@lucasfilm.com>
@jstone-lucasfilm
Copy link
Member

@portsmouth I addressed two very minor typos in the reference implementation, and I'm now getting successful renders of coated materials in the MaterialX Viewer:

OpenPBR_Carpaint

One unusual behavior that I should call out, though, is that neither specular_roughness_anisotropy nor coat_roughness_anisotropy seem to have any visual effect on our open_pbr_carpaint.mtlx example.

If I set coat_weight to zero, then specular_roughness_anisotropy has the expected effect on the shape of specular highlights, but when coat_weight is set to one, neither of these anisotropy controls have any noticeable effect on the rendered output.

Could this be a bug in our reference implementation, or is this behavior actually expected given the latest OpenPBR specification?

@portsmouth
Copy link
Contributor Author

portsmouth commented May 14, 2024

Thanks for the typo fixes.

If I load the "car-paint" material, and dial the coat anisotropy, then indeed the anisotropy doesn't seem to show up (i.e. the sun reflection doesn't smear). But this just seems to be a case of what Zap was complaining about, i.e. when the coat roughness is zero, coat anisotropy doesn't do anything.. (i.e. the roughness stays at zero, in both directions). That is by design, yes.

Also... in this material, the specular and coat IORs are equal (!). So the specular lobe is not showing up at all. So that accounts for the specular anisotropy not showing up either.

If you make the roughnesses non-zero and the IORs differ, then you will see the effect of the anisotropy in both lobes.

If you think that's unintuitive, we can discuss in the meeting (but it's the behavior of the spec, correctly implemented, I think).

So anyway, I think the PR is good to go, as we've verified it now in MaterialX.

@jstone-lucasfilm
Copy link
Member

Got it, and I'm fine to merge this latest pull request, as the specification and reference implementation are aligned. It's arguable that we can do a better job of making these behaviors intuitive, e.g. making sure there's always a visual impact associated with moving the anisotropy slider, but I'll leave that judgement to artists with more production experience.

@portsmouth
Copy link
Contributor Author

portsmouth commented May 14, 2024

Here is an example of the coat roughening behaving correctly (IMO), including anisotropy.

The coat and spec IORs are different. The coat is slightly rougher than the spec. Both are highly anisotropic.

Without the coat (just the spec lobe, tight stretched highlight):

image

With the coat (highlight of coat and spec superimposed, coat highlight much broader):

image

This does show one defect of the roughening formula, in that the anisotropy of the base lobe is not affected. In fact, an isotropic rough coat should cause the base lobe to become less anisotropic. But we opted to neglect such anisotropy effects for now. (Later we could try to improve that though, for sure).

@jstone-lucasfilm jstone-lucasfilm merged commit e7ec2c7 into AcademySoftwareFoundation:main May 14, 2024
1 check passed
@portsmouth
Copy link
Contributor Author

portsmouth commented May 14, 2024

Another thing to note, is that the weird brightening around the edge of the lower image above, is the "spurious TIR artifact" -- which shows up here because the coat IOR is higher than the spec.

So the spec lobe "thinks" that the rays are incoming from a higher IOR, so TIR occurs. Which is false, as physically the rays should actually pass through the coat and bend, eliminating that TIR.

The PR #166 proposes a discussion of this with a suggested practical solution. I will attempt to fix this up in time for the meeting, to incorporate Peter's suggestions.

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.

None yet

6 participants