-
-
Notifications
You must be signed in to change notification settings - Fork 35.4k
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
Intent to implement/contribute Enterprise PBR improvements #16977
Comments
/ping @DanielSturk - this gives context to your PRs so that people understand the overall motivation. |
Enthusiastic +1 from me! 🙂 This link is currently broken, but the |
No longer broken! |
The glTF sheen extension (KHR_materials_sheen) is nearly complete, although still welcoming feedback for a bit longer. To implement in MeshPhysicalMaterial, it would require:
The two maps use |
The glTF extensions KHR_materials_sheen and KHR_materials_transmission are now complete. Would be great if we could work toward supporting the remaining properties of those PBR features. See this article for a bit more info. |
+1 for this, in particular a KHR_materials_transmission implementation would have a massive impact on rendering physical products. i have no appreciation for how much work this is, but i would be curious @mrdoob if this is in line w/ your near-term roadmap for three.js? re: implementation, the only support i can offer is that both https://github.com/BabylonJS and https://github.com/KhronosGroup/glTF-Sample-Viewer have successfully implemented all three of the new PBR extensions - so perhaps there are methods that can be borrowed from those projects. also - the new Khronos "toy car" is an all-in-one test of a successful implementation: https://github.com/KhronosGroup/glTF-Sample-Models/tree/a35e94effc01db54f94bab34f793c960276a67fc/2.0/ToyCar |
How should this be architected? Seems to me that 1 - Render opaque to a render target (front to back) Considering that WebGL1 doesn't support multisampled render targets we can't just use this architecture for everything. WebGL1 people will see either things aliased or things not refracting. This could be the default architecture for WebGL2 though. |
The proposed suggestion only works for single layered transmission. I believe the more correct approach would be to implement an OIT method. OIT = order independent transparency, per #9977 It would then work on WebGL1 and 2. Cesium project has OIT code: https://gitlab.sensoro.com/wushijing/cesium/blob/9fd4154a2eb3696f1c4c053ccf3a9b8354d683d4/Source/Scene/OIT.js. Clara.io has also implemented OIT. https://cesium.com/blog/2014/03/14/weighted-blended-order-independent-transparency/ |
I'm not sure OIT solves refraction. |
I might be mis-reading the documentation, but I believe that the
Is this example from the GLTF Sample Viewer (which also uses WebGL) relevant to the challenge here? Transmission set from extension https://github.com/KhronosGroup/glTF-Sample-Viewer/blob/2e6f9f1cfef04239cc8c8c403a5c49a242b1dc3f/src/material.js
then loaded into PBR shader https://github.com/KhronosGroup/glTF-Sample-Viewer/blob/2e6f9f1cfef04239cc8c8c403a5c49a242b1dc3f/src/shaders/pbr.frag
index of refraction is set by @MiiBond - any ideas? |
Hello. Yeah, OIT is about rendering transparent polys in the correct order (or approximately correct order in some cases). The most important requirement for the transmission extension is rendering transparency with the correct blending and refraction. Transmissive surfaces can both absorb and reflect light so it's not really possible to use traditional OIT methods as two different blend modes are needed. Also, refraction requires sampling from an already-rendered target.
This is very straightforward to do so it's what we suggested as the bare-bones approach to supporting the transmission extension. It's also, notably, what SketchFab seems to do for refraction. They also only support refraction of opaque objects. If you want to get more complicated, you can combined some OIT techniques. I used depth-peeling, combined with MRT to render out a g-buffer (of sorts) and then composite them back together to render multiple layers of transparency with correct PBR blending. Here's an example scene: |
@MiiBond Super helpful! Many thanks! 🙏 |
Lately I have been studying PBR and locally tried transmission support as mentioned above first rendering opaque objects to a render target and then rendering a whole scene with the render target for refraction. I generate mipmaps of the render target every frame and use textureLod for rough transmission. From the screenshot, the basic concept seems fine. |
@takahirox That is looking great! |
And other devs seem to work on it, too... #21000 (comment) |
@takahirox Would you like to do a PR with what you have? |
@takahirox Sure, please share your source, I think I can learn much from that to improve mine. |
As I wrote I think @MiiBond's approach is good
What I want to discuss about API and implementation are... 1. The reason why only opaque objects First I would like to build a consensus why we render only opaque objects to a render target in a first pass, not all objects. My understanding is it is good balance between performance and quality. Three.js is primarily designed as real-time 3d engine. We should adopt an efficient approach even if we sacrifice a perfectness.
So I think it's good for us to go with this approach and we can revisit later if we get a lot of requests for better transparency. 2. Who sets up the render target? User or Renderer? We need to add a new property for the opaque objects render target to Who sets up the render target? 2-a. User renderTarget = new WebGLRenderTarget(1024, 1024, {
generateMipmaps: true,
minFilter: LinearMipmapLinearFilter,
magFilter: Nearest,
wrapS: ClampToEdgeWrapping,
wrapT: ClampToEdgeWrapping
});
const render = () => {
scene.traverse(obj => {
if (obj.material) {
if (object.material.transparent) {
obj.material.visible = false;
}
if (object.material.transmission) {
obj.material.transmissionSamplerMap = null;
}
}
});
renderer.setRenderTarget(renderTarget);
renderer.render(scene, camera);
renderer.setRenderTarget(null);
scene.traverse(obj => {
if (obj.material) {
if (object.material.transmission) {
obj.material.transmissionSamplerMap = renderTarget.texture;
}
obj.material.visible = true;
}
});
renderer.render(scene, camera);
}; Pros: Renderer won't be complex because we don't need to add the first opaque render pass. Cons: User code will be complex. If users don't correctly set up they don't get expected result. 2-b. Renderer Renderer sets up the render target in Pros: No user code change. And even if we optimize or improve the transmission later users automatically get the benefit. Cons: Renderer can be a bit complex. 2-c. Hybrid If I prefer 2-b or 2-c. 3. How should Renderer know whether it needss to set up the render target? If we adopt 2-b or 2-c, renderer needs to know whether it needs to set up the render target or not. How should renderer know that? 3-a. Add a new renderer property and user sets it true renderer.transmission = true; 3-b. Renderer automatically detects it in let needsTransmissionSamplerMap = false;
scene.traverse(obj => {
if (obj.material && obj.material.isMeshPhysicalMaterial && obt.transmission) {
needsTransmissionSamplerMap = true;
}
}); Perhaps 3-a is good? 4. How should This is what I couldn't deeply think of and look into yet.
Mine is based on glTF sampler viewer, too. I read through your code and it looks very similar to mine. |
@takahirox the reasoning of just rendering the opaque layer is that rendering all transparency correctly requires layering up all the transparent objects in the scene using something like depth peeling or other techniques. This can be expensive and it's hard to know how many layers to render. There are in-between approximations, of course, as well (like just rendering one layer of transparency behind the main one). |
👍
👍
We could add a new https://github.com/mrdoob/three.js/blob/dev/src/renderers/webgl/WebGLRenderLists.js#L61 And then this function could be like this instead: function push( object, geometry, material, groupOrder, z, group ) {
const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group );
if ( material.transparent === true ) {
if ( material.transmissive > 0.0 ) {
transmissive.push( renderItem );
} else {
transparent.push( renderItem );
}
} else {
opaque.push( renderItem );
}
} We can then:
Something like this here: const opaqueObjects = currentRenderList.opaque;
const transparentObjects = currentRenderList.transparent;
const transmissiveObjects = currentRenderList.transmissive;
if ( opaqueObjects.length > 0 ) renderObjects( opaqueObjects, scene, camera );
if ( transmissiveObjects.length > 0 ) {
this.setRenderTarget( transmissionSamplerMap );
renderObjects( opaqueObjects, scene, camera );
renderObjects( transmissiveObjects, scene, camera );
}
if ( transparentObjects.length > 0 ) renderObjects( transparentObjects, scene, camera );
If anything, it's a good start I think? |
Yes, I like this approach! Regarding how transmission mixes with When we start adding refraction on top of this, things will get a little more interesting, since then the transmission rays won't stay in the view frustum anymore. It's basically the same error as how we don't show reflections of other objects in the scene, which I think is fine for real-time. However, we'll want to be careful not to introduce noticeable artifacts as the refracted rays pass the boundary of the |
@elalish I think with I think @takahirox is referring to how the IBL mixes with transmission, but I'm not sure. |
@mrdoob I suppose that's right, though it brings up an interesting question: what is the relationship between IBL and background? We see the IBL (not the background) when light is reflected from a surface, so it seems odd to see the background instead of the IBL when refracting through a surface. And of course a background is generally just a screen-toned image, not really a source of light like an IBL is, so it may be difficult to fit it into any linear rendering equations. Still, from an artistic (rather than physics) point of view, I'd guess having transmission sample only the background and not the IBL would probably make the most sense. For sensible physics the IBL and background need to be the same anyway. |
(Give me some more time to reply, I'm fighting to the side effects of the 2nd vaccine now.) |
@takahirox Take your time! |
Sorry for the late response but I think I overcame the side effects.
So users don't need to be aware of
That sounds good to me. I thought adding a new objects traversal for transmission detection was costly but yeah we already have if ( material.transparent === true ) {
if ( material.transmissive > 0.0 ) {
transmissive.push( renderItem );
} else { Can we really expect that
I think backgrand needs to be rendrerred to Regarding I'm thinking of not touching |
Made a |
Sorry for the delayed response.
Do we need to add it to the material? Can we have a
I think it's up to the user/loader to set
Yep!
Sounds good! |
Good idea. I updated the PR.
I reviewed |
Most of original feature list has been implemented over the years in Please file a new issue if an additional enterprise PBR feature should be added. |
Description of the problem
To give context to a series of PRs are we are making to Three.JS I wanted to explain. We are adopting Enterprise PBR (PBR Next) as it is also the new material model that glTF is standardizing on. Enterprise PBR is a unification of advanced PBR parameters beyond just roughness and metalness.
It is specified here:
https://dassaultsystemes-technology.github.io/EnterprisePBRShadingModel/spec.md.html
The main additions to the Three.JS PBR model to adopt correctly Enterprise PBR are:
We are aiming to add these to the Node graph material system as I personally believe that is the future of material definitions (#16440). I think it is relatively easy to back-port them to PhysicalMaterial as well.
This will be useful for an advanced glTF loader in Three.JS.
I believe it is also the direction other projects are going, including Google's Filament. We are aiming to have our Three.JS contributes compatible with Filament directly when possible.
Three.js version
Browser
OS
Hardware Requirements (graphics card, VR Device, ...)
The text was updated successfully, but these errors were encountered: