Skip to content

Commit 70226b4

Browse files
committed
chore: squash feature changes from feat-mesh-blend (exclude build files)
1 parent 3a0a8d9 commit 70226b4

File tree

5 files changed

+334
-13
lines changed

5 files changed

+334
-13
lines changed

examples/files.json

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,6 @@
160160
"webgl_materials_video",
161161
"webgl_materials_video_webcam",
162162
"webgl_materials_wireframe",
163-
"webgl_pmrem_cubemap",
164-
"webgl_pmrem_equirectangular",
165-
"webgl_pmrem_test",
166163
"webgl_math_obb",
167164
"webgl_math_orientation_transform",
168165
"webgl_mesh_batch",
@@ -402,7 +399,6 @@
402399
"webgpu_pmrem_cubemap",
403400
"webgpu_pmrem_equirectangular",
404401
"webgpu_pmrem_scene",
405-
"webgpu_pmrem_test",
406402
"webgpu_portal",
407403
"webgpu_postprocessing_3dlut",
408404
"webgpu_postprocessing_afterimage",
@@ -425,8 +421,8 @@
425421
"webgpu_postprocessing_sobel",
426422
"webgpu_postprocessing_ssaa",
427423
"webgpu_postprocessing_ssgi",
424+
"webgpu_postprocessing_meshblend",
428425
"webgpu_postprocessing_ssr",
429-
"webgpu_postprocessing_sss",
430426
"webgpu_postprocessing_traa",
431427
"webgpu_postprocessing_transition",
432428
"webgpu_postprocessing",
@@ -577,6 +573,7 @@
577573
],
578574
"tests": [
579575
"webgl_furnace_test",
576+
"webgl_pmrem_test",
580577
"misc_uv_tests"
581578
]
582579
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import { TempNode, MeshBasicNodeMaterial, RenderTarget, QuadMesh, Vector2 } from 'three/webgpu';
2+
import { nodeObject, vec4, vec3, float, modelPosition, modelWorldMatrix, Fn, NodeUpdateType, texture, screenUV, fract, vec2, dot, abs, sqrt, mix, saturate, If, Loop, int } from 'three/tsl';
3+
4+
//Source: https://www.jacktollenaar.top/mesh-seam-smoothing-blending#h.50wag6hqg9gh
5+
6+
const _size = /*@__PURE__*/ new Vector2();
7+
8+
class MeshBlendNode extends TempNode {
9+
10+
constructor( sceneOutputNode, sceneDepthNode, camera, scene ) {
11+
12+
super( 'vec4' );
13+
this.sceneOutputNode = sceneOutputNode;
14+
this.sceneDepthNode = sceneDepthNode;
15+
this.updateBeforeType = NodeUpdateType.FRAME;
16+
this.renderTarget = new RenderTarget( 1, 1 );
17+
this.mainCamera = camera;
18+
this.mainScene = scene;
19+
this.blendFactor = float( 1.2 );
20+
this.kernelSize = float( 5 );
21+
this.kernelRadius = float( 0.01 * this.blendFactor.value );
22+
this.depthFalloff = float( 0.001 * this.blendFactor.value );
23+
this.debugMaterial = new MeshBasicNodeMaterial();
24+
this._quadMesh = new QuadMesh( this.debugMaterial );
25+
26+
}
27+
28+
setup() {
29+
30+
const CustomHash = Fn( ( [ p ] ) => {
31+
32+
var lp = fract( p.mul( 0.3183099 ).add( 0.1 ) );
33+
lp = lp.mul( 17.0 );
34+
return fract( lp.x.mul( lp.y ).mul( lp.z ).mul( lp.x.add( lp.y ).add( lp.z ) ) );
35+
36+
} );
37+
38+
this.hashShader = Fn( () => {
39+
40+
const p = vec3( modelWorldMatrix.mul( vec3( modelPosition ) ) ).toVar();
41+
return vec4( CustomHash( p ), 0., 0., 1. );
42+
43+
} );
44+
this.hashMaterial = new MeshBasicNodeMaterial();
45+
this.hashMaterial.colorNode = this.hashShader();
46+
47+
const uv = screenUV;
48+
const FinalOutputNode = Fn( ()=>{
49+
50+
// sampling helpers (capture outside Fn so they can be used with varying UV offsets)
51+
const sampleRT = ( v ) => texture( this.renderTarget.textures[ 0 ], v );
52+
53+
const outputPassFunc1 = Fn( ( [ sceneDepthNode, uvNode, kernelSizeNode, kernelRadiusNode ] ) => {
54+
55+
const sceneDepthVar = sceneDepthNode.toVar();
56+
57+
// kernelSizeNode is expected to be a numeric node with a .value available at build time
58+
const kSize = kernelSizeNode.value || 0;
59+
60+
const seamLocation = vec2( 0., 0. ).toVar();
61+
var minDist = float( 9999999. ).toVar();
62+
63+
const objectIDColor = sampleRT( uvNode ).toVar();
64+
65+
// Use TSL Loop so the iteration becomes shader-side loops
66+
const k = int( kSize );
67+
Loop( { start: k.negate(), end: k, type: 'int', condition: '<=', name: 'x' }, ( { x } ) => {
68+
69+
Loop( { start: k.negate(), end: k, type: 'int', condition: '<=', name: 'y' }, ( { y } ) => {
70+
71+
const offset = vec2( x.toFloat(), y.toFloat() ).mul( kernelRadiusNode.mul( sceneDepthVar.r.mul( 0.3 ) ).div( float( kSize ) ) ).toVar();
72+
const SampleUV = uvNode.add( offset ).toVar();
73+
const sampledObjectIDColor = sampleRT( SampleUV ).toVar();
74+
If( sampledObjectIDColor.x.notEqual( objectIDColor.x ), () => {
75+
76+
const dist = dot( offset, offset );
77+
If( dist.lessThan( minDist ), () => {
78+
79+
minDist.assign( dist );
80+
seamLocation.assign( offset );
81+
82+
} );
83+
84+
} );
85+
86+
} );
87+
88+
} );
89+
90+
return vec4( seamLocation.x, seamLocation.y, minDist, 1. );
91+
92+
} );
93+
94+
const finalPass = Fn( ( [ sceneColor, mirroredColor, kernelRadiusNode, sceneDepth, otherDepth, depthFalloffNode, minDist ] ) => {
95+
96+
const depthDiff = abs( otherDepth.r.sub( sceneDepth.r ) );
97+
98+
const maxSearchDistance = kernelRadiusNode.div( sceneDepth.r );
99+
const weight = saturate( float( 0.5 ).sub( sqrt( minDist ).div( maxSearchDistance ) ) );
100+
const depthWeight = saturate( float( 1. ).sub( depthDiff.div( depthFalloffNode.mul( kernelRadiusNode ) ) ) );
101+
const finalWeight = weight.mul( depthWeight );
102+
103+
return mix( sceneColor, mirroredColor, finalWeight );
104+
105+
} );
106+
107+
const pass1 = outputPassFunc1(
108+
texture( this.sceneDepthNode, uv ),
109+
uv, this.kernelSize, this.kernelRadius );
110+
111+
const mirroredColor = texture( this.sceneOutputNode, uv.add( pass1.xy.mul( 2. ) ) );
112+
const otherDepth = texture( this.sceneDepthNode, uv.add( pass1.xy.mul( 2. ) ) );
113+
114+
const sceneColor = texture( this.sceneOutputNode, uv );
115+
const sceneDepth = texture( this.sceneDepthNode, uv );
116+
return finalPass( sceneColor, mirroredColor, this.kernelRadius, sceneDepth, otherDepth, this.depthFalloff, pass1.z );
117+
118+
} )();
119+
return FinalOutputNode;
120+
121+
}
122+
123+
setSize( width, height ) {
124+
125+
this.renderTarget.setSize( width, height );
126+
127+
}
128+
129+
updateBefore( frame ) {
130+
131+
const { renderer } = frame;
132+
const size = renderer.getSize( _size );
133+
this.setSize( size.width, size.height );
134+
135+
this.mainScene.overrideMaterial = this.hashMaterial;
136+
renderer.setRenderTarget( this.renderTarget );
137+
renderer.render( this.mainScene, this.mainCamera );
138+
139+
this.mainScene.overrideMaterial = null;
140+
renderer.setRenderTarget( null );
141+
this._quadMesh.render( renderer );
142+
143+
}
144+
145+
dispose() {
146+
147+
this.renderTarget.dispose();
148+
this.hashMaterial.dispose();
149+
this.debugMaterial.dispose();
150+
151+
}
152+
153+
}
154+
155+
export const meshblend = ( sceneOutputNode, sceneDepthNode, camera, scene ) => nodeObject( new MeshBlendNode( sceneOutputNode, sceneDepthNode, camera, scene ) );

examples/tags.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@
156156
"webgpu_postprocessing_sobel": [ "filter", "edge detection" ],
157157
"webgpu_postprocessing_ssaa": [ "msaa", "multisampled" ],
158158
"webgpu_postprocessing_ssgi": [ "global illumination", "indirect diffuse" ],
159+
"webgpu_postprocessing_meshblend": ["mesh blend"],
159160
"webgpu_refraction": [ "water" ],
160161
"webgpu_rtt": [ "renderTarget", "texture" ],
161162
"webgpu_rendertarget_2d-array_3d": [ "renderTarget", "2d-array", "3d" ],
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>three.js webgpu - MeshBlend</title>
5+
<meta charset="utf-8">
6+
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
7+
<link type="text/css" rel="stylesheet" href="example.css">
8+
</head>
9+
<body>
10+
11+
<div id="info">
12+
<a href="https://threejs.org/" target="_blank" rel="noopener" class="logo-link"></a>
13+
14+
<div class="title-wrapper">
15+
<a href="https://threejs.org/" target="_blank" rel="noopener">three.js</a><span>MeshBlend</span>
16+
</div>
17+
18+
<small>Mesh Blend. Screen space effect to blend objects.</small>
19+
</div>
20+
21+
<script type="importmap">
22+
{
23+
"imports": {
24+
"three": "../build/three.webgpu.js",
25+
"three/webgpu": "../build/three.webgpu.js",
26+
"three/tsl": "../build/three.tsl.js",
27+
"three/addons/": "./jsm/"
28+
}
29+
}
30+
</script>
31+
32+
<script type="module">
33+
34+
import * as THREE from 'three/webgpu';
35+
import { pass, mrt, output, normalView, diffuseColor, velocity, add, vec3, vec4, directionToColor, colorToDirection, sample, depth } from 'three/tsl';
36+
import { meshblend } from 'three/addons/tsl/display/MeshBlendNode.js';
37+
38+
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
39+
40+
import { Inspector } from 'three/addons/inspector/Inspector.js';
41+
42+
let camera, scene, renderer, postProcessing, controls, textureLoader, sphere1, sphere2, box, floor;
43+
44+
init();
45+
46+
async function init() {
47+
48+
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 100 );
49+
camera.position.set( 0, 10, 30 );
50+
51+
scene = new THREE.Scene();
52+
scene.background = new THREE.Color("#33334C");
53+
54+
renderer = new THREE.WebGPURenderer();
55+
//renderer.setPixelRatio( window.devicePixelRatio ); // probably too costly for most hardware
56+
renderer.setSize( window.innerWidth, window.innerHeight );
57+
renderer.setAnimationLoop( animate );
58+
renderer.shadowMap.enabled = true;
59+
renderer.inspector = new Inspector();
60+
document.body.appendChild( renderer.domElement );
61+
62+
//
63+
64+
controls = new OrbitControls( camera, renderer.domElement );
65+
controls.target.set( 0, 7, 0 );
66+
controls.enablePan = true;
67+
controls.minDistance = 1;
68+
controls.maxDistance = 100;
69+
controls.update();
70+
71+
var directionalLight = new THREE.DirectionalLight(0xffffff);
72+
directionalLight.position.set(1, 1, 1);
73+
scene.add(directionalLight);
74+
//
75+
76+
postProcessing = new THREE.PostProcessing( renderer );
77+
78+
const scenePass = pass( scene, camera );
79+
scenePass.setMRT( mrt( {
80+
output: output,
81+
diffuseColor: diffuseColor,
82+
normal: directionToColor( normalView ),
83+
velocity: velocity
84+
} ) );
85+
86+
const scenePassColor = scenePass.getTextureNode( 'output' );
87+
const scenePassDiffuse = scenePass.getTextureNode( 'diffuseColor' ).toInspector( 'Diffuse Color' );
88+
const scenePassDepth = scenePass.getTextureNode( 'depth' ).toInspector( 'Depth', () => {
89+
90+
return scenePass.getLinearDepthNode();
91+
92+
} );
93+
94+
const scenePassNormal = scenePass.getTextureNode( 'normal' ).toInspector( 'Normal' );
95+
const scenePassVelocity = scenePass.getTextureNode( 'velocity' ).toInspector( 'Velocity' );
96+
97+
// bandwidth optimization
98+
99+
const diffuseTexture = scenePass.getTexture( 'diffuseColor' );
100+
diffuseTexture.type = THREE.UnsignedByteType;
101+
102+
const normalTexture = scenePass.getTexture( 'normal' );
103+
normalTexture.type = THREE.UnsignedByteType;
104+
105+
const sceneNormal = sample( ( uv ) => {
106+
107+
return colorToDirection( scenePassNormal.sample( uv ) );
108+
109+
} );
110+
111+
// Ambient light
112+
const ambientLight = new THREE.AmbientLight(0x404040, 2);
113+
scene.add( ambientLight );
114+
115+
textureLoader = new THREE.TextureLoader();
116+
const texture1 = textureLoader.load( 'textures/brick_diffuse.jpg' );
117+
texture1.colorSpace = THREE.SRGBColorSpace;
118+
const texture2 = textureLoader.load( 'textures/floors/FloorsCheckerboard_S_Diffuse.jpg' );
119+
texture2.colorSpace = THREE.SRGBColorSpace;
120+
121+
sphere1 = new THREE.Mesh(new THREE.SphereGeometry(3), new THREE.MeshPhongMaterial({ map: texture1 }));
122+
sphere1.position.set(5, 5, 0);
123+
scene.add(sphere1);
124+
125+
sphere2 = new THREE.Mesh(new THREE.SphereGeometry(3), new THREE.MeshPhongMaterial({ color: "orange"}));
126+
sphere2.position.set(0, 5, 0);
127+
scene.add(sphere2);
128+
129+
floor = new THREE.Mesh(new THREE.BoxGeometry(50, 1, 50), new THREE.MeshPhongMaterial({ map: texture2 }));
130+
scene.add(floor);
131+
floor.position.set(0, 2.5, 0);
132+
133+
window.addEventListener( 'resize', onWindowResize );
134+
135+
const meshBlend = meshblend( scenePassColor, scenePassDepth, camera, scene );
136+
postProcessing.outputNode = meshBlend;
137+
138+
const gui = renderer.inspector.createParameters( 'MeshBlend settings' );
139+
gui.add( meshBlend.kernelSize, 'value', 1, 20 ).step( 1 ).name( 'kernel size' ).onChange(() => postProcessing.needsUpdate = true);
140+
gui.add( meshBlend.blendFactor, 'value', 0.1, 10 ).step( 0.01 ).name( 'blend factor' ).onChange(() => {
141+
meshBlend.kernelRadius.value = 0.01 * meshBlend.blendFactor.value;
142+
meshBlend.depthFalloff.value = 0.0001 * meshBlend.blendFactor.value;
143+
postProcessing.needsUpdate = true;
144+
});
145+
// gui.add( meshBlend.kernelRadius, 'value', 0.01, 1 ).step( 0.01 ).name( 'kernel radius' ).onChange(() => postProcessing.needsUpdate = true);
146+
// gui.add( meshBlend.depthFalloff, 'value', 0.001, 0.1 ).step( 0.01 ).name( 'depth falloff' ).onChange(() => postProcessing.needsUpdate = true);
147+
148+
postProcessing.needsUpdate = true;
149+
}
150+
151+
function onWindowResize() {
152+
153+
const width = window.innerWidth;
154+
const height = window.innerHeight;
155+
156+
camera.aspect = width / height;
157+
camera.updateProjectionMatrix();
158+
159+
renderer.setSize( width, height );
160+
161+
}
162+
163+
function animate() {
164+
165+
controls.update();
166+
167+
sphere2.position.x = Math.sin( Date.now() * 0.001 ) * 3;
168+
sphere1.position.y = 5 + Math.sin( Date.now() * 0.001 ) * 3;
169+
postProcessing.render();
170+
171+
}
172+
173+
</script>
174+
</body>
175+
</html>

0 commit comments

Comments
 (0)