-
Notifications
You must be signed in to change notification settings - Fork 375
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
Implement OpenPBR's sheen BRDF #1819
Implement OpenPBR's sheen BRDF #1819
Conversation
src/testrender/shading.cpp
Outdated
const float len2 = dot(wiOriginal, wiOriginal); | ||
const float det = aInv * aInv; | ||
const float jacobian = det / (len2 * len2); | ||
pdf = jacobian * std::max(wiOriginal.z, 0.0f) * float(M_1_PI); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason not to call eval(wo, wi) here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It just seemed redundant to transform/untransform the vector and recompute the ltc factors several times. Since its just a few lines of code, it seemed easier to just do the small amount of work again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks very good!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better to reference the MaterialX implementation for now*, since the WebGL sandbox was just a test and may go away or move.
- I'm planning to publish something about the fitting methodology later once I've settled on a final implementation, at which point this can be the canonical reference.
src/testrender/shading.cpp
Outdated
float pdf; | ||
Sampling::sample_cosine_hemisphere(Vec3(0, 0, 1), rx, ry, wi, pdf); | ||
|
||
wi = Vec3(wi.x - wi.z * bInv, wi.y, wi.z * aInv).normalize(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice idea of scaling up by aInv here. I'll see if I can borrow that for the MaterialX implementation.
src/testrender/shading.cpp
Outdated
// TODO: is there a cheaper way to get the jacobian here? | ||
const Vec3 wiOriginal(aInv * wi.x + bInv * wi.z, aInv * wi.y, wi.z); | ||
const float len2 = dot(wiOriginal, wiOriginal); | ||
const float det = aInv * aInv; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's what I'm doing, using the length of L / wi instead (before normalisation): https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/libraries/pbrlib/genglsl/lib/mx_microfacet_sheen.glsl#L169-L183
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice - I'll go through this and try to consolidate the logic. Didn't realize yours was added to the MaterialX side already.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think if you retain the aInv
multiplication of wi
but otherwise follow what I'm doing then you'll end up with square(len2 / aInv)
for the jacobian, which should still be a little bit cheaper than what you have here.
NB: I may end up changing to fitting to work with the non-inverse matrix coefficients (TBD, but it's possible that they could be more linear), but that shouldn't change the implementation all that much.
src/testrender/shading.cpp
Outdated
const float NdotV = clamp(N.dot(V), 0.0f, 1.0f); | ||
const Vec3 ltc = fetch_ltc(NdotV); | ||
|
||
const Vec3 T1 = (V - N * NdotV).normalize(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's a danger of generating NaNs here if V = N (I've certainly hit this in other LTC-related code in production). I didn't bother to handle that in the WebGL sandbox, but I'm doing so in the MaterialX version:
https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/libraries/pbrlib/genglsl/lib/mx_microfacet_sheen.glsl#L117-L133
Things get a little simpler if vectors are already in local space:
https://github.com/shill-lucasfilm/OpenPBR-viewer/blob/main/glsl/fuzz_brdf.glsl#L30-L37
I've made the suggested changes. I also fixed the TangentFrame class so it could be used here and replace the ad-hoc one (which wasn't robust as you pointed out). This did invalidate all the reference images with small noise differences, but I think its worth the cleanup. |
@jstone-lucasfilm Do you have any preference for the keyword argument used to pick the new sheen variant? As I've implemented it so far, using the new sheen would look like this:
|
@fpsunflower This is a great question, and my initial thinking is that the MaterialX node would have a Since we have a great set of subject-matter experts on the thread, I'd be very interested in what your thoughts are, and I'm happy to defer to the group. |
@@ -16,7 +16,7 @@ sheen | |||
) | |||
{ | |||
if (N.x < 0) | |||
Ci = sheen_bsdf (N, Cs, roughness * 2, "mode", 1); | |||
Ci = sheen_bsdf (N, Cs, sqrt(roughness), "mode", 1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure this is correct? I thought the external parameterisation was roughness (alpha in the Zeltner implementation) and that only the underlying SGGX model uses sigma. The table itself (and my analytic refit) should be parameterised by alpha, not sigma.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When looking at the wedges of both sheen models, the sqrt seemed to bring them closer together. This part is just for the testsuite where its handy to see both models at once on the same sphere.
The materialx closure just exposes the "raw" parameter in any case (just like GGX).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, makes sense.
src/testrender/shading.cpp
Outdated
const float y = roughness; | ||
const float A = ((2.58126f * x + 0.813703f * y) * y) / (1.0f + 0.310327f * x * x + 2.60994f * x * y); | ||
const float B = sqrtf(1.0f - x) * (y - 1.0f) * y * y * y / (0.0000254053f + 1.71228f * x - 1.71506f * x * y + 1.34174f * y * y); | ||
const float invs = (0.0379424f + y * (1.32227f + y)) / (y * (0.0206607f + 1.58491f * y)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea! I had originally tried to fit 1/s directly but it made the optimisation unstable. I don't know why I didn't follow through on doing it as a post step but I'll bring this over to a future MaterialX GLSL version, assuming I don't replace the Gaussian fitting (I might try a logistic).
src/testrender/sampling.h
Outdated
u = v.cross(w); | ||
// fallsback to an arbitrary basis if the tangent is 0 or colinear with n | ||
static TangentFrame from_normal_and_tangent(const Vec3& n, const Vec3& t) { | ||
Vec3 y = n.cross(t); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity, was there a reason you went with computing y here rather than x (via t - n*dot(n, t))? Numerical stability or performance?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No real reason actually. Now that I look at both in the light of day, they should be equivalent, and the dot product way might be a few less operations (especially considering the dot product is needed for the LTC lookup anyway).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, fair enough. I just wanted to check that I wasn't missing anything.
@fpsunflower @shill-lucasfilm I've had a chance to write a pull request for the AcademySoftwareFoundation/MaterialX#1880 Following additional discussion with Stephen, our current thinking is that the two modes should be named Let me know how this sounds to you, and I'm very open to making adjustments based on feedback from this group. |
Sounds good to me. |
…osure Signed-off-by: Chris Kulla <[email protected]>
Signed-off-by: Chris Kulla <[email protected]>
…roughness Signed-off-by: Chris Kulla <[email protected]>
Signed-off-by: Chris Kulla <[email protected]>
Signed-off-by: Chris Kulla <[email protected]>
…DF implementations Signed-off-by: Chris Kulla <[email protected]>
Signed-off-by: Chris Kulla <[email protected]>
Signed-off-by: Chris Kulla <[email protected]>
…ngent vector Signed-off-by: Chris Kulla <[email protected]>
Signed-off-by: Chris Kulla <[email protected]>
Signed-off-by: Chris Kulla <[email protected]>
Signed-off-by: Chris Kulla <[email protected]>
e42de16
to
58ec48b
Compare
Signed-off-by: Chris Kulla <[email protected]>
Signed-off-by: Chris Kulla <[email protected]>
Finally got the testsuite passing. Just needed a handful of extra reference images and had to clamp the roughness to a small value to avoid generating NaNs for the R term. I will squash and merge since I don't believe there are any outstanding comments. The two remaining failures also happen in main and are udim related -- I am guessing that is coming from the latest OIIO. |
65024b0
into
AcademySoftwareFoundation:main
@@ -1529,7 +1529,7 @@ struct ZeltnerBurleySheen final : public BSDF, MxSheenParams { | |||
// for the implementation in MaterialX: | |||
// https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/libraries/pbrlib/genglsl/lib/mx_microfacet_sheen.glsl | |||
const float x = NdotV; | |||
const float y = roughness; | |||
const float y = std::max(roughness, 1e-3f); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ahh yes, I should've caught this, since I hit the same problem in the MaterialX GLSL implementation. I decided to clamp to the minimum used by Zeltner et. al for their table:
https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/libraries/pbrlib/genglsl/mx_sheen_bsdf.glsl#L30
Note: I may come up with a new fit against a higher-resolution table in the future, at which point the minimum roughness may be lower.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops, I left this comment as pending earlier. Oh well!
* fix: reparameter string needed to be ustringhash (AcademySoftwareFoundation#1841) * fix: make backfacing shadeop indicate backfacing shader-global is needed (AcademySoftwareFoundation#1827) * Fix the subsurface_bssrdf parameters (AcademySoftwareFoundation#1823) * Implement OpenPBR's sheen BRDF in testrender (AcademySoftwareFoundation#1819) * cleanup: OIIO deprecations (AcademySoftwareFoundation#1834) See merge request spi/dev/3rd-party/osl-feedstock!61
Description
This PR adds an implementation of the Zeltner-Burley sheen closure which is the new default in OpenPBR. I've exposed it via a new (optional) mode keyword arg in the existing closure, though it would be easy to switch the interface if needed.
The implementation is based on a fit done by Stephen Hill here: https://blog.selfshadow.com/sheen/ltc_sheen.html
Tests
I've modified the existing sheen render unit tests to use the new closure on half of the sphere so we can keep testing old and new modes.
Checklist:
already run clang-format v17 before submitting, I definitely will look at
the CI test that runs clang-format and fix anything that it highlights as
being nonconforming.