Skip to content

Improved webgpu reflection example#31409

Merged
Mugen87 merged 10 commits into
devfrom
examples
Jul 23, 2025
Merged

Improved webgpu reflection example#31409
Mugen87 merged 10 commits into
devfrom
examples

Conversation

@mrdoob

@mrdoob mrdoob commented Jul 14, 2025

Copy link
Copy Markdown
Owner

Related issue: #31372

Description

Tried to make webgpu_reflection (current) simpler and prettier.

Screen.Recording.2025-07-14.at.4.47.41.PM_.mov

@mrdoob mrdoob added this to the r179 milestone Jul 14, 2025
Comment thread examples/webgpu_reflection.html Fixed
Comment thread examples/webgpu_reflection.html Fixed
Comment thread examples/webgpu_reflection.html Fixed
Comment thread examples/webgpu_reflection.html Fixed
Comment thread examples/webgpu_reflection.html Fixed
Comment thread examples/webgpu_reflection.html Fixed
mrdoob and others added 4 commits July 14, 2025 17:08
…rt, function or class

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
@mrdoob

mrdoob commented Jul 14, 2025

Copy link
Copy Markdown
Owner Author

Still far from ChatGPT's suggestion 😅

image

@Mugen87

Mugen87 commented Jul 14, 2025

Copy link
Copy Markdown
Collaborator

What I liked about the original code is that it demonstrates how to animate objects from a zero scale to the intended value since it gives the impression that things grow. This can be useful with all kinds of objects. Not just vegetation but also stuff like pillars could be animated like that.

I'm okay with all updates but I would prefer to keep the initial animation and let the tree "grow".

@mrdoob

mrdoob commented Jul 14, 2025

Copy link
Copy Markdown
Owner Author

What I liked about the original code is that it demonstrates how to animate objects from a zero scale to something the intended value since it gives the impresses that things grow. This can be useful with all kinds of objects. Not just vegetation but also stuff like pillars could be animated like that.

I removed that when I added the shadow and I noticed that the objects in the shadow didn't animate.

Seemed like a foot-gun.

@Mugen87

Mugen87 commented Jul 14, 2025

Copy link
Copy Markdown
Collaborator

I see. In this case, I'm okay with the simplification. Maybe I can come up with an approach for a different or new example that works with shadows as well...

@oosmoxiecode

Copy link
Copy Markdown
Contributor

Nice! 😸
Still have the original one up here: https://oosmoxiecode.com/archive/js_webgl/recursive_tree_cubes/
Could link to that one instead of the web archive if you like.

@Mugen87

Mugen87 commented Jul 14, 2025

Copy link
Copy Markdown
Collaborator

Could link to that one instead of the web archive if you like.

Done!

BTW. I'm a huge admirer of your WebGL demos! I was so much fun to port recursive_tree_cubes to our new TSL approach 😊 .

@Mugen87 Mugen87 mentioned this pull request Jul 14, 2025
@mrdoob

mrdoob commented Jul 15, 2025

Copy link
Copy Markdown
Owner Author

@Mugen87 As a reflection example I am actually having a hard time to understand what's going on...

Could you explain these lines?

// floor
const floorUV = uv().mul( 15 );
const floorNormalOffset = texture( floorNormal, floorUV ).xy.mul( 2 ).sub( 1 ).mul( .02 );
const reflection = reflector( { resolution: 0.5 } ); // 0.5 is half of the rendering view
reflection.target.rotateX( - Math.PI / 2 );
reflection.uvNode = reflection.uvNode.add( floorNormalOffset );
scene.add( reflection.target );
const floorMaterial = new THREE.MeshPhongNodeMaterial();
floorMaterial.colorNode = texture( floorColor, floorUV ).add( reflection );
floorMaterial.normalMap = floorNormal;
floorMaterial.normalScale.set( 0.2, - 0.2 );
const floor = new THREE.Mesh( new THREE.BoxGeometry( 50, .001, 50 ), floorMaterial );
floor.receiveShadow = true;
scene.add( floor );

@Mugen87

Mugen87 commented Jul 15, 2025

Copy link
Copy Markdown
Collaborator
const reflection = reflector( { resolution: 0.5 } ); // 0.5 is half of the rendering view 
 reflection.target.rotateX( - Math.PI / 2 ); 

These lines create the reflector and align it with the floor. Notice that the reflector itself is a node object and not a 3D object like in earlier days. That allows to modify the effect with nodes and also use the reflection in subsequent node setups.

 reflection.uvNode = reflection.uvNode.add( floorNormalOffset ); 

If you remove this line, you end up with a perfect mirror that is mixed with the floor's diffuse texture. However, since the example models the reflection of reflective tiles, a mirror-like reflection would look strange. The above line adds a distortion similar to water effects by bending the uvs with a normal offset. Since the normal offset is derived from floorNormal, the resulting reflections should look convincing.

 const floorUV = uv().mul( 15 ); 
 const floorNormalOffset = texture( floorNormal, floorUV ).xy.mul( 2 ).sub( 1 ).mul( .02 ); 

