From b1275d8a9d3d43d48f45b00da808afac82681726 Mon Sep 17 00:00:00 2001 From: Christian Helgeson Date: Thu, 4 Jul 2024 00:03:46 -0700 Subject: [PATCH 01/24] sketched out draft of pixelation pass. --- examples/files.json | 1 + examples/webgl_postprocessing_pixel.html | 2 +- examples/webgpu_postprocessing_pixel.html | 271 ++++++++++++++++++++++ examples/webgpu_postprocessing_sobel.html | 2 + src/nodes/Nodes.js | 1 + src/nodes/display/PassNode.js | 59 ++++- src/nodes/display/PixelationNode.js | 160 +++++++++++++ src/nodes/display/SobelOperatorNode.js | 2 + 8 files changed, 495 insertions(+), 3 deletions(-) create mode 100644 examples/webgpu_postprocessing_pixel.html create mode 100644 src/nodes/display/PixelationNode.js diff --git a/examples/files.json b/examples/files.json index fe7ca369977a91..c1c329eca1757a 100644 --- a/examples/files.json +++ b/examples/files.json @@ -374,6 +374,7 @@ "webgpu_postprocessing_afterimage", "webgpu_postprocessing_anamorphic", "webgpu_postprocessing_dof", + "webgpu_postprocessing_pixel", "webgpu_postprocessing_sobel", "webgpu_postprocessing", "webgpu_procedural_texture", diff --git a/examples/webgl_postprocessing_pixel.html b/examples/webgl_postprocessing_pixel.html index 0710d87f3bf829..42044a97c5b858 100644 --- a/examples/webgl_postprocessing_pixel.html +++ b/examples/webgl_postprocessing_pixel.html @@ -274,4 +274,4 @@ - + \ No newline at end of file diff --git a/examples/webgpu_postprocessing_pixel.html b/examples/webgpu_postprocessing_pixel.html new file mode 100644 index 00000000000000..da69e9b03d71f6 --- /dev/null +++ b/examples/webgpu_postprocessing_pixel.html @@ -0,0 +1,271 @@ + + + + three.js webgpu - postprocessing pixel + + + + + + +
+ three.js - Node based pixelation pass with optional single pixel outlines by + Kody King

