WebGPU: Support for custom materials; simple glTF-compliant material#717
Conversation
|
Thanks for this - with the last couple PRs there will be quite a few conflicts here. I'm not sure how easy it is but if possible it would be nice to break this up into smaller chunks for review if possible. A few things on my mind as we move into BRDF implementation - there are definitely some improvements that can be made to the handling of BRDF blending from the previous implementation. GLTF, which is at least the base model I'd like to follow here, specifies the BRDF blending hierarchy here and some of the extensions like irridescence specify their terms internally, as well. It's been a long time since I've looked at the material code but it might be a good opportunity to look at that and try to match some of the gltf notation for consistency. I'm wondering if it makes sense to try to get a lot of the material qualities and BRDFs passing some of the more complicated tests (like the furnace test) before moving onto things like next event sampling and bi directional path tracing? Something to think about. These aren't things I'm expected in a first PR, to be clear. But just things that are on my mind and to consider to what would come next. |
|
Decided to split this into multiple PRs:
Let me know if you think it could/should be done differently. Once (1) merges, other PRs will be unblocked. This PR's focus will be about actual BRDF implementation. Do you think its better to build up the implementation over multiple iterations? First, base GLTF spec, then adding extensions? Could you expand on testing? Do you usually use some other implementation (blender?) as ground truth? I've read about furnace test but was unable to find them in examples. Does it need to be constructed manually? |
This seems good to me. I think this is a good opportunity to build this up again and validate it against a known specification while developing.
The viewer test demo (which you've already done the leg work for migrating here) is probably the best way to test these things. For some background, the viewer test page allows for loading and rendering the litany of gltf-sample-assets models which should cover the broad set of gltf features. For comparisons, there's an You'll notice that there is a dropdown with other comparison options (babylon, stellar etc - some of which are path tracers as well) that are currently broken. The This set of screenshots is good litmus test for ensuring we're covering the right features and should be a good way to catch any inconsistencies. A good long term goal would be to see if we can get our screenshots updated in the Khronos gltf-render-fidelity repo, as well, but one step at a time 😁 |
|
I forgot to mention that the material database demo can also be useful for testing against materials and Blender screenshots from physicallybased.info, which can be good for evaluating against something like cycles. |
…gltf-compliant form
…ee-gpu-pathtracer into webgpu-pbr-materials
…ecify to calculate.
I understand that and tried to check fully metallic and rough sphere by itself and it still showed some loss.
Yes, sorry for not attaching an image to showcase them. First noticed them on a model, as very bright white highlights. It seems that this was a problem with precision of complex trigonometric formulas. It took simplifying all of them to get rid of those bright highlights. Went back to webgl implementation to check if there were any problems. Turning off nan checks turns a lot of the same pixels that were white in webgpu into black (presumably NaNs). My guess is that trig is handled a little differently in webgl vs webgpu and causes different behavior. Initially I though filteredRoughness may help here, but it should not, so it could wait for another PR imo. Also: saved turquin compensation texture into an image file to load. Since its static for a material, we can package the default texture to not calculate it on the device. Let me know if you would like that to be handled differently.
To be clear: Do you want other gltf extensions handled in this PR? Like iridescence or clearcoat? |
There was a problem hiding this comment.
To be clear: Do you want other gltf extensions handled in this PR? Like iridescence or clearcoat?
Either way! Lets handle these in subsequent PRs since this is just about ready. Very excited to see this 😁
One thing I will note is that I am seeing these artifacts when toggling between wavefront and megakernel path tracers. Do you see it on your machine?
--
And just to make a list of remaining PBR features to tackle - is there anything missing here?
- Clearcoat support
- Sheen support
- Transmission support
- Iridescence
- Volume attenuation
- Opacity (handled in #751)
- Alpha cutout
- Anisotropy
- Dielectric / Transmission multiscatter compensation
- Material sided-ness
| import { scatterRecordStruct } from '../nodes/structs.wgsl'; | ||
| import { pcgRand } from '../nodes/random.wgsl'; | ||
| import { ComputeKernel } from '../compute/ComputeKernel'; | ||
| import turquinMetal from '../../textures/turquinMetal.png'; |
There was a problem hiding this comment.
This isn't a method we can use for importing textures - it may work for vite but it won't work across the board with other bundlers. A typical method is to use a data url or to use something like this which is pretty reliable across bundlers I believe:
const TURQUIN_URL = new URL( '../../textures/turquinMetal.png', import.meta.url ).toString();| export const ggxDistributionFunc = wgslFn( /* wgsl */ ` | ||
| fn ggxDistribution( NdotH: f32, alpha: f32 ) -> f32 { | ||
|
|
||
| let a2 = max( alpha * alpha, EPSILON ); | ||
| let denom = NdotH * NdotH * ( a2 - 1 ) + 1; | ||
|
|
||
| return ( a2 / ( PI * denom * denom ) ); | ||
|
|
||
| } | ||
| `, ); |
There was a problem hiding this comment.
Do we need to add "constants" to the dependencies of this function?
|
|
||
| } else if ( r <= cdf.y ) { // specular | ||
|
|
||
| wh = ggxDirection( wo, vec2( alpha ), pcgRand2() ); |
There was a problem hiding this comment.
We should add "pcgRand2" to the dependencies here, as well.
| if ( scatterRec.pdf <= 0.0 || any( scatterRec.color != scatterRec.color ) ) { | ||
|
|
||
| return; | ||
|
|
||
| } |
There was a problem hiding this comment.
I'm thinking this should be "break" rather than "return"? Otherwise we don't increment that we took a sample or save any accumulated emission.
|
|
||
| } | ||
|
|
||
| resultColor += vec4f( throughputColor * surface.emission, 0.0 ); |
There was a problem hiding this comment.
I'm also thinking this should be moved before the above "invalid scatter" check. Even if the path is ultimately terminated we should still accumulate the emission contribution.
There was a problem hiding this comment.
Small catch that may be hard to find later - this should be "clearcoatNormal" rather than "normal"
Yes. That is weird, looks like there are some problems with low res samples and/or problem with blending them. Looking into it. |
It looks like this issue starts to happen in 8fed465. I suspect it's because the turquin texture loads asynchronously but is not awaited, so it's being used before it's fully initialized, resulting in some invalid calculations. Why don't we just load the texture once in an awaited global variable so we can ensure it's available and then we can revisit the swappable material design later on to be more resilient to these cases. |
Did that. While trying to test with a debug material I noticed that the material change for a pathtracer does not work properly. Fixed that. I think it could be helpful to expose an example with editable debug texture. So you could inspect different values. Maybe even inside the viewerTest example? Another thing I noticed is normal map seems to have less effect in webgpu. |
|
Normal map issue turned out to be missing tangent attribute on the mesh. Added an option to generate common attributes as in StaticGeometryGenerator. |
Thin materials would also need special handling afaik |
| import { ComputeKernel } from '../compute/ComputeKernel'; | ||
|
|
||
| const TURQUIN_METAL_URL = new URL( '../../textures/turquinMetal.png', import.meta.url ).toString(); | ||
| const TURQUIN_METAL_TEXTURE = await new TextureLoader().load( TURQUIN_METAL_URL ); |
There was a problem hiding this comment.
"load" returns a Texture instance so await won't do anything here. We should use "loadAsync" if we want to await a promise.
What do you mean by "inspect different values"? Do you mean like hovering over a pixel to see intermediate calculations? Either way we will need a better few test scenes. We can probably start converting some of the other examples to use WebGPUPathtracer if it's helpful, as well. |
I mean having some debug overlays that would display intermediate values as color. This is a limited view that would allow to see only one value at a time.
That would be very useful. Do you know how that could be achieved? Only thing I can think of is writing intermediate values into storage textures but there is only so many texture binding slots. Another way to trace calculations is to debug shaders in native tools like RenderDoc or just Metal Debugger. So maybe we should not resort to very complex means of tracing if such debuggers useful enough. I tried debugging with RenderDoc myself but faced problems emitting hlsl shaders without symbol renaming. Although even without that translated shaders are recognizable and debuggable. And buffer contents are inspectable which is especially useful in wavefront case. Note: useful flags for debugging in chrome: https://dawn.googlesource.com/dawn/+/HEAD/docs/dawn/debugging.md |
Yeah this or storage buffers more or less. The good thing is that we can use any extra slots allowed by our hardware since this is for debugging only rather than being limited by the default WebGPU limitations.
The new three.js inspector may have utilities for stuff like this but I'm less familiar with it and it's unclear how well it works outside the traditional rendering paths. -- And again - very nice work on the PR. I'll go ahead and merge this along with some of the other opacity-related ones. |
Yes, I've also noticed that when working on clearcoat. The problem was that wavefront path tracer did not have a proper guard against scatter records with pdf <= 0. Fixed in #758 |




Transmissive materials need support from the light transport algorithm and will not work correctly for now. I think they can be addressed later.
@gkjohnson Should I divide this PR into multiple for better readability?