The first line scales the default uvs, then we sample the floor's normal map, transfer the sampled color into the normal range and scale it down otherwise the scattering is way too strong.

sunLight.position.set( 7, 5, 7 );
sunLight.castShadow = true;
sunLight.shadow.camera.zoom = 1.5;
sunLight.shadow.mapSize.set( 1024, 1024 );

@Mugen87 Mugen87 Jul 19, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The light shadow needs a small bias since there are self-shadowing artifacts all over the boxes.

@Mugen87 Mugen87 Jul 22, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Fixed with the latest commits. Removing the artifacts made the boxes look a bit pale so I've added a texture to give them more detail. Hope this looks good.

@Mugen87 Mugen87 marked this pull request as ready for review July 23, 2025 11:20
@Mugen87

Mugen87 commented Jul 23, 2025

Copy link
Copy Markdown
Collaborator

I'll go ahead and merge so we have this PR ready for the next release 🙌 . I'll update the screenshot after the merge.

@Mugen87 Mugen87 merged commit cc8051e into dev Jul 23, 2025
7 of 8 checks passed
@Mugen87 Mugen87 deleted the examples branch July 23, 2025 13:42
@mrdoob

mrdoob commented Jul 24, 2025

Copy link
Copy Markdown
Owner Author

I'm going to continue working on this one...

The shadow seems to be another foot gun:

Screenshot 2025-07-24 at 3 29 25 PM

Tried a similar setup in the editor using Garrett's Pathtracer:

Screenshot 2025-07-24 at 4 22 56 PM

@mrdoob

mrdoob commented Aug 1, 2025

Copy link
Copy Markdown
Owner Author

Found a hacky way to solve it...

Screen.Recording.2025-08-01.at.2.14.03.PM.mov

Old:

const floorMaterial = new THREE.MeshPhongNodeMaterial();
floorMaterial.colorNode = texture( floorColor, floorUV ).add( reflection );
floorMaterial.normalMap = floorNormal;
floorMaterial.normalScale.set( 0.2, - 0.2 );

New:

const floorMaterial = new THREE.MeshPhongNodeMaterial();
floorMaterial.colorNode = texture( floorColor, floorUV );
floorMaterial.emissiveNode = reflection.mul( 0.2 );
floorMaterial.normalMap = floorNormal;
floorMaterial.normalScale.set( 0.2, - 0.2 );

Compared to the rest of the code is not that hacky anyway... 😅

@mrdoob

mrdoob commented Aug 1, 2025

Copy link
Copy Markdown
Owner Author

@Mugen87

const reflection = reflector( { resolution: 0.5 } ); // 0.5 is half of the rendering view 
 reflection.target.rotateX( - Math.PI / 2 ); 

These lines create the reflector and align it with the floor. Notice that the reflector itself is a node object and not a 3D object like in earlier days. That allows to modify the effect with nodes and also use the reflection in subsequent node setups.

 reflection.uvNode = reflection.uvNode.add( floorNormalOffset ); 

If you remove this line, you end up with a perfect mirror that is mixed with the floor's diffuse texture. However, since the example models the reflection of reflective tiles, a mirror-like reflection would look strange. The above line adds a distortion similar to water effects by bending the uvs with a normal offset. Since the normal offset is derived from floorNormal, the resulting reflections should look convincing.

 const floorUV = uv().mul( 15 ); 
 const floorNormalOffset = texture( floorNormal, floorUV ).xy.mul( 2 ).sub( 1 ).mul( .02 ); 

The first line scales the default uvs, then we sample the floor's normal map, transfer the sampled color into the normal range and scale it down otherwise the scattering is way too strong.

What I'm finding confusing is that floorNormal.repeat doesn't seem to be reflected in TSL.

We have this:

const floorNormal = await textureLoader.loadAsync( 'textures/floors/FloorsCheckerboard_S_Normal.jpg' );
floorNormal.wrapS = THREE.RepeatWrapping;
floorNormal.wrapT = THREE.RepeatWrapping;
floorNormal.repeat.set( 15, 15 );

But then in TSL we do this again:

const floorUV = uv().mul( 15 );
const floorNormalOffset = texture( floorNormal, floorUV ).xy.mul( 2 ).sub( 1 ).mul( .02 );

/cc @sunag

@sunag

sunag commented Aug 1, 2025

Copy link
Copy Markdown
Collaborator

It would be redundant in the API to use two UV configurations in the same input, to use the native configurations, just do not add a UV as a second parameter.

For example:

// removing the floorUV will use the native uv settings / repeat + scale + channel + offset + center, etc
// you can have a more optimized uv if you want just by replacing it with uv()

const floorNormalOffset = texture( floorNormal ).xy.mul( 2 ).sub( 1 ).mul( .02 );

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.

5 participants