+
+ +
+ + + + + + + + \ No newline at end of file diff --git a/examples/webgpu_postprocessing_sobel.html b/examples/webgpu_postprocessing_sobel.html index 2b17cdc9c49133..e137ad25ff3564 100644 --- a/examples/webgpu_postprocessing_sobel.html +++ b/examples/webgpu_postprocessing_sobel.html @@ -82,6 +82,8 @@ const scenePass = pass( scene, camera ); const scenePassColor = scenePass.getTextureNode(); + console.log(scenePassColor) + postProcessing.outputNode = scenePassColor.sobel(); // diff --git a/src/nodes/Nodes.js b/src/nodes/Nodes.js index d6e0da047bd9f6..df9c9b28ce9e22 100644 --- a/src/nodes/Nodes.js +++ b/src/nodes/Nodes.js @@ -135,6 +135,7 @@ export { default as RGBShiftNode, rgbShift } from './display/RGBShiftNode.js'; export { default as FilmNode, film } from './display/FilmNode.js'; export { default as Lut3DNode, lut3D } from './display/Lut3DNode.js'; export { default as RenderOutputNode, renderOutput } from './display/RenderOutputNode.js'; +export { default as PixelationNode, pixelate } from './display/PixelationNode.js'; export { default as PassNode, pass, passTexture, depthPass } from './display/PassNode.js'; diff --git a/src/nodes/display/PassNode.js b/src/nodes/display/PassNode.js index 59bc3b4ac1a4a9..d437f51aa47f7c 100644 --- a/src/nodes/display/PassNode.js +++ b/src/nodes/display/PassNode.js @@ -10,6 +10,8 @@ import { HalfFloatType/*, FloatType*/ } from '../../constants.js'; import { Vector2 } from '../../math/Vector2.js'; import { DepthTexture } from '../../textures/DepthTexture.js'; import { RenderTarget } from '../../core/RenderTarget.js'; +import { MeshNormalMaterial } from '../../materials/MeshNormalMaterial.js'; +import MeshNormalNodeMaterial from '../materials/MeshNormalNodeMaterial.js'; const _size = new Vector2(); @@ -93,6 +95,16 @@ class PassNode extends TempNode { this.renderTarget = renderTarget; + if ( this.scope === PassNode.NORMAL ) { + + const target = new RenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, { type: HalfFloatType } ); + renderTarget.texture.name = 'PostProcessingNormal'; + this.normalRenderTarget = target; + + this._normalTextureNode = nodeObject( new PassTextureNode( this, target.texture ) ); + + } + this.updateBeforeType = NodeUpdateType.FRAME; this._textures = { @@ -169,6 +181,12 @@ class PassNode extends TempNode { } + getTextureNormalNode() { + + return this._normalTextureNode ? this._normalTextureNode : null; + + } + getViewZNode() { if ( this._viewZNode === null ) { @@ -214,7 +232,27 @@ class PassNode extends TempNode { this.renderTarget.depthTexture.isMultisampleRenderTargetTexture = this.renderTarget.samples > 1; - return this.scope === PassNode.COLOR ? this.getTextureNode() : this.getLinearDepthNode(); + switch( this.scope ) { + + case PassNode.COLOR: { + + return this.getTextureNode(); + + }; + + case PassNode.DEPTH: { + + return this.getLinearDepthNode(); + + }; + + case PassNode.NORMAL: { + + return this.getTextureNormalNode(); + + } + + } } @@ -240,6 +278,16 @@ class PassNode extends TempNode { renderer.render( scene, camera ); + if ( this._normalTextureNode ) { + + renderer.setRenderTarget( this.normalRenderTarget ); + const oldMaterial = scene.overrideMaterial; + scene.overrideMaterial = new MeshNormalNodeMaterial(); + renderer.render( scene, camera ); + scene.overrideMaterial = oldMaterial; + + } + renderer.setRenderTarget( currentRenderTarget ); renderer.setMRT( currentMRT ); @@ -254,6 +302,9 @@ class PassNode extends TempNode { const effectiveHeight = this._height * this._pixelRatio; this.renderTarget.setSize( effectiveWidth, effectiveHeight ); + if ( this.normalRenderTarget ) { + this.normalRenderTarget.setSize( effectiveWidth, effectiveHeight ); + } } @@ -268,19 +319,23 @@ class PassNode extends TempNode { dispose() { this.renderTarget.dispose(); + if (this.normalRenderTarget) { + this.normalRenderTarget.dispose() + } } - } PassNode.COLOR = 'color'; PassNode.DEPTH = 'depth'; +PassNode.NORMAL = 'normal' export default PassNode; export const pass = ( scene, camera, options ) => nodeObject( new PassNode( PassNode.COLOR, scene, camera, options ) ); export const passTexture = ( pass, texture ) => nodeObject( new PassTextureNode( pass, texture ) ); export const depthPass = ( scene, camera ) => nodeObject( new PassNode( PassNode.DEPTH, scene, camera ) ); +export const normalPass = ( scene, camera ) => nodeObject( new PassNode( PassNode.NORMAL, scene, camera ) ); addNodeClass( 'PassNode', PassNode ); diff --git a/src/nodes/display/PixelationNode.js b/src/nodes/display/PixelationNode.js new file mode 100644 index 00000000000000..16068c2ec15bf0 --- /dev/null +++ b/src/nodes/display/PixelationNode.js @@ -0,0 +1,160 @@ +import TempNode from '../core/TempNode.js'; +import { uv } from '../accessors/UVNode.js'; +import { addNodeElement, tslFn, nodeObject, vec2, vec3 } from '../shadernode/ShaderNode.js'; +import { NodeUpdateType } from '../core/constants.js'; +import { uniform } from '../core/UniformNode.js'; +import { dot, clamp, smoothstep, sign, step, floor } from '../math/MathNode.js'; +import { float, If } from '../shadernode/ShaderNode.js'; + +import { Vector2 } from '../../math/Vector2.js'; + +class PixelationNode extends TempNode { + + constructor( textureNode, pixelSize, normalEdgeStrengthNode, depthEdgeStrengthNode ) { + + super(); + + this.textureNode = textureNode; + this.pixelSize = pixelSize; + this.normalEdgeStrengthNode = normalEdgeStrengthNode; + this.depthEdgeStrengthNode = depthEdgeStrengthNode; + + this.updateBeforeType = NodeUpdateType.RENDER; + + + this._resolution = uniform( new Vector2() ); + this._renderResolution = uniform( new Vector2() ); + + } + + updateBefore() { + + + } + + setup() { + + const { textureNode } = this; + + const uvNode = textureNode.uvNode || uv(); + + const sampleTexture = ( uv ) => textureNode.uv( uv ); + const sampleDepthTexture = ( uv ) => textureNode.passNode._depthTextureNode.uv( uv ); + const sampleNormalTexture = ( uv ) => textureNode.passNode._normalTextureNode.uv(uv); + + const getDepth = ( uv, x, y ) => { + + const newUV = uv.add(vec2( x, y ) ); + newUV.mulAssign( this._resolution.zw ); + + return sampleDepthTexture(newUV).r + + } + + const getNormal = ( uv, x, y ) => { + + const newUV = uv.add(vec2( x, y ) ); + newUV.mulAssign( this._resolution.zw ); + + const temp = sampleNormalTexture(newUV).rgb.mul(2.0); + return temp.sub(1.0); + + } + + const clampNormal = ( valueNode ) => { + + return clamp( valueNode, 0.0, 1.0) + + } + + const depthEdgeIndicator = ( uv, depth, normalNode ) => { + + const diff = float(0.0); + diff.addAssign( clampNormal( getDepth(uv, 1, 0).sub(depth) ) ); + diff.addAssign( clampNormal( getDepth(uv, -1, 0).sub(depth) ) ); + diff.addAssign( clampNormal( getDepth(uv, 0, 1).sub(depth) ) ); + diff.addAssign( clampNormal( getDepth(uv, 0, -1).sub(depth) ) ); + + return floor( smoothstep( 0.01, 0.02, diff ).mul(2.0) ).div(2.0); + + } + + const neighborNormalEdgeIndicator = ( xNode, yNode, depthNode, normalNode ) => { + + const depthDiff = getDepth( xNode, yNode ).sub( depthNode ); + const neighborNormal = getNormal( xNode, yNode ); + + const normalEdgeBias = vec3(1.0, 1.0, 1.0); + const normalDiff = dot( normalNode.sub( neighborNormal ), normalEdgeBias ); + const normalIndicator = clampNormal( smoothstep( -.01, .01, normalDiff ) ); + + // Only the shallower pixel should detect the normal edge. + const depthIndicator = clampNormal( sign( depthDiff.mul(0.25).add( .0025 ) ) ); + + const dotThing = float( 1.0 ).sub( dot( normal, neighborNormal ) ); + return dotThing.mul( depthIndicator ).mul( normalIndicator ); + + } + + const normalEdgeIndicator = ( xNode, yNode, depthNode, normalNode ) => { + + const indicator = float(0.0); + indicator.addAssign( neighborNormalEdgeIndicator( 0, -1, depthNode, normalNode ) ); + indicator.addAssign( neighborNormalEdgeIndicator( 0, 1, depthNode, normalNode ) ); + indicator.addAssign( neighborNormalEdgeIndicator( -1, 0, depthNode, normalNode ) ); + indicator.addAssign( neighborNormalEdgeIndicator( 1, 0, depthNode, normalNode ) ); + + return step( 0.1, indicator ); + + } + + const pixelate = tslFn( () => { + + const textureColor = sampleTexture(uvNode); + + /*const depth = float( 0.0 ); + const normal = vec3( 0.0 ); + + const dei = float( 0.0 ); + If( this.depthEdgeStrengthNode.greaterThan( 0.0 ), () => { + + dei.assign( depthEdgeIndicator( depth, normal ) ); + + }) + + const nei = float(0.0); + + If( this.normalEdgeStrengthNode.greaterThan(0.0), () => { + + nei.assign( normalEdgeIndicator( depth, normal ) ); + + + }) + + const strength = dei.greaterThan( 0.0 ).cond( + float( 1.0 ).sub( this.depthEdgeStrengthNode.mul( dei ) ), + float( 1.0 ).add( this.normalEdgeStrengthNode.mul( nei ) ) + ) */ + + return textureColor;//.mul( strength ); + + + } ); + + const outputNode = pixelate(); + + return outputNode; + + } + +} + +export const pixelate = ( node, pixelSize, normalEdgeStrength, depthEdgeStrength ) => { + return nodeObject( new PixelationNode( + nodeObject( node ).toTexture(), pixelSize, normalEdgeStrength, depthEdgeStrength + )); +}; + +addNodeElement( 'pixelate', pixelate ); + +export default PixelationNode; diff --git a/src/nodes/display/SobelOperatorNode.js b/src/nodes/display/SobelOperatorNode.js index b798adfd3954ba..860912d7142dc5 100644 --- a/src/nodes/display/SobelOperatorNode.js +++ b/src/nodes/display/SobelOperatorNode.js @@ -12,6 +12,8 @@ class SobelOperatorNode extends TempNode { constructor( textureNode ) { + console.log(textureNode); + super(); this.textureNode = textureNode; From 1da945a2f5a3a2fdb033bf358ddd0875fe0ee52e Mon Sep 17 00:00:00 2001 From: Christian Helgeson Date: Thu, 4 Jul 2024 14:59:21 -0700 Subject: [PATCH 02/24] Have normal and depth edges working --- examples/webgpu_postprocessing_pixel.html | 5 +- src/nodes/Nodes.js | 2 +- src/nodes/display/PassNode.js | 2 +- src/nodes/display/PixelationNode.js | 148 ++++++++---------- src/renderers/webgpu/nodes/WGSLNodeBuilder.js | 1 + 5 files changed, 74 insertions(+), 84 deletions(-) diff --git a/examples/webgpu_postprocessing_pixel.html b/examples/webgpu_postprocessing_pixel.html index da69e9b03d71f6..b99cd28e4c1ebd 100644 --- a/examples/webgpu_postprocessing_pixel.html +++ b/examples/webgpu_postprocessing_pixel.html @@ -33,7 +33,7 @@ import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; - import { pass, normalPass, uniform, pixelate } from 'three/tsl'; + import { pass, normalPass, uniform, pixelation } from 'three/tsl'; let camera, scene, renderer, composer, crystalMesh, clock; let gui, params; @@ -131,6 +131,7 @@ document.body.appendChild( renderer.domElement ); const effectController = { + pixelSize: uniform(14), normalEdgeStrength: uniform(0.3), depthEdgeStrength: uniform(0.4) } @@ -141,7 +142,7 @@ const scenePassColor = scenePass.getTextureNode(); const scenePassDepth = scenePass.getTextureDepthNode(); const scenePassNormal = scenePass.getTextureNormalNode(); - composer.outputNode = scenePass.pixelate(6, effectController.normalEdgeStrength, effectController.depthEdgeStrength); + composer.outputNode = scenePassColor.pixelation( scenePassDepth, scenePassNormal, 14, effectController.normalEdgeStrength, effectController.depthEdgeStrength); window.addEventListener( 'resize', onWindowResize ); diff --git a/src/nodes/Nodes.js b/src/nodes/Nodes.js index df9c9b28ce9e22..b53ad8f56cee19 100644 --- a/src/nodes/Nodes.js +++ b/src/nodes/Nodes.js @@ -135,7 +135,7 @@ export { default as RGBShiftNode, rgbShift } from './display/RGBShiftNode.js'; export { default as FilmNode, film } from './display/FilmNode.js'; export { default as Lut3DNode, lut3D } from './display/Lut3DNode.js'; export { default as RenderOutputNode, renderOutput } from './display/RenderOutputNode.js'; -export { default as PixelationNode, pixelate } from './display/PixelationNode.js'; +export { default as PixelationNode, pixelation } from './display/PixelationNode.js'; export { default as PassNode, pass, passTexture, depthPass } from './display/PassNode.js'; diff --git a/src/nodes/display/PassNode.js b/src/nodes/display/PassNode.js index d437f51aa47f7c..db948ba3538676 100644 --- a/src/nodes/display/PassNode.js +++ b/src/nodes/display/PassNode.js @@ -338,4 +338,4 @@ export const passTexture = ( pass, texture ) => nodeObject( new PassTextureNode( export const depthPass = ( scene, camera ) => nodeObject( new PassNode( PassNode.DEPTH, scene, camera ) ); export const normalPass = ( scene, camera ) => nodeObject( new PassNode( PassNode.NORMAL, scene, camera ) ); -addNodeClass( 'PassNode', PassNode ); +addNodeClass( 'PassNode', PassNode ); \ No newline at end of file diff --git a/src/nodes/display/PixelationNode.js b/src/nodes/display/PixelationNode.js index 16068c2ec15bf0..513adce81518f1 100644 --- a/src/nodes/display/PixelationNode.js +++ b/src/nodes/display/PixelationNode.js @@ -5,143 +5,135 @@ import { NodeUpdateType } from '../core/constants.js'; import { uniform } from '../core/UniformNode.js'; import { dot, clamp, smoothstep, sign, step, floor } from '../math/MathNode.js'; import { float, If } from '../shadernode/ShaderNode.js'; - -import { Vector2 } from '../../math/Vector2.js'; +import { Vector4 } from '../../math/Vector4.js'; +import { temp } from '../Nodes.js'; +import { property } from '../core/PropertyNode.js'; class PixelationNode extends TempNode { - constructor( textureNode, pixelSize, normalEdgeStrengthNode, depthEdgeStrengthNode ) { + constructor( textureNode, depthNode, normalNode, pixelSizeNode, normalEdgeStrength, depthEdgeStrength ) { super(); this.textureNode = textureNode; - this.pixelSize = pixelSize; - this.normalEdgeStrengthNode = normalEdgeStrengthNode; - this.depthEdgeStrengthNode = depthEdgeStrengthNode; + this.depthNode = depthNode; + this.normalNode = normalNode; - this.updateBeforeType = NodeUpdateType.RENDER; + this.pixelSizeNode = pixelSizeNode; + this.normalEdgeStrength = normalEdgeStrength; + this.depthEdgeStrength = depthEdgeStrength; + this.updateBeforeType = NodeUpdateType.RENDER; - this._resolution = uniform( new Vector2() ); - this._renderResolution = uniform( new Vector2() ); + this._resolution = uniform( new Vector4() ); } updateBefore() { + const map = this.textureNode.value; + const width = map.image.width / this.pixelSizeNode.value; + const height = map.image.height / this.pixelSizeNode.value; + + this._resolution.value.set( width, height, 1 / width, 1 / height ); } setup() { - const { textureNode } = this; - - const uvNode = textureNode.uvNode || uv(); + const { textureNode, depthNode, normalNode } = this; - const sampleTexture = ( uv ) => textureNode.uv( uv ); - const sampleDepthTexture = ( uv ) => textureNode.passNode._depthTextureNode.uv( uv ); - const sampleNormalTexture = ( uv ) => textureNode.passNode._normalTextureNode.uv(uv); + const uvNodeTexture = textureNode.uvNode || uv(); + const uvNodeDepth = depthNode.uvNode || uv(); + const uvNodeNormal = normalNode.uvNode || uv(); - const getDepth = ( uv, x, y ) => { + const sampleTexture = () => textureNode.uv( uvNodeTexture ); - const newUV = uv.add(vec2( x, y ) ); - newUV.mulAssign( this._resolution.zw ); + const sampleDepth = ( x, y ) => depthNode.uv( uvNodeDepth.add( vec2( x, y ).mul( this._resolution.zw ) ) ).r; - return sampleDepthTexture(newUV).r + const sampleNormal = ( x, y ) => normalNode.uv( uvNodeNormal.add( vec2( x, y ).mul( this._resolution.zw ) ) ).rgb.mul( 2.0 ).oneMinus(); - } - - const getNormal = ( uv, x, y ) => { + const depthEdgeIndicator = ( depth ) => { - const newUV = uv.add(vec2( x, y ) ); - newUV.mulAssign( this._resolution.zw ); + const diff = property('float', 'diff'); + diff.addAssign( clamp( sampleDepth( 1, 0 ).sub( depth ) ) ); + diff.addAssign( clamp( sampleDepth( - 1, 0 ).sub( depth ) ) ); + diff.addAssign( clamp( sampleDepth( 0, 1 ).sub( depth ) ) ); + diff.addAssign( clamp( sampleDepth( 0, - 1 ).sub( depth ) ) ); - const temp = sampleNormalTexture(newUV).rgb.mul(2.0); - return temp.sub(1.0); + return floor( smoothstep( 0.01, 0.02, diff ).mul( 2 ) ).div( 2 ); } - const clampNormal = ( valueNode ) => { + const neighborNormalEdgeIndicator = ( x, y, depth, normal ) => { - return clamp( valueNode, 0.0, 1.0) + const depthDiff = sampleDepth( x, y ).sub(depth); + const neighborNormal = sampleNormal( x, y ); - } + // Edge pixels should yield to faces who's normals are closer to the bias normal. - const depthEdgeIndicator = ( uv, depth, normalNode ) => { + const normalEdgeBias = vec3( 1, 1, 1 ); // This should probably be a parameter. + const normalDiff = dot( normal.sub( neighborNormal ), normalEdgeBias ); + const normalIndicator = clamp( smoothstep( - 0.01, 0.01, normalDiff ), 0.0, 1.0 ); - const diff = float(0.0); - diff.addAssign( clampNormal( getDepth(uv, 1, 0).sub(depth) ) ); - diff.addAssign( clampNormal( getDepth(uv, -1, 0).sub(depth) ) ); - diff.addAssign( clampNormal( getDepth(uv, 0, 1).sub(depth) ) ); - diff.addAssign( clampNormal( getDepth(uv, 0, -1).sub(depth) ) ); + // Only the shallower pixel should detect the normal edge. + const depthIndicator = clamp( sign( depthDiff.mul(0.25).add(.0025) ), 0.0, 1.0 ); - return floor( smoothstep( 0.01, 0.02, diff ).mul(2.0) ).div(2.0); + return float( 1.0 ).sub( dot( normal, neighborNormal ) ).mul( depthIndicator ).mul( normalIndicator ); - } + }; - const neighborNormalEdgeIndicator = ( xNode, yNode, depthNode, normalNode ) => { + const normalEdgeIndicator = ( depth, normal ) => { - const depthDiff = getDepth( xNode, yNode ).sub( depthNode ); - const neighborNormal = getNormal( xNode, yNode ); + const indicator = property('float', 'indicator') - const normalEdgeBias = vec3(1.0, 1.0, 1.0); - const normalDiff = dot( normalNode.sub( neighborNormal ), normalEdgeBias ); - const normalIndicator = clampNormal( smoothstep( -.01, .01, normalDiff ) ); + indicator.addAssign( neighborNormalEdgeIndicator( 0, - 1, depth, normal ) ); + indicator.addAssign( neighborNormalEdgeIndicator( 0, 1, depth, normal ) ); + indicator.addAssign( neighborNormalEdgeIndicator( - 1, 0, depth, normal ) ); + indicator.addAssign( neighborNormalEdgeIndicator( 1, 0, depth, normal ) ); - // Only the shallower pixel should detect the normal edge. - const depthIndicator = clampNormal( sign( depthDiff.mul(0.25).add( .0025 ) ) ); - - const dotThing = float( 1.0 ).sub( dot( normal, neighborNormal ) ); - return dotThing.mul( depthIndicator ).mul( normalIndicator ); + return step( 0.1, indicator ); } - const normalEdgeIndicator = ( xNode, yNode, depthNode, normalNode ) => { + const pixelation = tslFn( () => { - const indicator = float(0.0); - indicator.addAssign( neighborNormalEdgeIndicator( 0, -1, depthNode, normalNode ) ); - indicator.addAssign( neighborNormalEdgeIndicator( 0, 1, depthNode, normalNode ) ); - indicator.addAssign( neighborNormalEdgeIndicator( -1, 0, depthNode, normalNode ) ); - indicator.addAssign( neighborNormalEdgeIndicator( 1, 0, depthNode, normalNode ) ); + const texel = sampleTexture(); - return step( 0.1, indicator ); + const depth = property( 'float', 'depth' ); + const normal = property( 'vec3', 'normal' ); - } + If( this.depthEdgeStrength.greaterThan( 0.0 ).or( this.normalEdgeStrength.greaterThan( 0.0 ) ), () => { - const pixelate = tslFn( () => { + depth.assign( sampleDepth( 0, 0 ) ); + normal.assign( sampleNormal( 0, 0 ) ); - const textureColor = sampleTexture(uvNode); + }) - /*const depth = float( 0.0 ); - const normal = vec3( 0.0 ); + const dei = property( 'float', 'dei' ); - const dei = float( 0.0 ); - If( this.depthEdgeStrengthNode.greaterThan( 0.0 ), () => { + If( this.depthEdgeStrength.greaterThan( 0.0 ), () => { - dei.assign( depthEdgeIndicator( depth, normal ) ); + dei.assign( depthEdgeIndicator( depth ) ) - }) + }); - const nei = float(0.0); + const nei = property( 'float', 'nei' ); - If( this.normalEdgeStrengthNode.greaterThan(0.0), () => { + If( this.normalEdgeStrength.greaterThan( 0.0 ), () => { nei.assign( normalEdgeIndicator( depth, normal ) ); + }); - }) - - const strength = dei.greaterThan( 0.0 ).cond( - float( 1.0 ).sub( this.depthEdgeStrengthNode.mul( dei ) ), - float( 1.0 ).add( this.normalEdgeStrengthNode.mul( nei ) ) - ) */ - return textureColor;//.mul( strength ); + const strength = dei.greaterThan( 0 ).cond( float( 1.0 ).sub( dei.mul( this.depthEdgeStrength ) ), nei.mul( this.normalEdgeStrength ).add( 1 ) ); + return texel.mul( strength ); } ); - const outputNode = pixelate(); + const outputNode = pixelation(); return outputNode; @@ -149,12 +141,8 @@ class PixelationNode extends TempNode { } -export const pixelate = ( node, pixelSize, normalEdgeStrength, depthEdgeStrength ) => { - return nodeObject( new PixelationNode( - nodeObject( node ).toTexture(), pixelSize, normalEdgeStrength, depthEdgeStrength - )); -}; +export const pixelation = ( node, depthNode, normalNode, pixelSize, normalEdgeStrength = 0.3, depthEdgeStrength = 0.4 ) => nodeObject( new PixelationNode( nodeObject( node ).toTexture(), nodeObject( depthNode ).toTexture(), nodeObject( normalNode ).toTexture(), nodeObject( pixelSize ), normalEdgeStrength, depthEdgeStrength ) ); -addNodeElement( 'pixelate', pixelate ); +addNodeElement( 'pixelation', pixelation ); export default PixelationNode; diff --git a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js index d1b34adc393cb3..3882888ec49eb0 100644 --- a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js +++ b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js @@ -1041,6 +1041,7 @@ ${ flowData.code } this.vertexShader = this._getWGSLVertexCode( shadersData.vertex ); this.fragmentShader = this._getWGSLFragmentCode( shadersData.fragment ); + console.log(this.fragmentShader) } else { From 67c89e0c82a00ba8b49bcda99ae7bffb32a30a50 Mon Sep 17 00:00:00 2001 From: Christian Helgeson Date: Thu, 4 Jul 2024 15:05:47 -0700 Subject: [PATCH 03/24] Pixel size modifier --- examples/webgpu_postprocessing_pixel.html | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/webgpu_postprocessing_pixel.html b/examples/webgpu_postprocessing_pixel.html index b99cd28e4c1ebd..103c41f0eff659 100644 --- a/examples/webgpu_postprocessing_pixel.html +++ b/examples/webgpu_postprocessing_pixel.html @@ -131,7 +131,7 @@ document.body.appendChild( renderer.domElement ); const effectController = { - pixelSize: uniform(14), + pixelSize: 14, normalEdgeStrength: uniform(0.3), depthEdgeStrength: uniform(0.4) } @@ -142,8 +142,8 @@ const scenePassColor = scenePass.getTextureNode(); const scenePassDepth = scenePass.getTextureDepthNode(); const scenePassNormal = scenePass.getTextureNormalNode(); - composer.outputNode = scenePassColor.pixelation( scenePassDepth, scenePassNormal, 14, effectController.normalEdgeStrength, effectController.depthEdgeStrength); - + const pixelationPass = scenePassColor.pixelation( scenePassDepth, scenePassNormal, 4, effectController.normalEdgeStrength, effectController.depthEdgeStrength); + composer.outputNode = pixelationPass; window.addEventListener( 'resize', onWindowResize ); const controls = new OrbitControls( camera, renderer.domElement ); @@ -152,6 +152,7 @@ // gui gui = new GUI(); + gui.add( pixelationPass.pixelSizeNode, 'value', 0, 14, 1).name( 'Pixel Size' ); gui.add( effectController.normalEdgeStrength, 'value', 0, 2, 0.05 ).name( 'Normal Edge Strength' ); gui.add( effectController.depthEdgeStrength, 'value', 0, 1, 0.05 ).name( 'Depth Edge Strength' ); From dd03bf7de419171cc46f532b75e59f5e3115e778 Mon Sep 17 00:00:00 2001 From: Christian Helgeson Date: Fri, 5 Jul 2024 18:36:33 -0700 Subject: [PATCH 04/24] playing with render targets --- src/nodes/Nodes.js | 1 + src/nodes/display/PassNode.js | 10 +-- src/nodes/display/PixelationNode.js | 2 +- src/nodes/display/PixelationPassNode.js | 90 +++++++++++++++++++++++++ 4 files changed, 97 insertions(+), 6 deletions(-) create mode 100644 src/nodes/display/PixelationPassNode.js diff --git a/src/nodes/Nodes.js b/src/nodes/Nodes.js index b53ad8f56cee19..0bb306b7530389 100644 --- a/src/nodes/Nodes.js +++ b/src/nodes/Nodes.js @@ -136,6 +136,7 @@ export { default as FilmNode, film } from './display/FilmNode.js'; export { default as Lut3DNode, lut3D } from './display/Lut3DNode.js'; export { default as RenderOutputNode, renderOutput } from './display/RenderOutputNode.js'; export { default as PixelationNode, pixelation } from './display/PixelationNode.js'; +export { default as PixelationPassNode, pixelationPass } from './display/PixelationPassNode.js' export { default as PassNode, pass, passTexture, depthPass } from './display/PassNode.js'; diff --git a/src/nodes/display/PassNode.js b/src/nodes/display/PassNode.js index db948ba3538676..81febe399651e0 100644 --- a/src/nodes/display/PassNode.js +++ b/src/nodes/display/PassNode.js @@ -15,7 +15,7 @@ import MeshNormalNodeMaterial from '../materials/MeshNormalNodeMaterial.js'; const _size = new Vector2(); -class PassTextureNode extends TextureNode { +export class PassTextureNode extends TextureNode { constructor( passNode, texture ) { @@ -97,11 +97,11 @@ class PassNode extends TempNode { if ( this.scope === PassNode.NORMAL ) { - const target = new RenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, { type: HalfFloatType } ); - renderTarget.texture.name = 'PostProcessingNormal'; - this.normalRenderTarget = target; + const normalTarget = new RenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, { type: HalfFloatType } ); + normalTarget.texture.name = 'PostProcessingNormal'; + this.normalRenderTarget = normalTarget; - this._normalTextureNode = nodeObject( new PassTextureNode( this, target.texture ) ); + this._normalTextureNode = nodeObject( new PassTextureNode( this, normalTarget.texture ) ); } diff --git a/src/nodes/display/PixelationNode.js b/src/nodes/display/PixelationNode.js index 513adce81518f1..636407e2d575ab 100644 --- a/src/nodes/display/PixelationNode.js +++ b/src/nodes/display/PixelationNode.js @@ -141,7 +141,7 @@ class PixelationNode extends TempNode { } -export const pixelation = ( node, depthNode, normalNode, pixelSize, normalEdgeStrength = 0.3, depthEdgeStrength = 0.4 ) => nodeObject( new PixelationNode( nodeObject( node ).toTexture(), nodeObject( depthNode ).toTexture(), nodeObject( normalNode ).toTexture(), nodeObject( pixelSize ), normalEdgeStrength, depthEdgeStrength ) ); +export const pixelation = ( node, depthNode, normalNode, pixelSize = 4, normalEdgeStrength = 0.3, depthEdgeStrength = 0.4 ) => nodeObject( new PixelationNode( nodeObject( node ).toTexture(), nodeObject( depthNode ).toTexture(), nodeObject( normalNode ).toTexture(), pixelSize, normalEdgeStrength, depthEdgeStrength ) ); addNodeElement( 'pixelation', pixelation ); diff --git a/src/nodes/display/PixelationPassNode.js b/src/nodes/display/PixelationPassNode.js new file mode 100644 index 00000000000000..2d922fe73c4ebd --- /dev/null +++ b/src/nodes/display/PixelationPassNode.js @@ -0,0 +1,90 @@ +import { nodeObject } from '../shadernode/ShaderNode.js'; +import { addNodeClass } from '../core/Node.js'; +import { NodeUpdateType } from '../core/constants.js'; +import PassNode from './PassNode.js'; +import { RenderTarget } from '../../core/RenderTarget.js'; +import { HalfFloatType/*, FloatType*/ } from '../../constants.js'; +import { PassTextureNode } from './PassNode.js'; +import { Vector2 } from '../../math/Vector2.js'; +import { MeshNormalNodeMaterial } from '../Nodes.js'; + +const _size = new Vector2(); + +class PixelationPassNode extends PassNode { + + constructor( scene, camera, pixelSize, normalEdgeStrength, depthEdgeStrength ) { + + super( 'normal', scene, camera ); + + this.pixelSize = pixelSize; + this.normalEdgeStrength = normalEdgeStrength; + this.depthEdgeStrength = depthEdgeStrength; + + this.updateBeforeType = NodeUpdateType.FRAME; + + this.isPixelationPassNode = true; + + } + + updateBefore( frame ) { + + const { renderer } = frame; + const { scene, camera } = this; + + this._pixelRatio = renderer.getPixelRatio(); + + const size = renderer.getSize( _size ); + const pixelSize = this.pixelSize.value ? this.pixelSize.value : this.pixelSize; + + this.setSize( pixelSize >= 1 ? size.width / pixelSize : size.width, pixelSize >= 1 ? size.height / pixelSize : size.height ); + + const currentRenderTarget = renderer.getRenderTarget(); + + this._cameraNear.value = camera.near; + this._cameraFar.value = camera.far; + + renderer.setRenderTarget( this.renderTarget ); + console.log(this.renderTarget) + + renderer.render( scene, camera ); + + renderer.setRenderTarget( this.normalRenderTarget ); + const oldMaterial = scene.overrideMaterial; + scene.overrideMaterial = new MeshNormalNodeMaterial(); + renderer.render( scene, camera ); + scene.overrideMaterial = oldMaterial; + + renderer.setRenderTarget( currentRenderTarget ); + + } + + setSize( width, height ) { + + super.setSize( width, height ); + this.normalRenderTarget.setSize( width * this._pixelRatio, height * this._pixelRatio ); + + } + + setup() { + + const pass = super.getTextureDepthNode(); + console.log( this.normalEdgeStrength ) + return pass.pixelation( this._depthTextureNode, this._normalTextureNode, this.pixelSize, this.normalEdgeStrength, this.depthEdgeStrength ); + + } + + dispose() { + + super.dispose(); + this.normalRenderTarget.dispose(); + + + } + +} + +export default PixelationPassNode; + +export const pixelationPass = ( scene, camera, pixelSize, normalEdgeStrength, depthEdgeStrength ) => nodeObject( new PixelationPassNode( scene, camera, pixelSize, normalEdgeStrength, depthEdgeStrength ) ); + +addNodeClass( 'PixelationPassNode', PixelationPassNode ); From 43cb319be0671af625189e9dd71532013dd5528b Mon Sep 17 00:00:00 2001 From: Christian Helgeson Date: Sun, 7 Jul 2024 13:26:14 -0700 Subject: [PATCH 05/24] fix renderTarget issue --- examples/webgpu_postprocessing_pixel.html | 28 ++++++++++------------- src/nodes/display/PixelationNode.js | 8 +++---- src/nodes/display/PixelationPassNode.js | 10 ++------ 3 files changed, 17 insertions(+), 29 deletions(-) diff --git a/examples/webgpu_postprocessing_pixel.html b/examples/webgpu_postprocessing_pixel.html index 103c41f0eff659..48d57bae7031bf 100644 --- a/examples/webgpu_postprocessing_pixel.html +++ b/examples/webgpu_postprocessing_pixel.html @@ -33,7 +33,7 @@ import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; - import { pass, normalPass, uniform, pixelation } from 'three/tsl'; + import { pass, normalPass, uniform, pixelation, pixelationPass } from 'three/tsl'; let camera, scene, renderer, composer, crystalMesh, clock; let gui, params; @@ -68,8 +68,8 @@ function addBox( boxSideLength, x, z, rotation ) { const mesh = new THREE.Mesh( new THREE.BoxGeometry( boxSideLength, boxSideLength, boxSideLength ), boxMaterial ); - mesh.castShadow = true; - mesh.receiveShadow = true; + mesh.castShadow = false; + mesh.receiveShadow = false; mesh.rotation.y = rotation; mesh.position.y = boxSideLength / 2; mesh.position.set( x, boxSideLength / 2 + .0001, z ); @@ -101,8 +101,8 @@ specular: 0xffffff } ) ); - crystalMesh.receiveShadow = true; - crystalMesh.castShadow = true; + crystalMesh.receiveShadow = false; + crystalMesh.castShadow = false; scene.add( crystalMesh ); // lights @@ -111,7 +111,7 @@ const directionalLight = new THREE.DirectionalLight( 0xfffecd, 1.5 ); directionalLight.position.set( 100, 100, 100 ); - directionalLight.castShadow = true; + directionalLight.castShadow = false; directionalLight.shadow.mapSize.set( 2048, 2048 ); scene.add( directionalLight ); @@ -120,10 +120,10 @@ const target = spotLight.target; scene.add( target ); target.position.set( 0, 0, 0 ); - spotLight.castShadow = true; + spotLight.castShadow = false; scene.add( spotLight ); - renderer = new THREE.WebGPURenderer(); + renderer = new THREE.WebGPURenderer({ antialias: false }); renderer.shadowMap.enabled = true; //renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( window.innerWidth, window.innerHeight ); @@ -131,19 +131,15 @@ document.body.appendChild( renderer.domElement ); const effectController = { - pixelSize: 14, + pixelSize: uniform(14), normalEdgeStrength: uniform(0.3), depthEdgeStrength: uniform(0.4) } composer = new THREE.PostProcessing( renderer ); - const scenePass = normalPass( scene, camera ); - const scenePassColor = scenePass.getTextureNode(); - const scenePassDepth = scenePass.getTextureDepthNode(); - const scenePassNormal = scenePass.getTextureNormalNode(); - const pixelationPass = scenePassColor.pixelation( scenePassDepth, scenePassNormal, 4, effectController.normalEdgeStrength, effectController.depthEdgeStrength); - composer.outputNode = pixelationPass; + const scenePass = pixelationPass( scene, camera, effectController.pixelSize, effectController.normalEdgeStrength, effectController.depthEdgeStrength ); + composer.outputNode = scenePass; window.addEventListener( 'resize', onWindowResize ); const controls = new OrbitControls( camera, renderer.domElement ); @@ -152,7 +148,7 @@ // gui gui = new GUI(); - gui.add( pixelationPass.pixelSizeNode, 'value', 0, 14, 1).name( 'Pixel Size' ); + gui.add( effectController.pixelSize, 'value', 0, 14, 1).name( 'Pixel Size' ); gui.add( effectController.normalEdgeStrength, 'value', 0, 2, 0.05 ).name( 'Normal Edge Strength' ); gui.add( effectController.depthEdgeStrength, 'value', 0, 1, 0.05 ).name( 'Depth Edge Strength' ); diff --git a/src/nodes/display/PixelationNode.js b/src/nodes/display/PixelationNode.js index 636407e2d575ab..a79fb37ff77167 100644 --- a/src/nodes/display/PixelationNode.js +++ b/src/nodes/display/PixelationNode.js @@ -6,7 +6,6 @@ import { uniform } from '../core/UniformNode.js'; import { dot, clamp, smoothstep, sign, step, floor } from '../math/MathNode.js'; import { float, If } from '../shadernode/ShaderNode.js'; import { Vector4 } from '../../math/Vector4.js'; -import { temp } from '../Nodes.js'; import { property } from '../core/PropertyNode.js'; class PixelationNode extends TempNode { @@ -32,9 +31,8 @@ class PixelationNode extends TempNode { updateBefore() { const map = this.textureNode.value; - const width = map.image.width / this.pixelSizeNode.value; - const height = map.image.height / this.pixelSizeNode.value; - + const width = map.image.width; + const height = map.image.height; this._resolution.value.set( width, height, 1 / width, 1 / height ); } @@ -141,7 +139,7 @@ class PixelationNode extends TempNode { } -export const pixelation = ( node, depthNode, normalNode, pixelSize = 4, normalEdgeStrength = 0.3, depthEdgeStrength = 0.4 ) => nodeObject( new PixelationNode( nodeObject( node ).toTexture(), nodeObject( depthNode ).toTexture(), nodeObject( normalNode ).toTexture(), pixelSize, normalEdgeStrength, depthEdgeStrength ) ); +export const pixelation = ( node, depthNode, normalNode, pixelSize = 14, normalEdgeStrength = 0.3, depthEdgeStrength = 0.4 ) => nodeObject( new PixelationNode( nodeObject( node ).toTexture(), nodeObject( depthNode ).toTexture(), nodeObject( normalNode ).toTexture(), nodeObject(pixelSize), nodeObject(normalEdgeStrength), nodeObject(depthEdgeStrength) ) ); addNodeElement( 'pixelation', pixelation ); diff --git a/src/nodes/display/PixelationPassNode.js b/src/nodes/display/PixelationPassNode.js index 2d922fe73c4ebd..0dfb8148385265 100644 --- a/src/nodes/display/PixelationPassNode.js +++ b/src/nodes/display/PixelationPassNode.js @@ -2,9 +2,6 @@ import { nodeObject } from '../shadernode/ShaderNode.js'; import { addNodeClass } from '../core/Node.js'; import { NodeUpdateType } from '../core/constants.js'; import PassNode from './PassNode.js'; -import { RenderTarget } from '../../core/RenderTarget.js'; -import { HalfFloatType/*, FloatType*/ } from '../../constants.js'; -import { PassTextureNode } from './PassNode.js'; import { Vector2 } from '../../math/Vector2.js'; import { MeshNormalNodeMaterial } from '../Nodes.js'; @@ -36,7 +33,7 @@ class PixelationPassNode extends PassNode { const size = renderer.getSize( _size ); const pixelSize = this.pixelSize.value ? this.pixelSize.value : this.pixelSize; - this.setSize( pixelSize >= 1 ? size.width / pixelSize : size.width, pixelSize >= 1 ? size.height / pixelSize : size.height ); + this.setSize( pixelSize >= 1 ? (size.width / pixelSize) | 0 : size.width, pixelSize >= 1 ? (size.height / pixelSize) | 0 : size.height ); const currentRenderTarget = renderer.getRenderTarget(); @@ -44,7 +41,6 @@ class PixelationPassNode extends PassNode { this._cameraFar.value = camera.far; renderer.setRenderTarget( this.renderTarget ); - console.log(this.renderTarget) renderer.render( scene, camera ); @@ -61,14 +57,12 @@ class PixelationPassNode extends PassNode { setSize( width, height ) { super.setSize( width, height ); - this.normalRenderTarget.setSize( width * this._pixelRatio, height * this._pixelRatio ); } setup() { - const pass = super.getTextureDepthNode(); - console.log( this.normalEdgeStrength ) + const pass = super.getTextureNode(); return pass.pixelation( this._depthTextureNode, this._normalTextureNode, this.pixelSize, this.normalEdgeStrength, this.depthEdgeStrength ); } From 5648328c26d7af29c8f0395d9c2583f55895565e Mon Sep 17 00:00:00 2001 From: Christian Helgeson Date: Wed, 10 Jul 2024 16:23:03 -0700 Subject: [PATCH 06/24] auto-mrt version of pixelation --- examples/webgpu_postprocessing_pixel.html | 23 +++-- src/nodes/display/PassNode.js | 64 +------------ src/nodes/display/PixelationNode.js | 109 +++++++++++++++++++--- src/nodes/display/PixelationPassNode.js | 84 ----------------- 4 files changed, 116 insertions(+), 164 deletions(-) delete mode 100644 src/nodes/display/PixelationPassNode.js diff --git a/examples/webgpu_postprocessing_pixel.html b/examples/webgpu_postprocessing_pixel.html index 48d57bae7031bf..207256887dfd96 100644 --- a/examples/webgpu_postprocessing_pixel.html +++ b/examples/webgpu_postprocessing_pixel.html @@ -33,9 +33,9 @@ import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; - import { pass, normalPass, uniform, pixelation, pixelationPass } from 'three/tsl'; + import { pass, mrt, output, pixelation, transformedNormalWorld, normalWorld, normalView, normalLocal, uniform, directionToColor, MeshNormalNodeMaterial} from 'three/tsl'; - let camera, scene, renderer, composer, crystalMesh, clock; + let camera, scene, renderer, postProcessing, crystalMesh, clock; let gui, params; init(); @@ -136,10 +136,19 @@ depthEdgeStrength: uniform(0.4) } - composer = new THREE.PostProcessing( renderer ); - - const scenePass = pixelationPass( scene, camera, effectController.pixelSize, effectController.normalEdgeStrength, effectController.depthEdgeStrength ); - composer.outputNode = scenePass; + postProcessing = new THREE.PostProcessing( renderer ); + + const scenePass = pass( scene, camera ); + const normalMaterial = new MeshNormalNodeMaterial(); + console.log(normalMaterial); + scenePass.setMRT( mrt( { + output: output, + normal: directionToColor(normalView), + } ) ); + const colorNode = scenePass.getTextureNode('output'); + const depthNode = scenePass.getTextureNode('depth'); + const normalNode = scenePass.getTextureNode('normal') + postProcessing.outputNode = scenePass.getTextureNode('output').pixelation( depthNode, normalNode, effectController.pixelSize, effectController.normalEdgeStrength, effectController.depthEdgeStrength ); window.addEventListener( 'resize', onWindowResize ); const controls = new OrbitControls( camera, renderer.domElement ); @@ -188,7 +197,7 @@ } - composer.render(); + postProcessing.render(); } diff --git a/src/nodes/display/PassNode.js b/src/nodes/display/PassNode.js index 81febe399651e0..a072092110107e 100644 --- a/src/nodes/display/PassNode.js +++ b/src/nodes/display/PassNode.js @@ -10,12 +10,10 @@ import { HalfFloatType/*, FloatType*/ } from '../../constants.js'; import { Vector2 } from '../../math/Vector2.js'; import { DepthTexture } from '../../textures/DepthTexture.js'; import { RenderTarget } from '../../core/RenderTarget.js'; -import { MeshNormalMaterial } from '../../materials/MeshNormalMaterial.js'; -import MeshNormalNodeMaterial from '../materials/MeshNormalNodeMaterial.js'; const _size = new Vector2(); -export class PassTextureNode extends TextureNode { +class PassTextureNode extends TextureNode { constructor( passNode, texture ) { @@ -95,16 +93,6 @@ class PassNode extends TempNode { this.renderTarget = renderTarget; - if ( this.scope === PassNode.NORMAL ) { - - const normalTarget = new RenderTarget( this._width * this._pixelRatio, this._height * this._pixelRatio, { type: HalfFloatType } ); - normalTarget.texture.name = 'PostProcessingNormal'; - this.normalRenderTarget = normalTarget; - - this._normalTextureNode = nodeObject( new PassTextureNode( this, normalTarget.texture ) ); - - } - this.updateBeforeType = NodeUpdateType.FRAME; this._textures = { @@ -181,12 +169,6 @@ class PassNode extends TempNode { } - getTextureNormalNode() { - - return this._normalTextureNode ? this._normalTextureNode : null; - - } - getViewZNode() { if ( this._viewZNode === null ) { @@ -232,27 +214,7 @@ class PassNode extends TempNode { this.renderTarget.depthTexture.isMultisampleRenderTargetTexture = this.renderTarget.samples > 1; - switch( this.scope ) { - - case PassNode.COLOR: { - - return this.getTextureNode(); - - }; - - case PassNode.DEPTH: { - - return this.getLinearDepthNode(); - - }; - - case PassNode.NORMAL: { - - return this.getTextureNormalNode(); - - } - - } + return this.scope === PassNode.COLOR ? this.getTextureNode() : this.getLinearDepthNode(); } @@ -262,9 +224,8 @@ class PassNode extends TempNode { const { scene, camera } = this; this._pixelRatio = renderer.getPixelRatio(); - + const size = renderer.getSize( _size ); - this.setSize( size.width, size.height ); const currentRenderTarget = renderer.getRenderTarget(); @@ -278,16 +239,6 @@ class PassNode extends TempNode { renderer.render( scene, camera ); - if ( this._normalTextureNode ) { - - renderer.setRenderTarget( this.normalRenderTarget ); - const oldMaterial = scene.overrideMaterial; - scene.overrideMaterial = new MeshNormalNodeMaterial(); - renderer.render( scene, camera ); - scene.overrideMaterial = oldMaterial; - - } - renderer.setRenderTarget( currentRenderTarget ); renderer.setMRT( currentMRT ); @@ -302,9 +253,6 @@ class PassNode extends TempNode { const effectiveHeight = this._height * this._pixelRatio; this.renderTarget.setSize( effectiveWidth, effectiveHeight ); - if ( this.normalRenderTarget ) { - this.normalRenderTarget.setSize( effectiveWidth, effectiveHeight ); - } } @@ -319,23 +267,19 @@ class PassNode extends TempNode { dispose() { this.renderTarget.dispose(); - if (this.normalRenderTarget) { - this.normalRenderTarget.dispose() - } } + } PassNode.COLOR = 'color'; PassNode.DEPTH = 'depth'; -PassNode.NORMAL = 'normal' export default PassNode; export const pass = ( scene, camera, options ) => nodeObject( new PassNode( PassNode.COLOR, scene, camera, options ) ); export const passTexture = ( pass, texture ) => nodeObject( new PassTextureNode( pass, texture ) ); export const depthPass = ( scene, camera ) => nodeObject( new PassNode( PassNode.DEPTH, scene, camera ) ); -export const normalPass = ( scene, camera ) => nodeObject( new PassNode( PassNode.NORMAL, scene, camera ) ); addNodeClass( 'PassNode', PassNode ); \ No newline at end of file diff --git a/src/nodes/display/PixelationNode.js b/src/nodes/display/PixelationNode.js index a79fb37ff77167..687d09a813b4c9 100644 --- a/src/nodes/display/PixelationNode.js +++ b/src/nodes/display/PixelationNode.js @@ -7,6 +7,13 @@ import { dot, clamp, smoothstep, sign, step, floor } from '../math/MathNode.js'; import { float, If } from '../shadernode/ShaderNode.js'; import { Vector4 } from '../../math/Vector4.js'; import { property } from '../core/PropertyNode.js'; +import { sobel } from './SobelOperatorNode.js'; +import QuadMesh from '../../renderers/common/QuadMesh.js'; +import { RenderTarget } from '../../core/RenderTarget.js'; +import { passTexture } from './PassNode.js'; + +const createEdgesQuad = new QuadMesh(); +const lowerResolutionQuad = new QuadMesh(); class PixelationNode extends TempNode { @@ -14,30 +21,87 @@ class PixelationNode extends TempNode { super(); + // Input textures this.textureNode = textureNode; this.depthNode = depthNode; this.normalNode = normalNode; + // Input uniforms this.pixelSizeNode = pixelSizeNode; this.normalEdgeStrength = normalEdgeStrength; this.depthEdgeStrength = depthEdgeStrength; + // Private uniforms + this._resolution = uniform( new Vector4() ); + + // Intermediary render targets + this._createEdgesRT = new RenderTarget(); + this._createEdgesRT.texture.name = 'PixelationNode.renderEdges'; + this._lowerResolutionRT = new RenderTarget(); + this._lowerResolutionRT.texture.name = 'PixelationNode.lowerResolution'; + + // Output textures + this._outputTextureNode = passTexture( this, this._lowerResolutionRT.texture ); + this.updateBeforeType = NodeUpdateType.RENDER; - this._resolution = uniform( new Vector4() ); + } + + setSize( width, height ) { + + this._createEdgesRT.setSize( width, height ); + this._lowerResolutionRT.setSize( width, height ); } - updateBefore() { + updateBefore( frame ) { + + const { renderer } = frame; + + const textureNode = this.textureNode; + const map = textureNode.value; + + // Set the internal resolution uniform + const adjustedWidth = map.image.width / this.pixelSizeNode.value; + const adjustedHeight = map.image.height / this.pixelSizeNode.value; + this._resolution.value.set( adjustedWidth, adjustedHeight, 1 / adjustedWidth, 1 / adjustedHeight ); + + const currentRenderTarget = renderer.getRenderTarget(); + const currentMRT = renderer.getMRT(); + const currentTexture = textureNode.value; + + createEdgesQuad.material = this._createEdgesMaterial; + lowerResolutionQuad.material = this._lowerResolutionMaterial; + + // Set size of render edges to match size of initial render target. + this.setSize( map.image.width, map.image.height ); + + const textureType = map.type; + this._createEdgesRT.texture.type = textureType; + this._lowerResolutionRT.texture.type = textureType; - const map = this.textureNode.value; - const width = map.image.width; - const height = map.image.height; - this._resolution.value.set( width, height, 1 / width, 1 / height ); + // Apply create edges post-process step to createEdgesQuad. + renderer.setMRT( null ); + renderer.setRenderTarget( this._createEdgesRT ); + createEdgesQuad.render( renderer ); + // Set input of next pass to output of last step. + textureNode.value = this._createEdgesRT.texture; + + // Apply lower resolution post-process step to lowerResolutionQuad. + renderer.setRenderTarget( this._lowerResolutionRT ); + lowerResolutionQuad.render( renderer ) + + // Render resolution pixelation to output. + renderer.setRenderTarget( currentRenderTarget ); + renderer.setMRT( currentMRT ); + + // Set textureNode back to intial input texture for next pass. + textureNode.value = currentTexture; + } - setup() { + setup( builder ) { const { textureNode, depthNode, normalNode } = this; @@ -45,8 +109,12 @@ class PixelationNode extends TempNode { const uvNodeDepth = depthNode.uvNode || uv(); const uvNodeNormal = normalNode.uvNode || uv(); + const pixelizeUV = ( coord ) => floor( coord.mul( this._resolution.xy ) ).div( this._resolution.xy ); + const sampleTexture = () => textureNode.uv( uvNodeTexture ); + const samplePixel = () => textureNode.uv( pixelizeUV( uvNodeTexture ) ); + const sampleDepth = ( x, y ) => depthNode.uv( uvNodeDepth.add( vec2( x, y ).mul( this._resolution.zw ) ) ).r; const sampleNormal = ( x, y ) => normalNode.uv( uvNodeNormal.add( vec2( x, y ).mul( this._resolution.zw ) ) ).rgb.mul( 2.0 ).oneMinus(); @@ -76,7 +144,7 @@ class PixelationNode extends TempNode { // Only the shallower pixel should detect the normal edge. const depthIndicator = clamp( sign( depthDiff.mul(0.25).add(.0025) ), 0.0, 1.0 ); - +333 return float( 1.0 ).sub( dot( normal, neighborNormal ) ).mul( depthIndicator ).mul( normalIndicator ); }; @@ -94,7 +162,7 @@ class PixelationNode extends TempNode { } - const pixelation = tslFn( () => { + const createEdges = tslFn( () => { const texel = sampleTexture(); @@ -124,16 +192,31 @@ class PixelationNode extends TempNode { }); - const strength = dei.greaterThan( 0 ).cond( float( 1.0 ).sub( dei.mul( this.depthEdgeStrength ) ), nei.mul( this.normalEdgeStrength ).add( 1 ) ); - return texel.mul( strength ); + const color = texel.mul( strength ); + + return color; } ); - const outputNode = pixelation(); + const lowerResolution = tslFn(() => { + + return samplePixel() + + }) + + const createEdgesMaterial = this._createEdgesMaterial || ( this._createEdgesMaterial = builder.createNodeMaterial() ); + createEdgesMaterial.fragmentNode = createEdges().context( builder.getSharedContext() ); + createEdgesMaterial.needsUpdate = true; + + const lowerResolutionMaterial = this._lowerResolutionMaterial || ( this._lowerResolutionMaterial = builder.createNodeMaterial() ); + lowerResolutionMaterial.fragmentNode = lowerResolution().context( builder.getSharedContext() ); + + const properties = builder.getNodeProperties( this ); + properties.textureNode = textureNode; - return outputNode; + return this._outputTextureNode; } diff --git a/src/nodes/display/PixelationPassNode.js b/src/nodes/display/PixelationPassNode.js deleted file mode 100644 index 0dfb8148385265..00000000000000 --- a/src/nodes/display/PixelationPassNode.js +++ /dev/null @@ -1,84 +0,0 @@ -import { nodeObject } from '../shadernode/ShaderNode.js'; -import { addNodeClass } from '../core/Node.js'; -import { NodeUpdateType } from '../core/constants.js'; -import PassNode from './PassNode.js'; -import { Vector2 } from '../../math/Vector2.js'; -import { MeshNormalNodeMaterial } from '../Nodes.js'; - -const _size = new Vector2(); - -class PixelationPassNode extends PassNode { - - constructor( scene, camera, pixelSize, normalEdgeStrength, depthEdgeStrength ) { - - super( 'normal', scene, camera ); - - this.pixelSize = pixelSize; - this.normalEdgeStrength = normalEdgeStrength; - this.depthEdgeStrength = depthEdgeStrength; - - this.updateBeforeType = NodeUpdateType.FRAME; - - this.isPixelationPassNode = true; - - } - - updateBefore( frame ) { - - const { renderer } = frame; - const { scene, camera } = this; - - this._pixelRatio = renderer.getPixelRatio(); - - const size = renderer.getSize( _size ); - const pixelSize = this.pixelSize.value ? this.pixelSize.value : this.pixelSize; - - this.setSize( pixelSize >= 1 ? (size.width / pixelSize) | 0 : size.width, pixelSize >= 1 ? (size.height / pixelSize) | 0 : size.height ); - - const currentRenderTarget = renderer.getRenderTarget(); - - this._cameraNear.value = camera.near; - this._cameraFar.value = camera.far; - - renderer.setRenderTarget( this.renderTarget ); - - renderer.render( scene, camera ); - - renderer.setRenderTarget( this.normalRenderTarget ); - const oldMaterial = scene.overrideMaterial; - scene.overrideMaterial = new MeshNormalNodeMaterial(); - renderer.render( scene, camera ); - scene.overrideMaterial = oldMaterial; - - renderer.setRenderTarget( currentRenderTarget ); - - } - - setSize( width, height ) { - - super.setSize( width, height ); - - } - - setup() { - - const pass = super.getTextureNode(); - return pass.pixelation( this._depthTextureNode, this._normalTextureNode, this.pixelSize, this.normalEdgeStrength, this.depthEdgeStrength ); - - } - - dispose() { - - super.dispose(); - this.normalRenderTarget.dispose(); - - - } - -} - -export default PixelationPassNode; - -export const pixelationPass = ( scene, camera, pixelSize, normalEdgeStrength, depthEdgeStrength ) => nodeObject( new PixelationPassNode( scene, camera, pixelSize, normalEdgeStrength, depthEdgeStrength ) ); - -addNodeClass( 'PixelationPassNode', PixelationPassNode ); From 57a011fff03483d9093139f87289f07ec55fead2 Mon Sep 17 00:00:00 2001 From: Christian Helgeson Date: Wed, 10 Jul 2024 16:50:16 -0700 Subject: [PATCH 07/24] cleanup --- examples/webgpu_postprocessing_pixel.html | 35 ++++++++----- examples/webgpu_postprocessing_sobel.html | 2 - src/nodes/Nodes.js | 1 - src/nodes/display/PassNode.js | 5 +- src/nodes/display/PixelationNode.js | 60 +++++++++++++---------- 5 files changed, 60 insertions(+), 43 deletions(-) diff --git a/examples/webgpu_postprocessing_pixel.html b/examples/webgpu_postprocessing_pixel.html index 207256887dfd96..5fdb36aff8cf9c 100644 --- a/examples/webgpu_postprocessing_pixel.html +++ b/examples/webgpu_postprocessing_pixel.html @@ -29,14 +29,14 @@ - \ No newline at end of file + diff --git a/src/nodes/display/SobelOperatorNode.js b/src/nodes/display/SobelOperatorNode.js index 860912d7142dc5..b798adfd3954ba 100644 --- a/src/nodes/display/SobelOperatorNode.js +++ b/src/nodes/display/SobelOperatorNode.js @@ -12,8 +12,6 @@ class SobelOperatorNode extends TempNode { constructor( textureNode ) { - console.log(textureNode); - super(); this.textureNode = textureNode; diff --git a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js index 3882888ec49eb0..d1b34adc393cb3 100644 --- a/src/renderers/webgpu/nodes/WGSLNodeBuilder.js +++ b/src/renderers/webgpu/nodes/WGSLNodeBuilder.js @@ -1041,7 +1041,6 @@ ${ flowData.code } this.vertexShader = this._getWGSLVertexCode( shadersData.vertex ); this.fragmentShader = this._getWGSLFragmentCode( shadersData.fragment ); - console.log(this.fragmentShader) } else { From 9269101f06026523a4c9bc25a611261d6acae011 Mon Sep 17 00:00:00 2001 From: Christian Helgeson Date: Wed, 10 Jul 2024 17:20:44 -0700 Subject: [PATCH 09/24] screenshot and cleanup --- .../screenshots/webgpu_postprocessing_pixel.jpg | Bin 0 -> 3413 bytes examples/webgpu_postprocessing_pixel.html | 4 ++-- src/nodes/display/PixelationNode.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 examples/screenshots/webgpu_postprocessing_pixel.jpg diff --git a/examples/screenshots/webgpu_postprocessing_pixel.jpg b/examples/screenshots/webgpu_postprocessing_pixel.jpg new file mode 100644 index 0000000000000000000000000000000000000000..001fe203e0b94e476d12e5c1c76aa979a0fbc77f GIT binary patch literal 3413 zcmex=iF;N$`UAd82aiwDGkXk%h!W@hDLXJZFTlSKSKz#z!M@QZN*Gov5_lOQ9r zAmjfdjEjJ7WCc47=uik?WMXDvWn%|Afm;CRY-VPlV_AVN1*)tC$}zAAvI;30I#U-U>$dGXcJ4ZK_{h;?$4{I*b?NeztJkjIxOwa0qsLF4K70P+<*SdMK7aZ8?fZ|P zzZe;qA>IL!82$lzoRJ9>=IkK_0Zp(e6?1osf!KmTtr@Gvt1BaB&)!Jgq?{R@T#ObuKN n8l%)`5R9gR(Tp%!7K|2$qcy^4Z8%yRj@E|5u{QkA@c$+N?bpn5 literal 0 HcmV?d00001 diff --git a/examples/webgpu_postprocessing_pixel.html b/examples/webgpu_postprocessing_pixel.html index 5fdb36aff8cf9c..b269cf47a832ba 100644 --- a/examples/webgpu_postprocessing_pixel.html +++ b/examples/webgpu_postprocessing_pixel.html @@ -33,7 +33,7 @@ import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; - import { pass, mrt, output, pixelation, transformedNormalWorld, normalWorld, normalView, normalLocal, uniform, directionToColor, MeshNormalNodeMaterial} from 'three/tsl'; + import { pass, mrt, output, normalView, uniform, directionToColor } from 'three/tsl'; let camera, scene, renderer, postProcessing, crystalMesh, clock; let gui, effectController; @@ -147,7 +147,7 @@ const colorNode = scenePass.getTextureNode( 'output' ); const depthNode = scenePass.getTextureNode( 'depth' ); - const normalNode = scenePass.getTextureNode( 'normal' ) + const normalNode = scenePass.getTextureNode( 'normal' ); postProcessing.outputNode = scenePass.getTextureNode('output').pixelation( depthNode, normalNode, effectController.pixelSize, effectController.normalEdgeStrength, effectController.depthEdgeStrength ); window.addEventListener( 'resize', onWindowResize ); diff --git a/src/nodes/display/PixelationNode.js b/src/nodes/display/PixelationNode.js index e6290baba4e9a1..4c29a8e3a73f80 100644 --- a/src/nodes/display/PixelationNode.js +++ b/src/nodes/display/PixelationNode.js @@ -152,7 +152,7 @@ class PixelationNode extends TempNode { const normalIndicator = clamp( smoothstep( - 0.01, 0.01, normalDiff ), 0.0, 1.0 ); // Only the shallower pixel should detect the normal edge. - + const depthIndicator = clamp( sign( depthDiff.mul( .25 ).add( .0025 ) ), 0.0, 1.0 ); return float( 1.0 ).sub( dot( normal, neighborNormal ) ).mul( depthIndicator ).mul( normalIndicator ); From 4db388a52497d7f6f84d47bd5e7ac0fdabc6c53e Mon Sep 17 00:00:00 2001 From: Christian Helgeson Date: Wed, 10 Jul 2024 17:40:23 -0700 Subject: [PATCH 10/24] more cleanup --- examples/webgpu_postprocessing_pixel.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/webgpu_postprocessing_pixel.html b/examples/webgpu_postprocessing_pixel.html index b269cf47a832ba..a63cbca5cc7f77 100644 --- a/examples/webgpu_postprocessing_pixel.html +++ b/examples/webgpu_postprocessing_pixel.html @@ -135,7 +135,7 @@ normalEdgeStrength: uniform( 0.3 ), depthEdgeStrength: uniform( 0.4 ), pixelAlignedPanning: true - } + }; postProcessing = new THREE.PostProcessing( renderer ); @@ -145,7 +145,6 @@ normal: directionToColor(normalView), } ) ); - const colorNode = scenePass.getTextureNode( 'output' ); const depthNode = scenePass.getTextureNode( 'depth' ); const normalNode = scenePass.getTextureNode( 'normal' ); postProcessing.outputNode = scenePass.getTextureNode('output').pixelation( depthNode, normalNode, effectController.pixelSize, effectController.normalEdgeStrength, effectController.depthEdgeStrength ); From 036af7ef5573c2e942d0a6a9e2740908e800bb16 Mon Sep 17 00:00:00 2001 From: Christian Helgeson Date: Wed, 10 Jul 2024 17:53:28 -0700 Subject: [PATCH 11/24] differentiate lighting from webgl version and modify to remove lighting errors --- .../webgpu_postprocessing_pixel.jpg | Bin 3413 -> 18027 bytes examples/webgpu_postprocessing_pixel.html | 10 ++++------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/examples/screenshots/webgpu_postprocessing_pixel.jpg b/examples/screenshots/webgpu_postprocessing_pixel.jpg index 001fe203e0b94e476d12e5c1c76aa979a0fbc77f..92b91c44ded097923d19d21576f485eb96b421c2 100644 GIT binary patch literal 18027 zcmeIa2UJsCw=TR96f1~`2qIC6fGCK7AT9c$AfO;1ptMLA5RfLFZ9}AnBA~RWh;$;- zYXqrMLhn8FnovSQ+Q0QZ-}j&IeCL1fIp2TB9pm0{!$`or*4lfnwdR`hna`Y&G02z% zj%uoDr~yn&0Kf$O0E`jf6|jft*X!5ko?kB(=3k$CSy-4^_U+xb@AsSa!2W%#2Uz#* z+ka^PfrGzZ&|ilS9X$N&AAb(pvzM89??KjmtiPN8TOW+?0LOk{mnn*wi38Zf!NknL z#HeTb1j)Gfw`h={f4-RZFthC4#|nvX=qMyMGczRYUPw}?X8_a&uyE`>c2?^8K2BW= zRsq-Jm!C&}+%Nb?K`obF7hXu(((T28gD1F8^6(0u6A=|VFC%+JPX4Nb>W!Of>bEpB z_3s%N8r^?jY-Md@Yxl_B!QI32sh78p@5@&~uY=!&gvP|ajf+os_dfAc+UN8y8JStx z-wKP0OG?YiE9!pKH#9aixBTqx>Fw(u7#td&n#Rq{&do0@E)h01x3-BpB=Rm3!2IWQ zpwEAr4>WP6J-^nR_1Aou_IN>WW)7CUXQlQXyROS>;mRp+`T73ie?)&Qs68Mkt%v8b zbn7~JLP%yxnDA>#zi0Gco6w7YX-0pa(BJ377zPeAGeH~1%mKgwDgyoaYTdJgfGF@` z&;Rznm_LwQ@Drw_kNH|39&sB+wI4|{l{QyAA9pzW+WB1fgwxfI3EW{BFTP~R3_A~l zS_QgcvELLCBy08%#1>yC27k_lE^F>ZB(jrjb|g2&hi*;R2>HtmsBkYwKjNkJeeSvQ zsPtgJbK6I)%g?mVC(p9GXl^}uSK&SE{N#?%Dte0&D_T}5jmrP@$N$W=Jh0U&xq4A^ zLVNRy&*o=8LQ3Vk!#Ip_M=mW+Oy`@+8n4k)MHopRv3(hwK`{V3b4lU^O_2&t@=_$F zdqHdTD}*fx=_f)JQY`#*ONw|KY<&cdpcSRtYKnvJ&Xkl+w2-+E`lHZl*LL9moKP11E>p`xA}i zr3TltHYlalyCaT1r!%ORVYGXa)Xz|dVgC%)f15tP6@k4DhJQ|~+pGhJ;f@rM1bXxg z*{@U$z2oK@F}OS&+qwOo{StpXi)jUJf&7bH-#5&q_?IvZrI?Tn;ex4qQ>yyuLb0MjEV94vP3?M*~WR%j; z&IV1}qw~TR`jnZeNvpWDz4m9tJyLV^e(Vm7MM)vLqr}iTQ35zDd9AR|-^tO{^+I-Q zy1=t=peadgO?mlCcw)%q(MkB!k!}VsU)@C8!c3XCf76yUAi<^-+qLJ`%v5!cTv%H< zaUXxc(wi*apOI{7-*Z>vQQiQxZA`w@-Mtb1t?9=)ySJ*rz)Eq1&6k26yRs0QvNu`Q z5%2b}T*C<_a~NA}<{B8|Uxq&(Hc>(MYA5TJPa15^v3{nvqFDW<38m9}=Yy1t0zelR zm$a6r$c&$FX8Z3)?J+xPb&P+gnXj+zyms40p}(i;KAwADVx2}1>T{K_hyDO9gw91W zp_owM2m&*iuEqcqmY5@IY^C0)XXU@q4O5YWrCJjBs=6W*7N}KNsQvXlk`Dv8p5n*= z_MBw^ID^1C))ZPg;u{rg%kpFZEU~oYK(UT-B?4=NztfRfy3)eRwzzEgWB&W!9SxO_ z+6iqYZZ5a=;Z9o3kKJP0O@3=?nEyml~#7$zK;j0hjrG~C2tS@%VwClyNYR&egASG zM^T^b9g+4_gtQ-dL&8;%>H9+kCWIWaX?nqOcE4te$)}a7h^hT$V<=01MLaA>fCPTL z$sZSF#^1{{-_r9FLCl$R=u2Mud1q-vPS@}PDW`V)R589L-F;t5G4AX8=&E`7vjV>`-+exO+C zd`p4+UwhG2iZza5qUrDH1D8hIp2a>R@ul{~X34C&#i*e|>8DAGcOeNaywcV~{SKtX zsdSwgP2Vqg_M5znevV$sP0?!ovSb4!&sSQa$^o6H(OQ$aSsm)cIP6CT@Fi}m+}t9( z9yg`^IX`7rqJ2v}E>ZjbW0ocTuAD%7Ph|{Il9m*Ba1nOgWzB;D?DKIRX2~|8sLc@O zzx%bUh_8O#`Y@jjSLl$w;ADDPKc$Z%foy&$G5!rrn)uv*#dAhAe9LATw*;P@` zlbG=_^WP0Qu~W)zMLVrw>#CcIU(}-6F z0%g20CVhcyl*+b3}3j z&M&UI{;$ITc`O%cy@~e<#`Vhr{c@kv#k8T>kr9W>jUjJYrVO6(2%)mjtf#b()wdK? z1l}5E9}_C2eWb&OP15twO_C=Yj^8>ak0ZzqXN#T$n9c#r#cqBFVLxkT%!ancBM6ey z%Jc$zs{jJCN#cQgtH?(-eYV!ohX8};kB_Re@NNogA5iA-vio2p&s%e}y6S6hs^h70 z3wNfypQ5q^-3Zb8!XB&{)JwgdM^slQTri0NsfJhuy%BeeJXSmIRrk#sb2V2S6&h2f zbG&6}hI1?mzEZdBY*@%K?$3Pj;+h)rFGr^&obz4m9Ftn#3r81JXqXoA9d^?xcF=%P z&zVaWGXS*`f?+zJE485M^wx6&bpT)o*!oNhRvwk8Ocmk>f}JI%OUpVhe2&!MGUM;t z|4@7XnaG zRtIxZwSh@D4YY*b+gTsQ!mjYM`-jgrlfmhI$j@51F=q{#99!GIH7)6DpJV4rmBm%; zy%eWTZsf7gD^v?^UHVr5sBq!enqJ{cgAZC2gGTjEwjNb}qUd&BL@!0wrzqiFw~Hl~ zD#$O)06zLs7yuKDDh|)H0Ow}{xA?xT!0_ST3G|@lbt(&pPg}l^Ad_L$tQRWz9}sx^ z=dg`l@dNQ2HwDOkY0(G2(uPy7qeVF7J1XCr4N#fy=iI2zJ$MjU!`N0MG&*Z!7b^10 zvqpx78;GbYb89w{GVMhlY~DS)d<&*0epu7(Q#ZYsca-8ciE3m3FLsSW^Ce_;!p6Bv zB8ql&g&Q%CLrvPkal*TJ@H`<~Hy8IAG5RR6d^Pn5DR`3o4aS}U1O?zCU}hA4ETm^v z4z6ZCr*5(b^YS~ba(F3m4p{vqs#+5 z%xJ;Q&*@y1%+CmMDRT+aT1lrhyG?w0Y)N+EY5BUhnw28=!y_C|Bs;(Hi+7UDjqewB zY<|v>d8S!@I#zr3Ty6lfr>CQpqww~2+mj!MZRQe<4rYF^E)aPJ%*2{%nA(X?qe4J7 zi7lShRe$i4mY~yc^wQS?_$6g#qvB||Rbo%dontlVoA|;OtXOYOT<g9`cNqlNLhN9TO>p_viv{Xlw%*%JMpFZMs_2vrpNO#WZX*zBJGo)Uh${#j0w$zG# zAVwdKn#zv?TTccoH#IQ;9$)bN#H&|vavA!)`qS_qt->ctYm~bz;sUqamZEYFZ#G|C zG!4}?DJ;;>B;F=5fN7U{NdQxiJX+z6Q$MquV9k-$@Iq;x0XS48zDrif>`9!cPzim_ zeqx~^+il1r09hwVGK3w&ETlSONMcD0AOx+m{0{z0(O|Bv615EA;E!n|8fUu~1K2AK z5@Z70Ss4IaaOcBpf~7b%n6A|GiF1knK;PsaBdYCRettmRj$5w%Qt(c__!e#9s?A^? z1K8gb0xp=>Fo5ss(Ct9uItGAk{@niu6an;URnwg*qyNoq=3 z4B(IL9r_+PNq0L}l1fF6zl=9~N#{TwV*s7i0Sw?7l72kE@zCGIvFP&FX^{T;_4-ua z^|8V80uRbB=v>=(Ri>sihykFYDV=Vu1@3gN;Cl3~Na8yEpe?jo_Wy1=4;6_j%m+*JJ>neDWE<&r0!PW6FH;J-~%YAw(Cv9tBJ>e3jVk5QisG=mbL*O-Ld6BQAl_p0~iPc7wpZo-RVo1hFxh*1jk~Df(89-$#C=a_jYUU}X_g{30cEt!m7+6j_- zfa8aSn4zU^vYzaSLVov;?F`V%(XAstx1~Fxjeqal6XL63>Xb(7ORcoh*D=w?6}`1V?8~ay?R&ZRK3yPquEa-a2>SCaXeVq0T%VtVBNs0IgrD5wgx^Fj${M;(oDHQvc$X zPQldt{ln%~LHF>4xtO>eEiKu;{P|9hbvr--OHp6tKIP>jPd-3r03XnqKU~jg8A7;c z;{g~=D9h*Rz&S$kN=q8_hTZT#O6tS6*6|-N>+!!ijVSEc4f~lzyowUF3TKY{Lm`Wr z)!%l>JIlR0u73pH$JZ*>c4>U|)`Rs58jpJ8#OWF?b5iQu+(?6BbcvJ8*AD4dx?|$C z4fF5KPaR$C&9bdZAEt4vT3kLnUc)ad)>GlN%umngsFA2H?xjA+b}-aie~r^ky@jwp z75CYaja~5Nc#T8xEyJAiQC@dSs(FZm4*F@qL05C+Kclh@?c%{Y_G#4aUIs7&0m|v@ z@@cbo|9B~L{>_7AWFxqb6f;qWSE_i*gt?Bo3NNE?r;9bcyU;at@6lWfRPq7koXnPpIh5|@1XvK021yj$^#h@KP(A9P5=Oev?{u5dMsn7eA z04vWgW3q88Cc?L!&A#IV1*2E&GhAUj{yGdGCT?1i!Zrpe#XP$668H;N*I@)>5S*8m zKVScg5S-NjA52$!zyOvTFk3Qck*f^A6ERKWNtb*>)L~%&t&nGC0JiA3tj-0woJ%TK zXgTni8pjFZG6VShb)X=KzK32BwWG9A@rz|(x(4$FYdOHV3~-d20k|wTbf`4_K5G<2 zJp(xE;c^i1B_(sjOtDJfn#D~)mguoyC0_h!jKW!MeIs?rEGj@qXjGghA*UjAZog>K zZHnfic3;Suea`88;1tv5SwR*8GTnCbwlZsr&{{RTt?yxWyO}J_AL)t*gJfasBrUB@ zmvcc-wEUqo?vv%cZ)(}Mnpt`HUsO4iCAw#c32i^#I;*Ms;x^euuQ*n}CtqgpU~t)U z+#fWihu1n3-OXRbgjFusI4(R16eiiBz1?pl8P`-6d4C)!w~P+s`>GqBd0Hp$scvJp z=dr@eS;0v!Z@>?3pS-A~Dib3f_4u9t2QWaisV~AoOo9Q-UAV}jpExm7ZTP?@Xii>^7d&7%+UG}!jtZuQ>(6DTBc6yj3v$CV8t;hL0H%0PO;BD^T4PTii8G~kTa=PRr zMQ~VZ=!ps9F&!GWu)Wsw@e-x@iv7Qd&m)ripo24!4i0Lv(@qv&Cf)C!#~6uO{L)0G z`5Jjh^e@YFCg==YbYg!MTuxwz)PpQ$F2Df3u1?S+eQB($^m!es_=fx}L7J`N#$QoL zzN{;_DI`ft$<;F>pmK#GlMxHXVdUe_hFkm6<8XE{vvoHafKTA4Q(UwR|2rRO zXzYec17$+edA0wp-0ft~CgYt;E=fnYm&xy2)qD&*kNtTF93Ynq^M~l9{dUv~I;1#S z#wvUWwlQHlhH+v5*7NIuaZ`5svqTWyx%i`-n9#e6X*3swOiI%|q&|gxsH)QS1XeEa zNt=5zJJfe`10}6!!ET%DxBmUa2tHQB6vTow(U_{odeu)EKwjh$;)lA`oSzX~>?2tp zI?xAYA-5+z-4sMW1=)dQ3O7}VU*KC$;0Oaa!2X^Hs(6I84G}#jN&}zH*qv-%MmUDH z1ia?;R8A-B`#m*U^N}eq-L^XA+(n!SgWI7iDu?<6`Wd=;i*;Wz`@`6znrp%l(t37nIPq%xUxaW>9FYN#QfXl;=0?;Ca?;K^1F(0EsG&JtP!k+9(HgDr zAg^aF7|X3}(h?l{82~eEkNUVM2;_~I;raH-$Re|NZ&_)G=(T_JCQ_jgb>A9v*J3E9hY9)@YYdPWOaNkU{PqyVU33IFRvL{R|BZp~|Dth~>65|{7$6rKNLnpSPY zW`bxV@0PjP-1#|wGeDlIf zlvH=s|K(fw6bf8|2$2?c`jC3@1{);cYY6ycL9kE=PO;%1XuhgS=h>luS*J!ZfYrBt zh%>Y7Bz+7%1Z*&Zp2|H8FNAkfk>4I~Dq&F0tMkrJaq{w5RrR8|?pjAysth+x6HbOR zfZ+wAxkVcc--+0Uv~`97c|pgtA)nw&;E(HIFN7N)nwpjm5z~AS)I0)tV?87ta!$kw z<%D|DsNtWlAN9M z;(eU*?!dSO*VnfLJ4Q3Et<$y!9{c}_&)Y@}G1D~-c4*Ji89-O(KnMyfZ=z~#*uJe1 zT*d@JE||su9%G2Uq@@(L%oH12T`~lb_d-}cwuazED+3p6hHHYE>B}(GT$>$%U&_on z=ofp<+CPEULXcB2Ydj2~)N|4JzXZ}>=M0QW+~6Lxq5$fbhS#5u-V9e7{85q zT~sZ1JLZrR-DFhttb6%T$E_%~Tt6cjd}o$~?NF}mbZ&$$XGJw;W zyD1VY=EHW=4B!X$lmq67KlqWV)|t0DTZ5+;IAuL(cpBcN^i}iQr!qsmd*7a2JA;!K z%nOV#f9u#4C*~wHFrMrEBNr?cvaJA?{`aZshE{KQXPl-(9h@4Aq0_4a3s`cXZbvM8q! zX}*Vi&#`yC2}2Ux7e8fpzfE!;E}q*J9-Ls0$a#ah5VQVqyJ@9>0Z2_yJRx1_+aL!I zk?Fx1s#(j6Jp{DOf{G6b3hab+?m^ZSwkx1OyoC9V7|>75Xl$=$pH6rqLp#<{cS`Yc zlyMX9iHMCqGMV9W=A!Wj-_Fd84D7C`mnL@ZCN&`r7o9?|EOSeZjm0B1%WU@DY`k$B ztTY!sDQv8gxtcukQsocQSo+9_|E1nJbvL7y##;;^l|V7-n}mWkRX7z2Ew6JGsT@^Og#6dWcIw&ur56$5GIP2a-mW z$>*)VI-I`qo|uC!#h#$W$T)5^31rrNiUbyy5pcacP3b2uzo}d9#0_mS0HjBR;t%@O z{-VsrefdtSmg6SY_TuOB@5ej=c!Uy(Vt&1!Cam2wYHR}(ao`EkYsB6qf<~r| ziB=hlk48_civwX%FSsZ&MR!C@D-I>$O^z&>60cU&Rk6P8#c4@EQrPy={ z??+wD)7gIC26HG^y;Ceq#dFn0xre^~W-u+H2x|yG208x-T!oz4RhMlBq_;pSQ zezsUdTKzbtR-^S35o zJg3XJX6!VS&SN$uTN3q1NEf_hn7;vRxC+DRa46;dh~F9dPPKpft)@F{FF!yCp|t_Z zP{=wUNT)?e{r&|jiiM6E1oaT>9-NQTP5(Ccm;q-U{dH4=lx&&EYe)P$J5jOQ>{?!t@=X6!m}6ZV>Fav-0xUQ9e8kuwlXr*vl8iuV})Gp zs|ve1#PKC#m%4Q}lTj0r+pdY<8<|@i)1J?Ne*1bmi)GLL84jc1dQ0IqFWmj|(dArX zwbvI)RF0r?lsZ4Kcj=JMAgFyv%5!i{twtvlK8vMQLpC}!9zm=@WbOJ)<{UbecV0u{ z@pN=hEz{?4&WN041dlGiQYNvoQyf?8Dypcz`IBs?BpHfaP@8Rz*^Ak_IcqB z?9smM0OJY6iWf)prUbQu@7+jwcK75nfc3fA4O8l8bf?bY-}?cFpvYv9!oHwvdNW<1 ztWU;7>pl0h>~L^diP4#Wmpbw-r?0|O{6#o_YQavyzhSQOcbHMRmv;qM-({fGuYXzO zRL-jcmnKUr-;~cx83EQYfnRFeei|26;n{gcOt82k#y*qkUcDK1Ue~lZ1+8O82eNcD z9Xso^44>>WfO;5z%SZDjk%;Z5hDA}AQ#AGV4}-&b7Mdu99ivg(d2wf}K&(RK$7y&t zuUf?PsYUe497R3F-cKxEq@gPOa$eMvYl4EA`@&m9A7+RyY*D+sog6g|PR$I1=l7o(KL=ke}w#u0bMO!70*3`83j zHWbmEqy+sDTKe><<%st~^JA5(SW|04XQ07Sk8>PZ2IIu`Kuwwy*LW^1lcSwFv9r~u zu#G0bt3!0tT!I3cm~k##-l;wHJ6DJ`f|yX*p<=-*Pv#l@PTpNUR?Mrgcr>Ct^~y!j zX`%Qqoo2{l|AW#FATIdv?bu8l@lL4sMZVdh!S53GZV@*pZt~&pJY9(qls}yNdGf^p z7XG629cZJo&~>QuRLz zQKk7w5MGypus>h-+KKBBRCajBHq#gjv0|KYEUGrjGh{G(;#9ct~~bf`HtPmnaPpKb1zABo_9(Y zOs{19gKe{xzpiSx{pur+>km#m2@n@pxutOM$ce_L=a`wNT?|MN7YI|FC|`oCj(K<{ z_15l9m2K~N-Ax#kE9^VJ5(!CBSg3&gz_KjuZm*cjmZX{}bKi+yy7$J|TrBFdX1%Db zZnD({R#)@6>xOZB*6Dg05=ND!_grxnV%?hOA$@%hrykY$uA)FvhGIPv`ic1$+b)Md z2uxal5MZfO&(N|Wq9rM+Z5z~CGm6(2*JKydZ7%;i<`JOH%I9=vr$n%I@PWSwo3nNQ zL>AGgVN6}8m#V?C>qM2~2;CsKYQelBr}hI3+ZSHG{I)6qfE9L`Q8%~TmyIFT{Zf?TYIkm zHq#d^S`WS5w<|~My7Q>46S=7#Br=Dr7S znPRT(CbmjvY2WAhJfPFg^(s7GK+X&be23unX!6lj_=+Tq9vWW*GEwao>FODXNhJl9 z?WN{fA#GHIhU;LQ%Bt$9^~(>tdKYD_+Wm#6XudG6#n?Na%s!p_ct~2d5CEdxG@7`G z>>R-{-x4WZn<|ap`Wk3W<^7hwQ6jrCp@|wHhrqMbm>eJZ3n^pY1d2eRohE^s7W^ye z?LM#|58qJ$rx4Lik|FOryZ_|7|8tkdTtYfBMMKmFN|qONIVmmb-|^w`Z!i^&kxDLL z&f>z&yM!wKKNfxIcJiXwXdE-AiuR(nUh(VcVdg z(Bz&cOe%42m-UlP_50p_6k5Y-AR-WlYg-7Cbd7e%W*c$$O`OpyF(i# z=R)3qEX$j;rR@Zr=FHxp9ex#A|I5n1(lascAc2dW{lS*_uxi;BX#n5M=Kt%mK1euT zlV?&o!{2?n3EjB<8hA*8?je)`amk{)#m*B^ez&}oaHgaOI4q~5SE^9wmZVDS-HMpG z4wvE^HP(7k){mw4T>i{=bpN`U6k3x**X5q=Vu$u3EXrW_`eyBK+G__!fXP z0Bs|1dd%=Z+dO<-iwe(C#AAL0X7nYp)*;@gh;&;H$S5;oi&0*2lEc$xBlokWk~x|M z)4gNRn#T(5j9(>IC9w z-OAhpznvi`n#c1kS+7u8V5gQYnAq{is^=S)b1%P}+pXOJfn;Zoq7}Eq{+Bu4%(oL7 z#wCg<=TP>gtK9sSErsqZh`0k*@iu&HTZRsKa{C%ENlql1-mFfQ0ofi0}AAaan z@db(grTbT86fF%rE>_(AK|?9 zfiTkaaysr3b~W%YcCc?n6UC%*0Y|^7%>XuOP+Y~Wzuk73f_Nz*<1k`*k92@w=Ryrd zk(GZn-6fPcQW21Lv36qBrG$ASswE!noAk2P%a+44MRE{wJA7_1o8YEsqM)?PJ_auBn&xJ04v{R||cw#O?LQUFNX}Jse&RrjRNwf#cFf09?87gKW zekgwG&GHvJ(`=PKk11&h4=TJzSZr?RUY{SXp|E~Ah9dpRfyjFNC4SvT4Ne;bPkU}& zf^RUrg#t;l59mX0xbV8aL&{=Sdz6#^o|JR+{$X~0bp*v8kvhlWIt#_==%AQ5>`k=% zGC%6+$F)7(%!*C$4WVTi@nG4n0N6-}6xLhre}P><% zU<~X!en)HwKEe_3g`gwQhQXbvGfuBY>JO)nu;b(du`>zu?e{e4nn9WpW zuJX2b%3Co2Ws~7*1%30MDsSfBBBYf&14kjI%?&=CLVzL2TafM|2awywDP0pD?hu@H zmxD`LwSj98+ZEE&+mtML!9-V3Wv`!Wjd_nwWwd;%3|)IU-UP? zLs3Rf{jmQDuaEQi?UhI7GT$#UKSw2H$gWL(3%_&0=4wgqXOww_55KeEr8Fg_>DWG% zv1*;y%Uja3zbkN=V+eB8V8|mqgK|v*CBb#9!QLpknwcsc195Jh?Ey91wEkbJ>ZUbq z!#nzyxhDwd!#4ELn3cVf^vKs$w<)v^_%tF`CY=DY@jni+yagDDMd|X#O1IFJtoZE; zw$ZN}I*HVZ;Q{4?Tmf-g%8F`z@ZweD`Q{B`vyt-(k+suzAl5vg0CjQA_jtD7{qcqw zN^#qc%!w$TaVWJFEpi`9Fc>t^!yGEWdh_(wDZ37?rQ{bGh`KK(T&J6Q-(FP+lmV9h zKXf@dp(5gAZ-8TRCuCh5;LlO`Zg5ZW#0w1mEojLAoB|T)=RgaJPVCIsP55s0Vx@}V zM3q#f5{|WyDQq`VLYC~}xajAcxVdIyo>206%FmR10GxcS7zv-s#B8)MfVJUWxrTTK zkN_vkK~L~mK93op!Y5OiC?0`O42y5T;KRTM2wuK|O3x6bU>oNOXiI@Z$&AIS${ip2 z;K+oMACH;~EeE^iwlR{o?njnrIwyHx+Z^G$gHc_>lFl@}hL>QhVH{%tl|M%a=LlEG zCYU)`M)Ed)@uaH^*_D78LnJBSVYPvnnE)7pnV1V9bh+te%xmcR&mmSGv?YU!nBi2N z_j79BXjrj=2-1>!_)dej)W=mLL-c1$)DL-V7jUIq#QbVy5C0A2ar$J%>l?WU3qlP3xXxP4S<(PCtl?U1| zRex11YjX`ziy*fo2HW* z=h%<~lQYV8gD5YE`W2pbA+tdwHWQ@XH=$$|I~xH`4X+5Mfe>=|m`L%Lq~!gIH)ay* zf6A7<_=_rawq;@fL3k&q%FaF0o^WGr*Q)Wx&s()I(u0xS5EOIOtGfxG`&0e@pu$Nb z(>XfDWZ%G4FCU+=ph9Jje2s@$VzOQ||H#bV*if4hSU6pj5VWFltas(Yv5|XmP7?h4lNCM( zTX6|DvmSPhQuE_&=?s!>t8g&Roc-7>xw1SN<4ViQW&jgVbq{IGo#uwNMtp<9$K)yw z6#1Y-HGIAbL3Iue%iKNN_|_Rt=V`5lZ5{6e*QwCu^ywvuHy9g^7Kl|6gl!z}^Mz_w zyfLIPoq_Yj>nopbVG4tvH)DzUISc@VGyvxj0NtPer-)(zp|HXHNOe*juPrL0#VK90 z|LMAtw)LZF#p4BROpir70G^fe{k&Yt#PN!#CHaL*f!%>uo1yw4!`K13H?)^cVgZ^7 z(8PmR=QN<~g$NF+C$NX_Oe5%5)qAtNz=39mq9iW3J)Ccze%)9{hdc$5_Iz3K2jfbi zwshlL8RfHwI&NB=gR!X5z`(ouc^iMYFCliqB67@P4 z=7`3d#ycArN3W}dqxX~CJy!ghsGMH4ygDmgctr!e$a4t^iIp{~kuo*LVeQ*3AxFpi z&HH4pmvTs+?9iC+Y44CKcJ)rbn(k+!V>IU!^rJ52N`iYS={Y{^2Y9T?%nN7sbaxl> zCX(%u-S_S{HupR4gS3V$V7D4o6TgO8vFTgTT6}fs7w>9}_7fxqxg*MAxspKUT zT(fVm=jt@iQdKEW{O@;ph7`3T*u6c|!kl}AKhJD@Ainb*$P1cU;c__W_BNk70IFiq ziPK^RPc$AtiuJx_Jg{$qLlP}qouJmG+AglRw=@ZejYWQ*FGp#EdvF4DHm)W zw6vsWCD;)h>jTeMh5g_SiVHPa(#y+TKkR&3PqiR4W`_BNYgAy|;9};IyZQWeDC$2| z2irQ_~ld zZ#g8+gbEa2|LRQAbfM(@PNEC^TZP7NtH$(CB_W=O{osrZie6;`p;*lgchrsnU9#&J z(P@p>p;m+6;Y0_raSGc@-$Eu;j_)#TE8h}3(s}oH&mT+!9-oGvZ}0jtHY{V1E$o;$ zzJFlb6?f-M7=|1Q-L4hNMyH0YKESfytX+^F$nnhD$`8(WNbvn^xWjD{U26SUmnuZ$ z!c9NvweNBZ9he_oO*8(SEi}}bR^m%UUsxTCKA`Mit@!Q6BB}Rnv9M0%HEd$lQz-sB z&{&A2HaNq#9F1~xNRFMe5a-j?8{p6)^uf_pA3ddiF$|{1T1IbMQGYIh2j?V3=%Jxi zxo+CoTMR%tJNUb=f}+#1sigZyVavO#&#?|IYr1EaREko+3y&g?RBD`h82%799%`>? zf9Y+aIx=t|^-Lc$@^!!TgRvE>{2QOi4&EEK+x&g&HYXiqr7miCY_1{YPMhb7W^fNs zvB|-aRreGeP-G@JDRCE`X#r0lfO9?w(|RaAc}ALV5XsMiurS0`homi?)S0Cs$iV|r zuy&H5!tU;}UWMbxkk?Pr=*YE>eXPubMt2mRr0+eMcUa`=y#$$3fgGW3ybCpA*NFBC z(xa|0eaWB*XTNQ?_}>{Sem@+N%?q(PEo@iH!3JNDhy7qxUKUgzHL7rF;pYs8sk2l}p<1Y(_F^pf*a7z? z3yR;t5vqf75c@2`O>(%8ZnqOm?Ek9ZFaY17VH%MZs$`?lm%p!v(Mt2!5~~O3EOMmo z-D>t3gd4k~5g9cO8>yI}?UTUZ&q4!oI_Woe&Jg>+_q2KJxOAWA6~gP>_RJ2A-i#J$ zIqH^+?@9zzF))U3P0G@v6v{hPd{L*~&R2k(jx7V=MO$e>4;0o*-+=&qv8T@N0aI{d z4r)-2keo%hW)z{G3N8(2@;P|HcF54qqOD_ezCsrd&=r0m}%bie=M$K6h`7L*9 zC7vBI@ConvvmtLRliuSVRBpilF5q_Z9R~dsH)j=mpQibS35dL@#1yg$_sP+N+hZukgZs#n`60?b^hV1bH^$^U0 zB?dC3rI5fXS+Ahz*p(6$@VouZxF|t90zEB0t$btzvfYOc1exd~M}tshQstxS) z&Cd#$Y<-0G6c34Vc=JcA^d;8T*kn$#lfE)p9fVNxHy!Dkv4*ja2}>)Ne-l^#YfFC6 zE6<@!@_7pVyB-vWEI8IGlMKmC4Xo+e#1=Wq(iY;CtHN3PmA;?C=B+ z*!&TKCWBW%+KFBq;fx|C<1I(Oc|@FE`xRU$&WAEZU61YcfZw^`4FnRt zAvXdJ%EGs-Rao7*JJH+a>r9(Z zsY5Yt-JOrnQCg?};eyYv(Krgj$36l#A(f3vqKeK$u~LPGVN|FVb>I>A?c8W|FwDibFZ|YfmcbTKL+?OmUE8RxX-iXka0so5ybW zAJ)2`FXsFLd`cc)O4|$HV1^h}8Mj*yzr=|=1a?AX+8Ow-GHsz3|J)~h{iOHE5B8~x zy4?CL3LcZ}!jRNSV4?PfN9!X1d6s3?lJ)MBDf`ua0!CyN;BwOWWE^Ed$rQP%*qT?_d{P{ph^+~Ixy(r zvG&8mjw9Jap9=R~R{I{+mUdEgqPuDZdqDrx^*@fBOWEEEtZsev)@*1Qjvs(vJGj7_ z59Oa^;MAv8@D)D0P4#tf#(vcj_8(i3e{1CLKl^JOet`bWcb(3x3W3W<^x8&HCUA#- z{jUcKJJ}HnN$!~oJHGzB5+(e8_JV&9J10-*?F0RK5*^PriUdBQty5nP*F)b0b@6tj z`@3M)yFr3yDe;PU6$sm4cRUI~!0IV9nNY(3Fi_ml&_#;~U57RO8uSliJU}bU$|eG| z*~9{ffgIP!h;o_ zBJDGGBj!Z8(J1ToT?|U9&RcMj=br3IF_5sPV~=ptpAV8)%X8v zS^jGENATRMdnd7n9FIBhzJ8xopi)T6{ZPRcf(oJB z5B!x7F$Nh1!f-2vmFM49^bPn=t?;j|sCF2XPl@uoAN*_N55z9hGKO>nvaF}m{>ww- zic!PZsgAU?u|@o&ib0>%&k=`4RG&S##Y0=yw$BYdnL~@n25-pR${9#?b!*)^{)_ delta 37 mcmaFe!+2F{Lp&4nzxo%O7djtj+T@Y}$CF$>u>5EEe-i*f^brC8 diff --git a/examples/webgpu_postprocessing_pixel.html b/examples/webgpu_postprocessing_pixel.html index a63cbca5cc7f77..7a9a37aa7e4144 100644 --- a/examples/webgpu_postprocessing_pixel.html +++ b/examples/webgpu_postprocessing_pixel.html @@ -68,7 +68,7 @@ function addBox( boxSideLength, x, z, rotation ) { const mesh = new THREE.Mesh( new THREE.BoxGeometry( boxSideLength, boxSideLength, boxSideLength ), boxMaterial ); - mesh.castShadow = false; + mesh.castShadow = true; mesh.receiveShadow = false; mesh.rotation.y = rotation; mesh.position.y = boxSideLength / 2; @@ -102,16 +102,14 @@ } ) ); crystalMesh.receiveShadow = false; - crystalMesh.castShadow = false; + crystalMesh.castShadow = true; scene.add( crystalMesh ); // lights - scene.add( new THREE.AmbientLight( 0x757f8e, 3 ) ); - const directionalLight = new THREE.DirectionalLight( 0xfffecd, 1.5 ); directionalLight.position.set( 100, 100, 100 ); - directionalLight.castShadow = false; + directionalLight.castShadow = true; directionalLight.shadow.mapSize.set( 2048, 2048 ); scene.add( directionalLight ); @@ -120,7 +118,7 @@ const target = spotLight.target; scene.add( target ); target.position.set( 0, 0, 0 ); - spotLight.castShadow = false; + spotLight.castShadow = true; scene.add( spotLight ); renderer = new THREE.WebGPURenderer({ antialias: false }); From 550f0630599dc309aa145662a77da00a79e296d7 Mon Sep 17 00:00:00 2001 From: Christian Helgeson Date: Wed, 10 Jul 2024 18:06:30 -0700 Subject: [PATCH 12/24] final lighting adjustment --- .../webgpu_postprocessing_pixel.jpg | Bin 18027 -> 18588 bytes examples/webgpu_postprocessing_pixel.html | 12 +++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/examples/screenshots/webgpu_postprocessing_pixel.jpg b/examples/screenshots/webgpu_postprocessing_pixel.jpg index 92b91c44ded097923d19d21576f485eb96b421c2..07205cf492af3a918b56c61cc090eb3582574d63 100644 GIT binary patch literal 18588 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 literal 18027 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 diff --git a/examples/webgpu_postprocessing_pixel.html b/examples/webgpu_postprocessing_pixel.html index 7a9a37aa7e4144..15f0322ec9788b 100644 --- a/examples/webgpu_postprocessing_pixel.html +++ b/examples/webgpu_postprocessing_pixel.html @@ -68,8 +68,8 @@ function addBox( boxSideLength, x, z, rotation ) { const mesh = new THREE.Mesh( new THREE.BoxGeometry( boxSideLength, boxSideLength, boxSideLength ), boxMaterial ); - mesh.castShadow = true; - mesh.receiveShadow = false; + mesh.castShadow = false; + mesh.receiveShadow = true; mesh.rotation.y = rotation; mesh.position.y = boxSideLength / 2; mesh.position.set( x, boxSideLength / 2 + .0001, z ); @@ -102,7 +102,7 @@ } ) ); crystalMesh.receiveShadow = false; - crystalMesh.castShadow = true; + crystalMesh.castShadow = false; scene.add( crystalMesh ); // lights @@ -113,6 +113,12 @@ directionalLight.shadow.mapSize.set( 2048, 2048 ); scene.add( directionalLight ); + const fillLight = new THREE.DirectionalLight( 0x2a3c6b, 1.2 ); + fillLight.position.set(-100, 100, 100); + fillLight.castShadow = true; + fillLight.shadow.mapSize.set( 2048, 2048 ); + scene.add( fillLight ); + const spotLight = new THREE.SpotLight( 0xffc100, 10, 10, Math.PI / 16, .02, 2 ); spotLight.position.set( 2, 2, 0 ); const target = spotLight.target; From 7788849ac4f09f0c12abc61f3abf20862554302d Mon Sep 17 00:00:00 2001 From: Christian Helgeson Date: Thu, 11 Jul 2024 10:58:41 -0700 Subject: [PATCH 13/24] Revert lighting --- examples/webgpu_postprocessing_pixel.html | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/examples/webgpu_postprocessing_pixel.html b/examples/webgpu_postprocessing_pixel.html index 15f0322ec9788b..fb0afc34e9e034 100644 --- a/examples/webgpu_postprocessing_pixel.html +++ b/examples/webgpu_postprocessing_pixel.html @@ -68,7 +68,7 @@ function addBox( boxSideLength, x, z, rotation ) { const mesh = new THREE.Mesh( new THREE.BoxGeometry( boxSideLength, boxSideLength, boxSideLength ), boxMaterial ); - mesh.castShadow = false; + mesh.castShadow = true; mesh.receiveShadow = true; mesh.rotation.y = rotation; mesh.position.y = boxSideLength / 2; @@ -101,24 +101,20 @@ specular: 0xffffff } ) ); - crystalMesh.receiveShadow = false; - crystalMesh.castShadow = false; + crystalMesh.receiveShadow = true; + crystalMesh.castShadow = true; scene.add( crystalMesh ); // lights + scene.add( new THREE.AmbientLight( 0x757f8e, 3 ) ); + const directionalLight = new THREE.DirectionalLight( 0xfffecd, 1.5 ); directionalLight.position.set( 100, 100, 100 ); directionalLight.castShadow = true; directionalLight.shadow.mapSize.set( 2048, 2048 ); scene.add( directionalLight ); - const fillLight = new THREE.DirectionalLight( 0x2a3c6b, 1.2 ); - fillLight.position.set(-100, 100, 100); - fillLight.castShadow = true; - fillLight.shadow.mapSize.set( 2048, 2048 ); - scene.add( fillLight ); - const spotLight = new THREE.SpotLight( 0xffc100, 10, 10, Math.PI / 16, .02, 2 ); spotLight.position.set( 2, 2, 0 ); const target = spotLight.target; @@ -135,7 +131,7 @@ document.body.appendChild( renderer.domElement ); effectController = { - pixelSize: uniform( 14 ), + pixelSize: uniform( 6 ), normalEdgeStrength: uniform( 0.3 ), depthEdgeStrength: uniform( 0.4 ), pixelAlignedPanning: true @@ -146,12 +142,12 @@ const scenePass = pass( scene, camera ); scenePass.setMRT( mrt( { output: output, - normal: directionToColor(normalView), + normal: normalView, } ) ); const depthNode = scenePass.getTextureNode( 'depth' ); const normalNode = scenePass.getTextureNode( 'normal' ); - postProcessing.outputNode = scenePass.getTextureNode('output').pixelation( depthNode, normalNode, effectController.pixelSize, effectController.normalEdgeStrength, effectController.depthEdgeStrength ); + postProcessing.outputNode = scenePass.getTextureNode( 'output' ).pixelation( depthNode, normalNode, effectController.pixelSize, effectController.normalEdgeStrength, effectController.depthEdgeStrength ); window.addEventListener( 'resize', onWindowResize ); From 63dfe769bbe6da39b922c1102b3b50500d8474fb Mon Sep 17 00:00:00 2001 From: Christian Helgeson Date: Thu, 11 Jul 2024 11:20:48 -0700 Subject: [PATCH 14/24] bring back directionToColor --- examples/webgpu_postprocessing_pixel.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/webgpu_postprocessing_pixel.html b/examples/webgpu_postprocessing_pixel.html index fb0afc34e9e034..0f38e212f5f4f6 100644 --- a/examples/webgpu_postprocessing_pixel.html +++ b/examples/webgpu_postprocessing_pixel.html @@ -142,7 +142,7 @@ const scenePass = pass( scene, camera ); scenePass.setMRT( mrt( { output: output, - normal: normalView, + normal: directionToColor( normalView ), } ) ); const depthNode = scenePass.getTextureNode( 'depth' ); From 58cc81ce1c6c70d232a8fe9c02f4fbe640ca223e Mon Sep 17 00:00:00 2001 From: Christian Helgeson Date: Thu, 11 Jul 2024 11:33:16 -0700 Subject: [PATCH 15/24] fix screenshot --- .../webgpu_postprocessing_pixel.jpg | Bin 18588 -> 3415 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/examples/screenshots/webgpu_postprocessing_pixel.jpg b/examples/screenshots/webgpu_postprocessing_pixel.jpg index 07205cf492af3a918b56c61cc090eb3582574d63..4d46824c2aafc137a782a18838fc9b202658eb06 100644 GIT binary patch delta 167 ncmbO;k@33Jh6E78W(=^!Gaw5Wh6Eg}S@ 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 From c16e31632edd844678b473f1088abff0f795854e Mon Sep 17 00:00:00 2001 From: Christian Helgeson Date: Thu, 11 Jul 2024 13:29:15 -0700 Subject: [PATCH 16/24] filtering fix --- src/nodes/display/PixelationNode.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/nodes/display/PixelationNode.js b/src/nodes/display/PixelationNode.js index 4c29a8e3a73f80..eb71c332e2f66c 100644 --- a/src/nodes/display/PixelationNode.js +++ b/src/nodes/display/PixelationNode.js @@ -9,6 +9,7 @@ import { property } from '../core/PropertyNode.js'; import QuadMesh from '../../renderers/common/QuadMesh.js'; import { RenderTarget } from '../../core/RenderTarget.js'; import { passTexture } from './PassNode.js'; +import { NearestFilter } from '../../constants.js'; const createEdgesQuad = new QuadMesh(); const lowerResolutionQuad = new QuadMesh(); @@ -39,8 +40,12 @@ class PixelationNode extends TempNode { this._createEdgesRT = new RenderTarget(); this._createEdgesRT.texture.name = 'PixelationNode.renderEdges'; + this._createEdgesRT.texture.minFilter = NearestFilter; + this._createEdgesRT.texture.magFilter = NearestFilter; this._lowerResolutionRT = new RenderTarget(); this._lowerResolutionRT.texture.name = 'PixelationNode.lowerResolution'; + this._lowerResolutionRT.texture.minFilter = NearestFilter; + this._lowerResolutionRT.texture.magFilter = NearestFilter; // Output textures From 03f1135fc217a07ebb8bb3aa425ecc7b7d22f7c9 Mon Sep 17 00:00:00 2001 From: Christian Helgeson Date: Thu, 11 Jul 2024 13:39:28 -0700 Subject: [PATCH 17/24] fix normalView and add new screenshot --- .../webgpu_postprocessing_pixel.jpg | Bin 3415 -> 19526 bytes examples/webgpu_postprocessing_pixel.html | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/screenshots/webgpu_postprocessing_pixel.jpg b/examples/screenshots/webgpu_postprocessing_pixel.jpg index 4d46824c2aafc137a782a18838fc9b202658eb06..97c17054f55ad7eb6824181b9851e0b1f9717fff 100644 GIT binary patch literal 19526 zcmeIa2UJsQv@RMpDq;Zv=}qZ^fOM%*=^!8=U5HYo2}timM0yEDK)Oip(mRn}1?jzn z-btu|gyb&W=brKIIrr@I?s;#F_uhCn3<9jRverNUGUxYwbN=9`@JpaODhkR9AOZpq zhyeHk;b%dgKz|YZe*J#`>-Xyl;qUjWSFR9Vxpwv1wLjlP#MiG85ffdzcKzmc;v2tT zz;Cy1-njMqkH0_m*HuEot2cYt_o4gLE?@E75gtJjEtCfvLOG@FnRXzW#>rNEWGz&X$rva96x1)g4`(0oJ0;&@lk zKPK}!>$CD!O08ien~>Qk254eRzM8zJ9OUTN}D<~={t7yN}(bdy8 zFf=o_cx!28ZR70X>gMj@=@sxPFeo@AG%Pmmb9_SLm!#xxS=l+cdHDr}6_r)hHMMp1 z4Q=fmon75My?rC2W8)K(Q`0ku<(1X7^^MJ~ZPek>@yY2K`uu_bMEJKk!27?m2QZxA zuit%6^xGbSzubXuLb5AY?+aWbf2v9J#*u01Ox*83+u*Ldvqm+dkX?ae==6_5b-Fge_=B zjD*MR%Gqfi_Wn$6yxnEos&5`f-IKySW!-MJLTu=wX3%lWwbTop?BVUqnLkp0&~hIS z`siRwjFGX@^HQ}U^C+*@qsz^|$U%fziM@J5`P|~^kKpKl{dCWbkK$pcdyR+3hEU^+ zzy@Yy?K4gJ$+qtqhY{&zc18tcQr0 zMVEZK3wlBDO)rchARWGv-8I)PbG{$d+${Q%4o;_zSY`R>;n!Wt(_0i#WSH_Pwvu~7 zrXOv#vKW-?+g$QlhBbQIE7>Uf^rs!icxuEu?Mjfnsh~2W_`xetQ+H{)7|vL%209eE z_-fQ*LYS0H>LOc{&23`Oj=O7R;JmV)NYVhdz<5G~rJgiep3bU_`sTJFCN>dpy>)Dn0wu@3aJ%xdjFVx=rLQ^=_I( zc;*(jDC$y(_M^rbB`6wIFd}(B`8J4OZG8K`FlC5xeK;=TWq5s*(EfR7em$gO$g!&T zqS(MVpO2U%nmud+r?=dw>1sdexu#tGBBZ#kG?&(=ejO7q%Vg~1s#3GoWVcSoS~dYT4+jUC+^Mcb z&Nms>tHv=;zjFbl=+xHx@Q7aX*cxw`N)@qQWD6M!9O^)~+f;!NX6K+~Ty`5?b%n z(zASl@$Q&3A+AJLfyv4;n`-I>Rg1WwqOR;XS(8mq^s zNzIrCvMRj3`RLv)kdu?53g5@gy{yYfsoPFIaI)K+FF)MMX+8)xjiJOmy47JxOM52F z9T(nY95~=Hv`38xU6~&Bv0kw?*8X8wMaP)xr6tdv8-pNQllo?$?7Y?&;CL%5_kC7+ z{H!dq#EYs#u>nJ&HBYETM%XVOU5DT}5220+uhZ{KIPdHsdOLWoXY`4&`TlD3^Ks|` zhc|oEcvThkk*zED&3k`Ywz-6mO4Pu)9rpFWFQBhs)4>SnZa*?l3DuQ}kS6Sss2)#S z3mmKT@VjnLYl2!OIb;?I(Io#$R3lFFSIZ9nkCApwZELZ@*#WF2_-DC6F5e5S&#W#Z z>vle(v+@%gzh;J_{HV6H{0>f?kl5jOS96W@|0>|5;p~xFO!Lx;WUtaZ8qNwy#I`_T z=h4=1G90}vU0_R0wqO4YSBJj5rG@|g*B^{N30K?v^|JuHPLGh+=cXvT7)nzYI-2vu-3BrEIET{^kDF z=%6cK&&^lvtzsu^BXSJ9x#SIm&l8QMr7*|RKIJQd+ogp?oUXRUrA0$MI7g^0^E4bs z>%GjI_yLR%!p&jchB9TI!gmiU-z^Fc@6N6(r+OL;Lc?iRID0$a;7T+V&PFzNJVmW9 z%K}~N#qD?u&o(nG=8kTxOTYgw$57HS=ydIhL;E~uyS5GZu8XmT)`Z#EDu#FdvsHdH zO%}m*@_U?)(U+rV!7}OWuWse=j8;H|z_xZnnGanoCxvNzjB`Fm^&S3n;9ZV~eJPi4 z(0MH=cJ&^UhBRg}jlGwX`d8I{spm$xjD&}3>%zS{Q}Z#)%5z$U%c{77O1}dCNk44qh($Znxhk=t61a#KK+Q? zFStzn3Yp#(v=Nh$ZyG%(yFT-FCa2Di+Hr_5&wiYje0Uta(`~5Oe#s(jX;53A@W5V< zyC%PQHmuTe{k1@#^rp&E{179$9HYLSY%OsXa8PC^yvZA5`O%1rPT_Np0;18K#$sQB zN!le(ZSYlAg{pP{bIn<}&|r1G8Ll;}Ao8}Lr8yPo8 zypZnydc{B0Wg`xv*?#Uvac4zKvni3suo-u>=8+-L`Fhi-O*dS-CH!UV!S;~ba4~-i zZI={T;XtgSc^9-gobhGbDqik+z+R_2J z!D8o?MCVZ5yLGWo^8bq7t6}i@8L^VCT*fcdshy=ZYQ12cb8dT<_E^I@MDw%B1U%`a zrD)kZ5JlNlo7smv?NWiZCj`}M(zUFh*31~di2 zb_yNV-P)izdO}dV&{xj0L$0ut9aq7SRkx-tX6{75P+c@wbs*;E%Y4gJ;3Y~klh`oD zt~wXnyyq8QfbKP5{ucOO#zemJ$Rpg73Os0X6uQatYItfjVV${)QN@>D=a*~vONE4y zW#udb0&W9C4$xARAdf_Wim1ituW|$|DgXla1~d0so4x&>7-l5tsY?44^k5C$YBu_P zy=nW4iO#;wV4%!KE*6ZoTy}^s6mH=_tL2Io4t@nY7S5JAfV54}_OUoPTR^JY2?W2R%fGqk04xM2!~xLn$}PvS3rMA7H@oiKFN zyz_a3mPTYmrqc$rwQ#qz)YjIv7*+Yoyj0}N-z4CR5C|oGw@PBOH5UKCn0$OFx`oAW z0D7m1y`(dDE1fY}X<^))!yDt|WDxw8;XTh(K%|a`O+=YP2!ihzV@J1bB-zWz&&y_> zK<-4P^Oz_4ktfX(PFVT|kMXwiLKjQ?9nkdoQ#^_UQdDYFdbdKSf!J&H~>!c3#EW?J@vk4s0f-+U=$ zvJfS*TCu#$nx4^I+=ik=G_>1U&A$DoPVV*R-y-Gdd&z8Osyl-+*yp$c<7jS?h~M)X zIj!J(q&9U{76eDWaEO?>C379^XpP9YB@$i|m245)x75Ez%6)H|!oSga&@tk4&xAvW zvt7n!(%s#c*B8S$=BOha7@rVaYqMA#ygT#;%AU~)4du_-*@D$h&1{J|Fc^SxY2Une zrj>1H(y$Ncjv}JB9f@2S)xlZqxLTJi zYXaHz%;=HCUa5OlcT)|YQS^`ZEx};Uur5<8%g^5<`3w|rP}{+INoMD^x*H-~QrZs5 zg4S+my~GUX^Qkg@%)S3gP~fN?t-jeIIHRw|9q@6uL*H^VERm~ZiX8f82Xai*fCv3> z1MrAAVjlGGju4#kw=RZAaFigD_FZ`zjfkl(Kg&uluSSNrJ4cDLB1`p^g`Ed^1xcf{ z2I zk^V0G9)Gb zb6qKFn_k&?5asc~EwuKJn%fwjAFf2BOR(o-k>8c8A0#~ftDg)by5VM8ESezh*xSPy zR>P#jZ`0tmw^l!@Zo`Yo%SJ=n;Urki%%>=#mB~-SkgL__=H409)9tj@@nLKVs(~

BGG~zVtwo4b@aiYl+I`m5RM=-c=z1BoK!=46&jb0VgEOl04da{Xmlr4CCy3%Q z#$Z@gfw=qwuw!oAnQkL4jnp5GQ1E$&2Yr)VF2iz`LeXJIE#a3D20eH) z3JrjD;++f}3#|AB&h_kTCASUpO+St;p__Jkh{0w%R=%~U6{RGaQl-Hg$4j`E-d@ZD zEyq+AQ1lZYcRc8sZW#bJr=jR%>1NV(h}TVk(tRT8h90xq;6Z5KL_8=oYxWn;0=O~h z|MDjuTH`@LTee5(?RbE;IJ1I1-kZeQzMh*)$X0ntpi!VvGCnSD;C_n-iuzjt9v=&dK0i6%`!2U+L-?f6&=E=;+<&0p%#ZNBBW7RxGdXejZ8d)xWFMa#*xf&-`Pq(nKk`eu?U{f2!O1 zcdwp#Yl?cbD+au{ojg~AyXQ@dpepVQcjQLYauEeGh8FLP2hlN%j@xCv%;Y{?3&L4a zTD7pD)0S=RBsN}z?yp5=g&BNi34AdWQ0~{#APWQMm*FFZ*vQhFU2pe~I5x3>;^B8D z9vfs8lG#c>3hbuNVWZW)dp zn#3{cQy#D%i8Xl89X(C>#LO;l&Z_SBY-`Zr;0W1gOBqW^jRl4aUtOK_H%IRt7IvyC z+G!b6Y+{GPexhwD!!OdM9=J8O%zMfdZ*n;rrITsVkzewTla>uAeS#IXlHNA zY+|Z-xAHe^(GLsRHQ97G&cgZ0GCr>cc?{Uyk|rJxvz8a*m*m+hD>F;HnXoKC)8Im( zm-<=vbbQuWYDg5Ff_dTh3tA-wK^8PQ1Hw)1s6B!XSqAuy4D6?$g00{G4@n`#qm!;H%M8NvkzHs3%?oO6&nOsWiWK#Pw1JRRRIus7h9pn*YKd;P%ygiFJLYkq4{wi7S9DZ0nhuyIGq)vjHYO-SrD&M zaw++`C6MZQ$b4?gkI~Vb$|=}@BZE9_asKdgfYf&7zX6tpWe|K5fYLD;SSt|VY=qh& z$G3ro4B>=9&^B5`*#+>dRmI=*u}3q27jWkw@UAYVoszz=t}djDBEJ|+!d zgzs!?TvGAG{_O~|b>*&tUJf4{Ydov5RnwjAi^=9IYBpN?LA#V8tISbLZ@uf~@B?}d zC~32#mj#BS+ZNiHn>v|EomhEvcnMn3C|qsS&dx-v+CVvtCN<&BbTBhl{^w+it_N9) z&(J9dUv(ZygA;k`+M9Gu4;(nIlzezeE)dzx2&J=84(-g^w8F~2vBrp?y}DP%R^*U$ zHnC~+M&A`;*S%NRdk>Sef8KXX35cVE&6oY`nctYONx$D0RV|TMU3Gl8ZF_g^rm9&7 z+=`#lLkO|hVarU4eLTU02Pvi~EAN+9Ux)lu9*8Z_4Ae*@W=3W-c^*G7EvK{+XR9y6 znOGDG$IIpJ!Zgv4Tzw-C(@~SEWCc6jwuNQ2MHljv7R?C%Bc}Q?BF30BF%Ipn}cWCer4T>s|o5i7`{rEuf^u40ZC`W3H~iP3en>bDE& z#YeWwoI%~{numrLtZgU#wB$=3LA|Q;vt8s|Q<0y@pAN|m!=NgNHsx_V$kJQ&09Pe} zB9=YtcRhm|Ch0LzG$pT-(rmq#s?-P1^2cYthYgFlVm%`G0ZiIO@< zG@F-Uw1JR6Y;rJ9zk$5WS=EoOeD#z-52hR#?HvzgU49B>Ixp5(>v>q7MeOPK4O9ty(yd3XkcG**xQ+HfqOr; z(^IUO*y}7A?I|p4qUj-e%vvW?yv8X1^XgS z&%-6JDrnYt;EeNw_tTX(zMmcrfNQ_5VsIg-BVq-SSYNoVuh0@{*o9t*kI1*$kc0aH zM$P3lVyAAWhn|k}5`5?}Y`c}y6G<3h!H)hA*)x$e$?dd;v+|oajYl_X65wi4ZPnsK zw=Q$xt3FkD(04iLg{Wcv?n^hR?fkEQc)~GcIuWPX564^q7GsuaK|)8r+1$4BpH)W6 zcv%Bo4c!4$ZSL*G3pGOo9t0!RIgfnjr!_em^3Sn(4r;+4U~&%~lb5Eb6o!fs5zhp_ z9Zi1|^Ou+oYwDxrx}7@i@ML8xxfE47ueI(U#Cx3HMrGtuPF-tkQEE~_3Pe}#Kt7V% zs1O(BS`4iU;qHPj)gb4rc+m9dso$GDsmLKB4|nC}QMN?=UnOgw)t^=WEUdgb)8x~h z>-;j-ykal9qaC(Syln(=@tOOL|Xz{KZL>&Ra~3>ll*zJPIUuwF47f z@t|9-nqTWjQzk2=ogjMkbVd0DO^RMlnb== zB1@0mk7j%Nsfymrtff@8Bp3N{3YB>*jN2vZgKVgO}!Im-#Z>9v=TB&=tQsbD1lA z8&ePV9QN8^1P7P+%$ZIo>zEuQ`8i4#fYC2=vG9QzBrFO`YwNk98YaoK=| z9DA-zb)vvgO}r(GIrF(Od7C!hKZ(W+gfXn9VYyERC7`5+7sY{2uUAvx0O(=!!M!^# ze~>eJVp%(jHfy5>UoSBqcHI+|c6foDNn^Lu1y`%OOsu>Pwso*}uUK*TSr_3X?=u=` zZAqTUv!=thP@|gSKFs4TT{IG97#F+#e%RF^(PG_G!yjvgidzEKoP+kVT1IHuk`Xhz z-AQlhefjtZdm3Wd+q1F+i&v5_3J=>~=B9C^H}hUFi_nZ|RmCaV{bo5>VN0y;q64lA z!+$|K?Ne4|Ka2T^srWLK5@Ck+I_hR_vqlDE1-mabpy#T1&|Z}@(IaU4L+IEU9`qTG zn^k<|O!S|fW`QFIp(rC?IL2}hvI(|0Y#FL`KM>pR-(b=efGzKIb9OM_fwpVoL1UqK zP`n0iT#?Hu7%Kw4RLVGi2|VLDoX#D_avH!B2}2r$x6YyHjQ}D2p3Ps06eN0 ztP8I2m0xxUU?QIQI>vA3B4@0!$L_Ju*7$`h&HsMSEd?=#6`IpMeeKSP0Oe-t+%Ef6 zgY|}?R$u4ViJ}5@(0Z2#TeMt(op4~l9VMwkRG{?EXl7`hiBEwL?bA-8ud+2kz(a#iT&9nr&D7cz5jJ`O$$p2!LXyiw9c2t`P-NK&qr(85 zN`Kzmr$IJuWa)WI*dGtSdnP5j$D87Ct|)NXb)d@|h%%-W9P zTULrrj8K|>p|rFVGv>PUil$gffXuqYwYFe3xP+V6$c|6H8o3Ll^3ij(LtIg{yhGdz zchf9?cAuGV-$@anLG;XJliR@+I-nm`o~FoNDP7Zl>Qj#F?z}Cggt{!r*&C=S<5sqG zeWb}W{e)=WxnBQxU%c!Fg;uXkA9w)%I6ZrL!t#2{;yMJ$*-CoV=3+AJc%D-(il??g zK);`NZq_U&`SmAk_lNfyM0v8>Nk7|(gP`-1`=PlIrg!qL8yA%n%R_C(kR|@0B@_&1 z|M1}9zJu-{S5>|7DtU$EN7=&Lb&-u5Y>rzSi2Dy+^=^0xmz5o3(mX;RJ`~@mnPGBE zCm(MSFySa)q7Qkvm&I)9al*mfCEp(_8oXl5wKK-VGoL9EKm5L^sQDPvFk8MdX-lgk z;bmbRxrDdjM{&5u!)e-Emv-2aYAdDAH!oinFOvKjw|A z=uQ>i97A9X(L5vRN-o7sZW^n8Ron9l9-T(&xs2Qt4>%apF1{u z_BT9{_>F61f8&}a=moc-1fAb=**oNX^=|MAST{l>-WKNr*(JdhFR@4h+|iWNZ+ z^GQi(v7ZdX6Sb;I9!3)x7^nuZbp?jpOrIhOJJ4^?)>PX$nhR;|tlXfR${K6A4(%y$ z#Dm^Y<3YoY6W^!$ywl!hnNj8C4do6Fu+{~Ba|({8x#O6Q9dA}LWJ*Gc_9I9GutaaN zJ<2-uCd%95KyuIbnseV0u7;elO$LXzn%Yd-3yi84m2YWwXk{rYgS`tM+j*Nbp(vLZ z7P`qBI__NeCj`aKWf-13nzDNH8gcEcC6Cc&q>Rs+8SK%BQ)t$}wFyBtyK>Lyrh1!E zlm|hk)Td-}`+I9Kg0YW00g}Ko6mL(qz@&J4vaqzCmm9Yv=~C6Mo90VsPoJLDZR#d46&5fK zA6tIH-St>hL#}0XFlx&QSex*ahigvQcaACc)Vh#2c4UU%Vk>@f9|n4z4yT88bQO=2 zo}YMh56;-0@pb<~kbkn!bdZK@C733V_D$r1mE7)n%hpc z@r|08f6+}CrD{)-SwZzy{1y@6qGWYQo8pwwyw^F-Pr-VmgwIoG!2u`eBY<-7mwMpi z^BjtJfQDwvb7bUpS_-yZe3U06dv5TkcYU@nHY)21cJ8CV*X9WBF7V-zB6{!aJgQ7M zkBIl%@$s)C?}M4}P6(YJn++2QDn2USHs8o7XTGx~%}#J{ru-Xrh_&FVIT2`sbDJl& zl=uyD`&&!tywqTW>%7XKG^yuhLJQ;OOxXv1K0ZEsf4YJ2LkWKvH|xCbPI9iLN%&eA z1a_WyazjJ}fFBUnIuvy~eK?d8NA|_CZtws+m~?I7OZWU4w#bLijutXo)o1|DaK5Er z%r!gBfwRRC3~jeCOt{F;7fheazK5KI&Bdg5oR+CX@sNhNLvuN9520_@Ot^w$8pzzX729OK_ z{a1!A7^|UJS^z+G>SdTq-ut%)HO;A>0un4?cgC^gZ#`EK&~wAS^*jvaM8~aQb~s0> zHRyM!b+zS8>nrNU**$VYB81=NgmCNK`}pW-Csj@U!w@VhMh301g3HF;E^iub;yC?9 zKbqf})BPa&!OO$%bTTb(clW{0d5*?M>(9Rx+b-hzWVq3ikB~#3_C-jkX4T;~ zzN+fK=C#BeqC4D#1Mc0k|BC2l9Lfyt)K2B+NF3NGp!Owk+aK9(+cu)#vA&;9ODCQg zf1}BsZYkEB=VcGN^%%zX)vvN}j%_>JRc&TasY9j8b>j@Y9PuMW5Rq&ISYXnN;R zf#CJcTMnFe(jPxQEH5w4otKD&uNez3DqgsX<*wN24V>e6j0z@ws74+W(aLc&n-PYh zAgw$~rejTS_#@2t!bMi}MZOLyK>9fc@SqPH%)vIn8YMsPUNEUHqeRPdb7~U5RVq~m zu`Eb;)9bqs=CPF9tQSm`KUXZ` zyW>qw;>QkW3pia?pd@Yu@PjquxhNd<&Zra?xAY` z(7>lTmSN*{hhLfui{GHfOw0C&#*t;qsE3uI?&!fVlL5-T{lc%rM#>1e(v3vt&TMay z^IzMH;v?^rdspH_#rgi)TC0rV5%^WQw{@MV-uTRFP17Z|!TsRZYW$YpsmYO-QEdid zw!l^vZHe4KFcx_Pv?nu#F6Y%mH=u30H3HQT?^!u*&Xc%F@Sr*mSgq#J4egGf&sov`Ot+JmSq#f4?`Tx@wenb((jsc%SO-inR+1I zRcX$o2ez)h**NfO!y06MOjzw`8JAe0&U|$|dDgO4HjD4$##wsJZ0SY=?$=CI4iaEs zH4R{Y5!z!Tap9_DRW%mfn$T52m7Kn=Cl8(UB_oobY9~)*{0x-Ea9oYcF+Vtv1-Iw zOoqGmw3?D-wre6)m%6N~eysNVnrE0BGK9{PH-vU$7(Svt>Mw6rD|5)EgaLl=D ziwE6H_zkY`pbPc*mABw-lZ+y6N#-Yd1W$jLloD*hEBUdoMCj@DI6O$9Q=2z5p&PDm z+fbTBTwC5S64OsKh zD!tZ>BG{O0z;K8BenLSspqYT78Ozb4?+oAUIYdOBIQ|O-We+Bywd`7Bckk+2_ZaI{3m!zWnPTTqY)@vgts49~Wmvx< zz60gzcxDup6gK6hZ(~nxg4-%7xrAiCbfk2oB#%4u%1S~)S~G590q*)Kd^tBUM1 z225u&XQ=B#yA9R#=Zso{J?fi>FC&|Yo=-~6zoX`?a*tFGuSqnPkcn8386x{hQ({(I zb_{!d8G{u^ln5>J?&rv)c6Y>D#pY(_vz4s%GpCV^-!QUTQh2O+rXkbfW@pfsoTs5; zUBov7PCwgrUD}jzT2HU2DNa1C28w857658c0x*Tk_$mTNnlvdrB=7>X+;E-Yng^xe z6p?>iSWF46w#ly&ks`skHah(kNBe=*tELjDeWDVF;Eu8Op9Y&V_3?%RCDg8E&!E#Q z>cE)AL*Kmb8Dfe)=Tc_qB|bPQ%gWOgDR|KR)}y+hLCevyMu+Ba=n{pS1s}?t;6WHl zSBFcElJ|H}cW@!Ls|NhQcB|t*S+r-n_mO8^O#Hu?4%!QWQ^mV(T~s7=k7OXf`EdjOrv%8;{DPufTU{i-0{({rrH2c2z@`V_LE+yv`2uiv zH98>|l<-3@QXF6S*9IWz3#No_>H?{#O)$CvC`H|Dp|vtBYyIivK!H`P#toLI|12@h z@cP-HYyQ5`^r2#3gmZ-BWQ_98_w+`_Ni4rZAmmsW;ARnc5LTW?z##bF9HM&jmKoXH zLJ9pf3XFyj9)!AU5cmb!c8b=)gJd;uN<)BW)O<^2$%E2+iB?6$DdK(vcZzBFO^nWN zeK&f73e8P=Rv5?TLHjz3?Sr6`t&K>Y!l_X#V&Fuo{|9pgq|2DIW0LAZFTgx@-0pgq zva6(V_rp&cX8|qVdae(R*~-K$kAzZ`*a$$~CT=Bs!+NM#K;k@nWnPN3h8&C}IY16X_qs*(ScY-x6Oq9_Ws)QI>(h`V!u%{KyORUf=qYoBS*H<)vG4V3~u+!F9$ntv*Z2TnX!P9&Lr6Yt{k<`|*Mun)@Q^ zwwW;Nr4rkv;D~*!B}%lns$a6m(UM`o=J~B+vbMdDO40;TaaIQJunUHYh64GgF7S1a zsS9-xhGsA;1V>%lR(3%LJyd|=SejLZhJacRlf@hGO@QL`0(o0>*u%B~Fv8mhShSgW zfb0xpXP3Duf7glx?jehCK^fSX3_K|0+rYyij>~cxeyqeq3+axRU(zzasCAP_p(34c ztVO>_3#j*r#6%Uv>F=ECNCjB1Xe;!ebE=p!95FzrSVX!nBlVyc@>e~Q5>bxs~Su)3R(^BLMoqcK77pso7WS4C9*COMC%9wP5u3wH- zXVWF*D+})q-6-PvlR*-}F#~b%{2O%2WEOxCC7j>WyT=mF#6(NYtLr?bBB&yJ9v?4% z{Rj(dVZutG1_Qu>Rg2fH8m^ofx!EACe;owa20#8GdvulOEO_DI*PaHMZt7TyL*MFS zj$)rojFMkxx*!@_WU!q<%2y4hhrWAdtm=`>A8hWhixs>}V(RIK2Ys0uTs*O)-C(RL z*UVoO&3Lp{@t8jl$25uML0vn0ic$#n%w2q%$1Kx_n?@u^|D@nseSf_#T0!w%z(T{8 z2(Ml z+)L;vmSB_7DrlH9<0jKN+iFmQe1LQRfIF@)QTcJMQOqhZG5S6kGg->VKK z?$q^M*(e)n<}$r6?v$1AGKbPlr)1zGu-$GF4aI|4*Fyo!>FsbySC!u8&HQ5Q&Rqga zjqwvk9_SRG6kLcN_ry73CI2kP);}QM=RTTZ*@4p7M-~P1-)KZ##o97nUO?18>gc@G zEVp<`qx9>2);=cIEzoOh{M&A*qlR-|lP5+1eTLWwUnK0gH!3{zj8X2NWbSS@Sax3Oh!_(=kjG@!x)LrbO!VOU zvfZq95g*NB1)>L>NLdSzLMnAux>|Wuxha!_JI0uErYQ0>gU+I@ygAPUn3UR5uQu+E z2FaF~8;-Fe>Fwz8xu;cX!9&$VKBo~SPqVY;4@@sn`VEyEpR4^;u~BG5{!_!nq<*!M zZFfg{@9?yqd4*1<%RuM4Z2OdVfh3PlvR_=Ny|Ouw-7K4?SwVDeF(3audrkaUSI+5& zr%o)SiA$~Nzddmh(t732c8hvlBz;AFgAhfu%r@G~5&TV6m^;Xdza!>8Ica0->dJwa zYgtaeQ(Y%_wU+ne?c<%&1>+$K#wfc6m*aWq>Mfk*N54_X;r$uFC4w(0d3~?AONH!v zSQxzdL%V+X{lbF|Ss>6UmNPs^UbhMiW7atj-Su&WC_MS+tk*acsc{gtz=9+VX8S(8 zTb!*hS<23_E@oCY(*bX5k03DNGc7X)r0$$TnRFlloQ&GI{L2M~LGc4&u?!qf#LY;( z2VU7Ua@wK;KX-Nj4l`yiC8^44EuQ{B2Mt~wgk=_( za6DVFXSWwf^&ct~G#PrSWv@Q4Nqwq|_VC(~7{XnV2iSI@baD8aoIAWitlj(aqn*Pm zdZEbLM?k;Sn2eW5>8R*c$f@2!_8K+kIlFJ0D|{QPJIgK)97CBQoL|`Bt@riKwL}YI&KJ zUYR`U?jwDFZk33ofT-o<0Uj}1;8UF^DsEnt081pAXCxfrUHIcwbp@d2L&0oIJBEw6D?r5QDv&smJ=J8_m=;0|LH0@<@Swi{bz(&a zte?VM5ylPyj<>Gbdi~4Qg-k5pHnsAYvL;WTqu)c<#Y#4CEx)gS*-{Q>iGQ3$*lEHy z27l|TEJd`Wbg!|p@dKonqqKzWn*uK_sOyDYhiaEltmm=KjoM}whu!&@7*C7^dPPk~ zZ02o55q;uwXOrK5dUx8hPMb0T13Oq^L%gl0JbyG zLI_~b8enolmw_H~aelN8^wh0>KnuDFNXBP4ew>)15Xt}i%HQR45Kt2%V)0YMV39?E zjU5{kmFFlAusXH)Zv;%O*hnmYio!i9gQ%=Z!DE#27jtwz0!^`N6oj9FJ+( zZ>ZN7)%}a|#|yx@+=x}C9dHu7Bzf>SuYt~vDG9^3?f%{ahx*wOna1=K2EfrF{UW;0 z)_tNCnBYBjro3P6T{PmjLExr~mp026uroL*nq$!jcd%^u9VOB{&0?#^PjL_$U03EC7cG@uMesvJ3$g9TNq!RXtB!;2IYmK;^dr9hQi- z_NH6x!!7ApyF)zFP&Yi@qFqbBZ2hmQ(88Go7{y&*!)c}M z+id~Z?F6Pq!>5-6($$n%v6${scqTA(_Zu~^#1K^eMGfmY^IZF%TJ3L)s$F#I83YLn z`wL6EX61Z(;)MQPUK3T{MGE^y)O?cCo&)=1ax(^cAmQ}t(C^?wKyUkAy146b;faQ} zio>hH^?-x=A>({mCS~SZ>Z4NLSS>iiz3F&AO7i1V8JBJ;f}IP!tWQ5iGu66u6B=i6 z4C^J~Y1YoXGL~^eP+u2NeN)17mm@Y#<}^2j_{Tm1s;h4v3>r1Y=)?jt0?{|z0Q^wK z7!R6a(YU0id|tz0#I&LHciDu;!CoamF*E>&Sgr^31(1gDbT zd8aAg2jUwSySh9`3pH34(eG=h!l3q4JE>BMKsPY z0C9vG%Z)2htX;R>D?B48-Oh?}1(qd0TJyw48vf{X+&ladUIZOl#K}UCIVXmUe~ty8 z;sD(P3_ZUMZG#p5+B=8Mj_>nh*(AELIOwVfpt;Hv$=0qjEfjA1sxI+U*&ZMi>ZdwpcI#kB{VlIoVu`i!{`DBMr;9x&ALqQN{%A{~Jiw{sxj6mvrN{rW0NA z0)H!5^}i?9eSkJknP^;6Qp(k`7^R0R{pXK-&s+t@h9oYYUT+5UP@NLemF{srQ*UNQ zz2WG8*1gUkSn^<;oGlDPR|*JsVK-1h{42m-7uarw)nw}58={WNW^8eCk{Xzs-9Slx zP97tW{JK^2@IR)YB3A&7`}ZVg=J$E>yDtFI;Cu5BF4CsD+}4cc%inbdb9m7IP@(Y6 zD28`PKKnm^=3~+sW}KYr0p^Mc5D*F{f>A*2l7KWUs%W$4pZWL&=PCH|yA&DlpC?#0 zo=v*^v{5}5DR8)-qF(h;?xhC{jA$Un^PIlsk)lkxn-GyUnWhTwJK=GdDNx>9=0T71 zkphIYk16~(=~wXSYD`%-Hej9?CYl<&zV(H5Lwk(<1w4Zx#=U0^Z*(Ciz5ZlnY8?*rg$u*Q0)u7BTW{V5)FJ093w zG$akUA`q}<(GB3rqB9`ROe9E(epkYY^V|jwUUdMnufvyVe;Otp4Q;ou1|JRS0q&Cr zisOUrZeLEW;GPbIx6ohK3L8^*=+boz+p_cWnV(Duou+rYjOCP0U2RRbUZQx--P?h3f-Xzxrsh|~lrhDCb-@%^eO1nULco%!?V z{a~!L_m9OR1h9$d?lcs&4X(8PV?_fq^5lRJIuslRM`{Dgb9X-OXW6_Tc$H~uwT10> zYi&F9E;E$KsyKQcWjrX|3yvfBt!%kKE%km6 z9*H?Ka@tS>do+#*A#W@KU^DB#f5`dXzw317>G3~vA&CepstX`fsS_ynFJOT$Z{i03 zw1}bSm^l!P)N&w36SY@bot>t$SjL{@UR!)#Wzi}JS^ms`t>)(~y#Pk_<5rs3|J>l4 z|38^(5l{x)XbTex7F6b9z2~Zs0Bpzc&#np7G5A%gKDm|ZH4bb83Y6!VfL&}-D~3)M d|8u<}!EfgGA4c5&eeQpE&;NU72myZjzX88_PXqt} delta 167 ncmX>$gYml5h6E Date: Thu, 11 Jul 2024 13:41:17 -0700 Subject: [PATCH 18/24] normalzie uvNodeNormal --- src/nodes/display/PixelationNode.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nodes/display/PixelationNode.js b/src/nodes/display/PixelationNode.js index eb71c332e2f66c..0b6609ad1f788a 100644 --- a/src/nodes/display/PixelationNode.js +++ b/src/nodes/display/PixelationNode.js @@ -131,7 +131,7 @@ class PixelationNode extends TempNode { const sampleDepth = ( x, y ) => depthNode.uv( uvNodeDepth.add( vec2( x, y ).mul( this._resolution.zw ) ) ).r; - const sampleNormal = ( x, y ) => normalNode.uv( uvNodeNormal.add( vec2( x, y ).mul( this._resolution.zw ) ) ).rgb.mul( 2.0 ).oneMinus(); + const sampleNormal = ( x, y ) => normalNode.uv( uvNodeNormal.add( vec2( x, y ).mul( this._resolution.zw ) ) ).rgb.normalize(); const depthEdgeIndicator = ( depth ) => { From 09893e9428db7a9be41df8c7cab9eebc3cb1b1b4 Mon Sep 17 00:00:00 2001 From: Christian Helgeson Date: Thu, 11 Jul 2024 13:49:12 -0700 Subject: [PATCH 19/24] remove unused directionToColor import, floor widtth and height of resolution, change uniform naming to match other uniforms, fix comment spacing in updateBefore --- examples/webgpu_postprocessing_pixel.html | 3 +-- src/nodes/display/PixelationNode.js | 9 +++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/webgpu_postprocessing_pixel.html b/examples/webgpu_postprocessing_pixel.html index 90104ae4e7b0e9..0cf5cb624ab5e0 100644 --- a/examples/webgpu_postprocessing_pixel.html +++ b/examples/webgpu_postprocessing_pixel.html @@ -33,7 +33,7 @@ import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; - import { pass, mrt, output, normalView, uniform, directionToColor } from 'three/tsl'; + import { pass, mrt, output, normalView, uniform } from 'three/tsl'; let camera, scene, renderer, postProcessing, crystalMesh, clock; let gui, effectController; @@ -162,7 +162,6 @@ gui.add( effectController.depthEdgeStrength, 'value', 0, 1, 0.05 ).name( 'Depth Edge Strength' ); gui.add( effectController, 'pixelAlignedPanning' ); - } function onWindowResize() { diff --git a/src/nodes/display/PixelationNode.js b/src/nodes/display/PixelationNode.js index 0b6609ad1f788a..d75662f72e8e73 100644 --- a/src/nodes/display/PixelationNode.js +++ b/src/nodes/display/PixelationNode.js @@ -28,7 +28,7 @@ class PixelationNode extends TempNode { // Input uniforms - this.pixelSizeNode = pixelSizeNode; + this.pixelSize = pixelSize; this.normalEdgeStrength = normalEdgeStrength; this.depthEdgeStrength = depthEdgeStrength; @@ -71,8 +71,8 @@ class PixelationNode extends TempNode { // Set resolution uniform - const adjustedWidth = map.image.width / this.pixelSizeNode.value; - const adjustedHeight = map.image.height / this.pixelSizeNode.value; + const adjustedWidth = Math.floor( map.image.width / this.pixelSizeNode.value ); + const adjustedHeight = Math.floor( map.image.height / this.pixelSizeNode.value ); this._resolution.value.set( adjustedWidth, adjustedHeight, 1 / adjustedWidth, 1 / adjustedHeight ); const currentRenderTarget = renderer.getRenderTarget(); @@ -97,6 +97,7 @@ class PixelationNode extends TempNode { createEdgesQuad.render( renderer ); // Set input of next pass to output of last step. + textureNode.value = this._createEdgesRT.texture; // Apply lower resolution post-process step to lowerResolutionQuad. @@ -237,7 +238,7 @@ class PixelationNode extends TempNode { } -export const pixelation = ( node, depthNode, normalNode, pixelSize = 14, normalEdgeStrength = 0.3, depthEdgeStrength = 0.4 ) => nodeObject( new PixelationNode( nodeObject( node ).toTexture(), nodeObject( depthNode ).toTexture(), nodeObject( normalNode ).toTexture(), nodeObject( pixelSize ), nodeObject( normalEdgeStrength ), nodeObject( depthEdgeStrength ) ) ); +export const pixelation = ( node, depthNode, normalNode, pixelSize = 6, normalEdgeStrength = 0.3, depthEdgeStrength = 0.4 ) => nodeObject( new PixelationNode( nodeObject( node ).toTexture(), nodeObject( depthNode ).toTexture(), nodeObject( normalNode ).toTexture(), nodeObject( pixelSize ), nodeObject( normalEdgeStrength ), nodeObject( depthEdgeStrength ) ) ); addNodeElement( 'pixelation', pixelation ); From 2ea62ee9958a252ac6421805ae48c193244d4f2a Mon Sep 17 00:00:00 2001 From: Christian Helgeson Date: Thu, 11 Jul 2024 13:51:32 -0700 Subject: [PATCH 20/24] replace single expression tslFn function --- src/nodes/display/PixelationNode.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/nodes/display/PixelationNode.js b/src/nodes/display/PixelationNode.js index d75662f72e8e73..f1783f073aef6a 100644 --- a/src/nodes/display/PixelationNode.js +++ b/src/nodes/display/PixelationNode.js @@ -16,7 +16,7 @@ const lowerResolutionQuad = new QuadMesh(); class PixelationNode extends TempNode { - constructor( textureNode, depthNode, normalNode, pixelSizeNode, normalEdgeStrength, depthEdgeStrength ) { + constructor( textureNode, depthNode, normalNode, pixelSize, normalEdgeStrength, depthEdgeStrength ) { super(); @@ -71,8 +71,8 @@ class PixelationNode extends TempNode { // Set resolution uniform - const adjustedWidth = Math.floor( map.image.width / this.pixelSizeNode.value ); - const adjustedHeight = Math.floor( map.image.height / this.pixelSizeNode.value ); + const adjustedWidth = Math.floor( map.image.width / this.pixelSize.value ); + const adjustedHeight = Math.floor( map.image.height / this.pixelSize.value ); this._resolution.value.set( adjustedWidth, adjustedHeight, 1 / adjustedWidth, 1 / adjustedHeight ); const currentRenderTarget = renderer.getRenderTarget(); @@ -97,7 +97,7 @@ class PixelationNode extends TempNode { createEdgesQuad.render( renderer ); // Set input of next pass to output of last step. - + textureNode.value = this._createEdgesRT.texture; // Apply lower resolution post-process step to lowerResolutionQuad. @@ -227,7 +227,7 @@ class PixelationNode extends TempNode { createEdgesMaterial.needsUpdate = true; const lowerResolutionMaterial = this._lowerResolutionMaterial || ( this._lowerResolutionMaterial = builder.createNodeMaterial() ); - lowerResolutionMaterial.fragmentNode = lowerResolution().context( builder.getSharedContext() ); + lowerResolutionMaterial.fragmentNode = samplePixel().context( builder.getSharedContext() ); const properties = builder.getNodeProperties( this ); properties.textureNode = textureNode; From cff1d4d71bc1eab4a755d4ac8bc360c5cdc1c536 Mon Sep 17 00:00:00 2001 From: Christian Helgeson Date: Thu, 11 Jul 2024 13:56:06 -0700 Subject: [PATCH 21/24] update lowerResolutionMaterial, remove unnecessary const color assignment --- src/nodes/display/PixelationNode.js | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/nodes/display/PixelationNode.js b/src/nodes/display/PixelationNode.js index f1783f073aef6a..9622ce9b965b71 100644 --- a/src/nodes/display/PixelationNode.js +++ b/src/nodes/display/PixelationNode.js @@ -210,15 +210,7 @@ class PixelationNode extends TempNode { const strength = dei.greaterThan( 0 ).cond( float( 1.0 ).sub( dei.mul( this.depthEdgeStrength ) ), nei.mul( this.normalEdgeStrength ).add( 1 ) ); - const color = texel.mul( strength ); - - return color; - - } ); - - const lowerResolution = tslFn( () => { - - return samplePixel(); + return texel.mul( strength ); } ); @@ -228,6 +220,7 @@ class PixelationNode extends TempNode { const lowerResolutionMaterial = this._lowerResolutionMaterial || ( this._lowerResolutionMaterial = builder.createNodeMaterial() ); lowerResolutionMaterial.fragmentNode = samplePixel().context( builder.getSharedContext() ); + lowerResolutionMaterial.needsUpdate = true; const properties = builder.getNodeProperties( this ); properties.textureNode = textureNode; From 98e4c0ed03a9fe2d3dff28e634614180172a5c10 Mon Sep 17 00:00:00 2001 From: Christian Helgeson Date: Sun, 14 Jul 2024 17:39:20 -0700 Subject: [PATCH 22/24] revert to pixelationPass approach --- .../webgpu_postprocessing_pixel.jpg | Bin 19526 -> 17940 bytes examples/webgpu_postprocessing_pixel.html | 14 +- src/nodes/Nodes.js | 2 +- ...ixelationNode.js => PixelationPassNode.js} | 155 +++++++----------- 4 files changed, 63 insertions(+), 108 deletions(-) rename src/nodes/display/{PixelationNode.js => PixelationPassNode.js} (51%) diff --git a/examples/screenshots/webgpu_postprocessing_pixel.jpg b/examples/screenshots/webgpu_postprocessing_pixel.jpg index 97c17054f55ad7eb6824181b9851e0b1f9717fff..85dc9edb420f274d73508d527a2e5818ecf6680c 100644 GIT binary patch literal 17940 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 diff --git a/examples/webgpu_postprocessing_pixel.html b/examples/webgpu_postprocessing_pixel.html index 0cf5cb624ab5e0..58f3972c578007 100644 --- a/examples/webgpu_postprocessing_pixel.html +++ b/examples/webgpu_postprocessing_pixel.html @@ -33,7 +33,7 @@ import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; import { GUI } from 'three/addons/libs/lil-gui.module.min.js'; - import { pass, mrt, output, normalView, uniform } from 'three/tsl'; + import { uniform, pixelationPass } from 'three/tsl'; let camera, scene, renderer, postProcessing, crystalMesh, clock; let gui, effectController; @@ -138,16 +138,8 @@ }; postProcessing = new THREE.PostProcessing( renderer ); - - const scenePass = pass( scene, camera ); - scenePass.setMRT( mrt( { - output: output, - normal: normalView , - } ) ); - - const depthNode = scenePass.getTextureNode( 'depth' ); - const normalNode = scenePass.getTextureNode( 'normal' ); - postProcessing.outputNode = scenePass.getTextureNode( 'output' ).pixelation( depthNode, normalNode, effectController.pixelSize, effectController.normalEdgeStrength, effectController.depthEdgeStrength ); + const scenePass = pixelationPass( scene, camera, effectController.pixelSize, effectController.normalEdgeStrength, effectController.depthEdgeStrength ); + postProcessing.outputNode = scenePass; window.addEventListener( 'resize', onWindowResize ); diff --git a/src/nodes/Nodes.js b/src/nodes/Nodes.js index b53ad8f56cee19..665a61c2b2a935 100644 --- a/src/nodes/Nodes.js +++ b/src/nodes/Nodes.js @@ -135,7 +135,7 @@ export { default as RGBShiftNode, rgbShift } from './display/RGBShiftNode.js'; export { default as FilmNode, film } from './display/FilmNode.js'; export { default as Lut3DNode, lut3D } from './display/Lut3DNode.js'; export { default as RenderOutputNode, renderOutput } from './display/RenderOutputNode.js'; -export { default as PixelationNode, pixelation } from './display/PixelationNode.js'; +export { default as PixelationPassNode, pixelationPass } from './display/PixelationPassNode.js'; export { default as PassNode, pass, passTexture, depthPass } from './display/PassNode.js'; diff --git a/src/nodes/display/PixelationNode.js b/src/nodes/display/PixelationPassNode.js similarity index 51% rename from src/nodes/display/PixelationNode.js rename to src/nodes/display/PixelationPassNode.js index 9622ce9b965b71..972f3a5e2316b9 100644 --- a/src/nodes/display/PixelationNode.js +++ b/src/nodes/display/PixelationPassNode.js @@ -5,15 +5,12 @@ import { NodeUpdateType } from '../core/constants.js'; import { uniform } from '../core/UniformNode.js'; import { dot, clamp, smoothstep, sign, step, floor } from '../math/MathNode.js'; import { Vector4 } from '../../math/Vector4.js'; -import { property } from '../core/PropertyNode.js'; -import QuadMesh from '../../renderers/common/QuadMesh.js'; -import { RenderTarget } from '../../core/RenderTarget.js'; -import { passTexture } from './PassNode.js'; +import { output, property } from '../core/PropertyNode.js'; +import PassNode from './PassNode.js'; +import { mrt } from '../core/MRTNode.js'; +import { normalView } from '../accessors/NormalNode.js'; import { NearestFilter } from '../../constants.js'; -const createEdgesQuad = new QuadMesh(); -const lowerResolutionQuad = new QuadMesh(); - class PixelationNode extends TempNode { constructor( textureNode, depthNode, normalNode, pixelSize, normalEdgeStrength, depthEdgeStrength ) { @@ -36,87 +33,22 @@ class PixelationNode extends TempNode { this._resolution = uniform( new Vector4() ); - // Intermediary render targets - - this._createEdgesRT = new RenderTarget(); - this._createEdgesRT.texture.name = 'PixelationNode.renderEdges'; - this._createEdgesRT.texture.minFilter = NearestFilter; - this._createEdgesRT.texture.magFilter = NearestFilter; - this._lowerResolutionRT = new RenderTarget(); - this._lowerResolutionRT.texture.name = 'PixelationNode.lowerResolution'; - this._lowerResolutionRT.texture.minFilter = NearestFilter; - this._lowerResolutionRT.texture.magFilter = NearestFilter; - - // Output textures - - this._outputTextureNode = passTexture( this, this._lowerResolutionRT.texture ); - this.updateBeforeType = NodeUpdateType.RENDER; } - setSize( width, height ) { - - this._createEdgesRT.setSize( width, height ); - this._lowerResolutionRT.setSize( width, height ); - - } - - updateBefore( frame ) { - - const { renderer } = frame; - - const textureNode = this.textureNode; - const map = textureNode.value; - - // Set resolution uniform - - const adjustedWidth = Math.floor( map.image.width / this.pixelSize.value ); - const adjustedHeight = Math.floor( map.image.height / this.pixelSize.value ); - this._resolution.value.set( adjustedWidth, adjustedHeight, 1 / adjustedWidth, 1 / adjustedHeight ); - - const currentRenderTarget = renderer.getRenderTarget(); - const currentMRT = renderer.getMRT(); - const currentTexture = textureNode.value; + updateBefore() { - createEdgesQuad.material = this._createEdgesMaterial; - lowerResolutionQuad.material = this._lowerResolutionMaterial; + const map = this.textureNode.value; - // Set size of render edges to match size of initial render target. + const width = map.image.width; + const height = map.image.height; - this.setSize( map.image.width, map.image.height ); - - const textureType = map.type; - this._createEdgesRT.texture.type = textureType; - this._lowerResolutionRT.texture.type = textureType; - - // Apply create edges post-process step to createEdgesQuad. - - renderer.setMRT( null ); - renderer.setRenderTarget( this._createEdgesRT ); - createEdgesQuad.render( renderer ); - - // Set input of next pass to output of last step. - - textureNode.value = this._createEdgesRT.texture; - - // Apply lower resolution post-process step to lowerResolutionQuad. - - renderer.setRenderTarget( this._lowerResolutionRT ); - lowerResolutionQuad.render( renderer ); - - // Reset render target and MRT back to initial values. - - renderer.setRenderTarget( currentRenderTarget ); - renderer.setMRT( currentMRT ); - - // Set textureNode back to intial input texture for next pass. - - textureNode.value = currentTexture; + this._resolution.value.set( width, height, 1 / width, 1 / height ); } - setup( builder ) { + setup() { const { textureNode, depthNode, normalNode } = this; @@ -124,12 +56,8 @@ class PixelationNode extends TempNode { const uvNodeDepth = depthNode.uvNode || uv(); const uvNodeNormal = normalNode.uvNode || uv(); - const pixelizeUV = ( coord ) => floor( coord.mul( this._resolution.xy ) ).div( this._resolution.xy ); - const sampleTexture = () => textureNode.uv( uvNodeTexture ); - const samplePixel = () => textureNode.uv( pixelizeUV( uvNodeTexture ) ); - const sampleDepth = ( x, y ) => depthNode.uv( uvNodeDepth.add( vec2( x, y ).mul( this._resolution.zw ) ) ).r; const sampleNormal = ( x, y ) => normalNode.uv( uvNodeNormal.add( vec2( x, y ).mul( this._resolution.zw ) ) ).rgb.normalize(); @@ -178,7 +106,7 @@ class PixelationNode extends TempNode { }; - const createEdges = tslFn( () => { + const pixelation = tslFn( () => { const texel = sampleTexture(); @@ -214,25 +142,60 @@ class PixelationNode extends TempNode { } ); - const createEdgesMaterial = this._createEdgesMaterial || ( this._createEdgesMaterial = builder.createNodeMaterial() ); - createEdgesMaterial.fragmentNode = createEdges().context( builder.getSharedContext() ); - createEdgesMaterial.needsUpdate = true; - - const lowerResolutionMaterial = this._lowerResolutionMaterial || ( this._lowerResolutionMaterial = builder.createNodeMaterial() ); - lowerResolutionMaterial.fragmentNode = samplePixel().context( builder.getSharedContext() ); - lowerResolutionMaterial.needsUpdate = true; + const outputNode = pixelation(); - const properties = builder.getNodeProperties( this ); - properties.textureNode = textureNode; - - return this._outputTextureNode; + return outputNode; } } -export const pixelation = ( node, depthNode, normalNode, pixelSize = 6, normalEdgeStrength = 0.3, depthEdgeStrength = 0.4 ) => nodeObject( new PixelationNode( nodeObject( node ).toTexture(), nodeObject( depthNode ).toTexture(), nodeObject( normalNode ).toTexture(), nodeObject( pixelSize ), nodeObject( normalEdgeStrength ), nodeObject( depthEdgeStrength ) ) ); +const pixelation = ( node, depthNode, normalNode, pixelSize = 6, normalEdgeStrength = 0.3, depthEdgeStrength = 0.4 ) => nodeObject( new PixelationNode( nodeObject( node ).toTexture(), nodeObject( depthNode ).toTexture(), nodeObject( normalNode ).toTexture(), nodeObject( pixelSize ), nodeObject( normalEdgeStrength ), nodeObject( depthEdgeStrength ) ) ); addNodeElement( 'pixelation', pixelation ); -export default PixelationNode; +class PixelationPassNode extends PassNode { + + constructor( scene, camera, pixelSize = 6, normalEdgeStrength = 0.3, depthEdgeStrength = 0.4 ) { + + super( 'color', scene, camera, { minFilter: NearestFilter, magFilter: NearestFilter } ); + + this.pixelSize = pixelSize; + this.normalEdgeStrength = normalEdgeStrength; + this.depthEdgeStrength = depthEdgeStrength; + + this.isPixelationPassNode = true; + + this._mrt = mrt( { + output: output, + normal: normalView + } ); + + } + + setSize( width, height ) { + + const pixelSize = this.pixelSize.value ? this.pixelSize.value : this.pixelSize; + + const adjustedWidth = Math.floor( width / pixelSize ); + const adjustedHeight = Math.floor( height / pixelSize ); + + super.setSize( adjustedWidth, adjustedHeight ); + + } + + setup() { + + const color = super.getTextureNode( 'output' ); + const depth = super.getTextureNode( 'depth' ); + const normal = super.getTextureNode( 'normal' ); + + return pixelation( color, depth, normal, this.pixelSize, this.normalEdgeStrength, this.depthEdgeStrength ); + + } + +} + +export const pixelationPass = ( scene, camera, pixelSize, normalEdgeStrength, depthEdgeStrength ) => nodeObject( new PixelationPassNode( scene, camera, pixelSize, normalEdgeStrength, depthEdgeStrength ) ); + +export default PixelationPassNode; \ No newline at end of file From 8742026fe6b3acc2bcaeb1b425c10e5f8336efbd Mon Sep 17 00:00:00 2001 From: Christian Helgeson Date: Sun, 14 Jul 2024 18:09:06 -0700 Subject: [PATCH 23/24] fix lint issue, ignore puppeteer test for now --- src/nodes/display/PixelationPassNode.js | 2 +- test/e2e/puppeteer.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/nodes/display/PixelationPassNode.js b/src/nodes/display/PixelationPassNode.js index 972f3a5e2316b9..625f1cd38209df 100644 --- a/src/nodes/display/PixelationPassNode.js +++ b/src/nodes/display/PixelationPassNode.js @@ -198,4 +198,4 @@ class PixelationPassNode extends PassNode { export const pixelationPass = ( scene, camera, pixelSize, normalEdgeStrength, depthEdgeStrength ) => nodeObject( new PixelationPassNode( scene, camera, pixelSize, normalEdgeStrength, depthEdgeStrength ) ); -export default PixelationPassNode; \ No newline at end of file +export default PixelationPassNode; diff --git a/test/e2e/puppeteer.js b/test/e2e/puppeteer.js index 90f534583296d9..17e071bb3f3b9e 100644 --- a/test/e2e/puppeteer.js +++ b/test/e2e/puppeteer.js @@ -126,6 +126,7 @@ const exceptionList = [ // WebGPURenderer: Unknown problem 'webgpu_postprocessing_afterimage', 'webgpu_postprocessing_3dlut', + "webgpu_postprocessing_pixel", 'webgpu_postprocessing_ao', 'webgpu_backdrop_water', 'webgpu_camera_logarithmicdepthbuffer', From e338644e2b1d136964f9a3f3ff0d8bcb66eba147 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Mon, 15 Jul 2024 11:27:23 +0200 Subject: [PATCH 24/24] Update webgpu_postprocessing_pixel.html --- examples/webgpu_postprocessing_pixel.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/webgpu_postprocessing_pixel.html b/examples/webgpu_postprocessing_pixel.html index 58f3972c578007..a44bc4867070b1 100644 --- a/examples/webgpu_postprocessing_pixel.html +++ b/examples/webgpu_postprocessing_pixel.html @@ -69,7 +69,7 @@ const mesh = new THREE.Mesh( new THREE.BoxGeometry( boxSideLength, boxSideLength, boxSideLength ), boxMaterial ); mesh.castShadow = true; - mesh.receiveShadow = true; + //mesh.receiveShadow = true; mesh.rotation.y = rotation; mesh.position.y = boxSideLength / 2; mesh.position.set( x, boxSideLength / 2 + .0001, z ); @@ -101,7 +101,7 @@ specular: 0xffffff } ) ); - crystalMesh.receiveShadow = true; + //crystalMesh.receiveShadow = true; crystalMesh.castShadow = true; scene.add( crystalMesh ); @@ -123,7 +123,7 @@ spotLight.castShadow = true; scene.add( spotLight ); - renderer = new THREE.WebGPURenderer({ antialias: false }); + renderer = new THREE.WebGPURenderer( { antialias: false } ); renderer.shadowMap.enabled = true; //renderer.setPixelRatio( window.devicePixelRatio ); renderer.setSize( window.innerWidth, window.innerHeight ); @@ -149,7 +149,7 @@ // gui gui = new GUI(); - gui.add( effectController.pixelSize, 'value', 1, 14, 1 ).name( 'Pixel Size' ); + gui.add( effectController.pixelSize, 'value', 1, 16, 1 ).name( 'Pixel Size' ); gui.add( effectController.normalEdgeStrength, 'value', 0, 2, 0.05 ).name( 'Normal Edge Strength' ); gui.add( effectController.depthEdgeStrength, 'value', 0, 1, 0.05 ).name( 'Depth Edge Strength' ); gui.add( effectController, 'pixelAlignedPanning' ); @@ -274,4 +274,4 @@ - \ No newline at end of file +