11import { TempNode , MeshBasicNodeMaterial , RenderTarget , QuadMesh , Vector2 } from 'three/webgpu' ;
2- import { nodeObject , vec4 , vec3 , float , modelPosition , modelWorldMatrix , Fn , wgslFn , NodeUpdateType , texture , screenUV } from 'three/tsl' ;
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' ;
33
44//Source: https://www.jacktollenaar.top/mesh-seam-smoothing-blending#h.50wag6hqg9gh
55
@@ -19,22 +19,22 @@ class MeshBlendNode extends TempNode {
1919 this . blendFactor = float ( 1.2 ) ;
2020 this . kernelSize = float ( 5 ) ;
2121 this . kernelRadius = float ( 0.01 * this . blendFactor . value ) ;
22- this . depthFalloff = float ( 0.0001 * this . blendFactor . value ) ;
22+ this . depthFalloff = float ( 0.001 * this . blendFactor . value ) ;
2323 this . debugMaterial = new MeshBasicNodeMaterial ( ) ;
2424 this . _quadMesh = new QuadMesh ( this . debugMaterial ) ;
2525
2626 }
2727
2828 setup ( ) {
2929
30- const CustomHash = wgslFn ( `
31- fn Hash(p: vec3f) -> f32 {
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+ } ) ;
3237
33- var lp = (fract(p * 0.3183099 + 0.1));
34- lp *= 17.0;
35- return fract(lp.x * lp.y * lp.z * (lp.x + lp.y + lp.z));
36- }
37- ` ) ;
3838 this . hashShader = Fn ( ( ) => {
3939
4040 const p = vec3 ( modelWorldMatrix . mul ( vec3 ( modelPosition ) ) ) . toVar ( ) ;
@@ -47,45 +47,64 @@ class MeshBlendNode extends TempNode {
4747 const uv = screenUV ;
4848 const FinalOutputNode = Fn ( ( ) => {
4949
50- const outputPassFunc1 = wgslFn ( `
51- fn OutputPassFunc1(sceneDepth: vec4<f32>, tex: texture_2d<f32>, sampler: sampler, uv: vec2f, kernelSize: f32, kernelRadius: f32, depthFalloff: f32) -> vec4<f32> {
52- var seamLocation = vec2<f32>(0., 0.);
53- var minDist = f32(9999999.);
54-
55- let objectIDColor = textureSample(tex, sampler, uv);
56-
57- for(var x: f32 = -kernelSize; x <= kernelSize; x += 1.) {
58- for(var y: f32 = -kernelSize; y <= kernelSize; y += 1.) {
59- let offset = vec2<f32>(x, y) * kernelRadius * sceneDepth.r / kernelSize;
60- let SampleUV = uv + offset;
61- let sampledObjectIDColor = textureSample(tex, sampler, SampleUV);
62- if(sampledObjectIDColor.x != objectIDColor.x) {
63- let dist = dot(offset, offset);
64- if(dist < minDist) {
65- minDist = dist;
66- seamLocation = offset;
67- }
68- }
69- }
70- }
71-
72- return vec4<f32>(seamLocation.x, seamLocation.y, minDist, 1.);
73- }
74- ` ) ;
75-
76- const finalPass = wgslFn ( `
77- fn FinalPass(sceneColor: vec4f, mirroredColor: vec4f, seamLocation: vec2f, kernelRadius: f32, sceneDepth: vec4f, otherDepth: vec4f, depthFalloff: f32, minDist: f32) -> vec4f {
78-
79- let depthDiff = abs(otherDepth.r - sceneDepth.r);
80-
81- let maxSearchDistance = kernelRadius / sceneDepth.r;
82- let weight = saturate(0.5 - sqrt(minDist) / maxSearchDistance);
83- let depthWeight = saturate(1. -depthDiff / depthFalloff * kernelRadius);
84- var finalWeight = weight * depthWeight;
85-
86- return mix(sceneColor, mirroredColor, finalWeight);
87- }
88- ` ) ;
50+ // sampling helpers (capture outside Fn so they can be used with varying UV offsets)
51+ const sampleSceneDepth = ( v ) => texture ( this . sceneDepthNode , v ) ;
52+ const sampleRT = ( v ) => texture ( this . renderTarget . textures [ 0 ] , v ) ;
53+ const sampleSceneOutput = ( v ) => texture ( this . sceneOutputNode , v ) ;
54+
55+ const outputPassFunc1 = Fn ( ( [ sceneDepthNode , rtNode , sceneOutNode , uvNode , kernelSizeNode , kernelRadiusNode , depthFalloffNode ] ) => {
56+
57+ const sceneDepthVar = sceneDepthNode . toVar ( ) ;
58+
59+ // kernelSizeNode is expected to be a numeric node with a .value available at build time
60+ const kSize = kernelSizeNode . value || 0 ;
61+
62+ const seamLocation = vec2 ( 0. , 0. ) . toVar ( ) ;
63+ var minDist = float ( 9999999. ) . toVar ( ) ;
64+
65+ const objectIDColor = sampleRT ( uvNode ) . toVar ( ) ;
66+
67+ // Use TSL Loop so the iteration becomes shader-side loops
68+ const k = int ( kSize ) ;
69+ Loop ( { start : k . negate ( ) , end : k , type : 'int' , condition : '<=' , name : 'x' } , ( { x } ) => {
70+
71+ Loop ( { start : k . negate ( ) , end : k , type : 'int' , condition : '<=' , name : 'y' } , ( { y } ) => {
72+
73+ const offset = vec2 ( x . toFloat ( ) , y . toFloat ( ) ) . mul ( kernelRadiusNode . mul ( sceneDepthVar . r . mul ( 0.3 ) ) . div ( float ( kSize ) ) ) . toVar ( ) ;
74+ const SampleUV = uvNode . add ( offset ) . toVar ( ) ;
75+ const sampledObjectIDColor = sampleRT ( SampleUV ) . toVar ( ) ;
76+ If ( sampledObjectIDColor . x . notEqual ( objectIDColor . x ) , ( ) => {
77+
78+ const dist = dot ( offset , offset ) ;
79+ If ( dist . lessThan ( minDist ) , ( ) => {
80+
81+ minDist . assign ( dist ) ;
82+ seamLocation . assign ( offset ) ;
83+
84+ } ) ;
85+
86+ } ) ;
87+
88+ } ) ;
89+
90+ } ) ;
91+
92+ return vec4 ( seamLocation . x , seamLocation . y , minDist , 1. ) ;
93+
94+ } ) ;
95+
96+ const finalPass = Fn ( ( [ sceneColor , mirroredColor , seamLocation , kernelRadiusNode , sceneDepth , otherDepth , depthFalloffNode , minDist ] ) => {
97+
98+ const depthDiff = abs ( otherDepth . r . sub ( sceneDepth . r ) ) ;
99+
100+ const maxSearchDistance = kernelRadiusNode . div ( sceneDepth . r ) ;
101+ const weight = saturate ( float ( 0.5 ) . sub ( sqrt ( minDist ) . div ( maxSearchDistance ) ) ) ;
102+ const depthWeight = saturate ( float ( 1. ) . sub ( depthDiff . div ( depthFalloffNode . mul ( kernelRadiusNode ) ) ) ) ;
103+ const finalWeight = weight . mul ( depthWeight ) ;
104+
105+ return mix ( sceneColor , mirroredColor , finalWeight ) ;
106+
107+ } ) ;
89108
90109 const pass1 = outputPassFunc1 (
91110 texture ( this . sceneDepthNode , uv ) ,
0 commit comments