diff --git a/examples/files.json b/examples/files.json index a6c47df1812b25..e2c5c8c0cc7c30 100644 --- a/examples/files.json +++ b/examples/files.json @@ -370,6 +370,7 @@ "webgpu_tsl_transpiler", "webgpu_video_panorama", "webgpu_postprocessing_afterimage", + "webgpu_postprocessing_anamorphic", "webgpu_mirror", "webgpu_multisampled_renderbuffers", "webgpu_materials_texture_anisotropy" diff --git a/examples/jsm/nodes/Nodes.js b/examples/jsm/nodes/Nodes.js index 354d9136b0df4e..fa9b4f82326712 100644 --- a/examples/jsm/nodes/Nodes.js +++ b/examples/jsm/nodes/Nodes.js @@ -107,7 +107,7 @@ export { default as UserDataNode, userData } from './accessors/UserDataNode.js'; // display export { default as BlendModeNode, burn, dodge, overlay, screen } from './display/BlendModeNode.js'; export { default as BumpMapNode, bumpMap } from './display/BumpMapNode.js'; -export { default as ColorAdjustmentNode, saturation, vibrance, hue, lumaCoeffs, luminance } from './display/ColorAdjustmentNode.js'; +export { default as ColorAdjustmentNode, saturation, vibrance, hue, lumaCoeffs, luminance, threshold } from './display/ColorAdjustmentNode.js'; export { default as ColorSpaceNode, linearToColorSpace, colorSpaceToLinear, linearTosRGB, sRGBToLinear } from './display/ColorSpaceNode.js'; export { default as FrontFacingNode, frontFacing, faceDirection } from './display/FrontFacingNode.js'; export { default as NormalMapNode, normalMap, TBNViewMatrix } from './display/NormalMapNode.js'; @@ -120,6 +120,7 @@ export { default as ViewportDepthTextureNode, viewportDepthTexture } from './dis export { default as ViewportDepthNode, viewZToOrthographicDepth, orthographicDepthToViewZ, viewZToPerspectiveDepth, perspectiveDepthToViewZ, depth, depthTexture, depthPixel } from './display/ViewportDepthNode.js'; export { default as GaussianBlurNode, gaussianBlur } from './display/GaussianBlurNode.js'; export { default as AfterImageNode, afterImage } from './display/AfterImageNode.js'; +export { default as AnamorphicNode, anamorphic } from './display/AnamorphicNode.js'; export { default as PassNode, pass, depthPass } from './display/PassNode.js'; diff --git a/examples/jsm/nodes/display/AfterImageNode.js b/examples/jsm/nodes/display/AfterImageNode.js index 9f67fc5395db14..2e9314e71e0edb 100644 --- a/examples/jsm/nodes/display/AfterImageNode.js +++ b/examples/jsm/nodes/display/AfterImageNode.js @@ -3,6 +3,7 @@ import { nodeObject, addNodeElement, tslFn, float, vec4 } from '../shadernode/Sh import { NodeUpdateType } from '../core/constants.js'; import { uv } from '../accessors/UVNode.js'; import { texture } from '../accessors/TextureNode.js'; +import { texturePass } from './PassNode.js'; import { uniform } from '../core/UniformNode.js'; import { RenderTarget } from 'three'; import { sign, max } from '../math/MathNode.js'; @@ -26,10 +27,18 @@ class AfterImageNode extends TempNode { this._oldRT = new RenderTarget(); this._oldRT.texture.name = 'AfterImageNode.old'; + this._textureNode = texturePass( this, this._compRT.texture ); + this.updateBeforeType = NodeUpdateType.RENDER; } + getTextureNode() { + + return this._textureNode; + + } + setSize( width, height ) { this._compRT.setSize( width, height ); @@ -42,6 +51,12 @@ class AfterImageNode extends TempNode { const { renderer } = frame; const textureNode = this.textureNode; + const map = textureNode.value; + + const textureType = map.type; + + this._compRT.texture.type = textureType; + this._oldRT.texture.type = textureType; const currentRenderTarget = renderer.getRenderTarget(); const currentTexture = textureNode.value; @@ -58,7 +73,6 @@ class AfterImageNode extends TempNode { this._compRT = temp; // set size before swapping fails - const map = currentTexture; this.setSize( map.image.width, map.image.height ); renderer.setRenderTarget( currentRenderTarget ); @@ -120,7 +134,7 @@ class AfterImageNode extends TempNode { // - return texture( this._compRT.texture ); + return this._textureNode; } diff --git a/examples/jsm/nodes/display/AnamorphicNode.js b/examples/jsm/nodes/display/AnamorphicNode.js new file mode 100644 index 00000000000000..4092b45a62d558 --- /dev/null +++ b/examples/jsm/nodes/display/AnamorphicNode.js @@ -0,0 +1,148 @@ +import TempNode from '../core/TempNode.js'; +import { nodeObject, addNodeElement, tslFn, float, vec2, vec3, vec4 } from '../shadernode/ShaderNode.js'; +import { loop } from '../utils/LoopNode.js'; +import { uniform } from '../core/UniformNode.js'; +import { NodeUpdateType } from '../core/constants.js'; +import { threshold } from './ColorAdjustmentNode.js'; +import { uv } from '../accessors/UVNode.js'; +import { texturePass } from './PassNode.js'; +import { Vector2, RenderTarget } from 'three'; +import QuadMesh from '../../objects/QuadMesh.js'; + +const quadMesh = new QuadMesh(); + +class AnamorphicNode extends TempNode { + + constructor( textureNode, tresholdNode, scaleNode, samples ) { + + super( 'vec4' ); + + this.textureNode = textureNode; + this.tresholdNode = tresholdNode; + this.scaleNode = scaleNode; + this.colorNode = vec3( 0.1, 0.0, 1.0 ); + this.samples = samples; + this.resolution = new Vector2( 1, 1 ); + + this._renderTarget = new RenderTarget(); + this._renderTarget.texture.name = 'anamorphic'; + + this._invSize = uniform( new Vector2() ); + + this._textureNode = texturePass( this, this._renderTarget.texture ); + + this.updateBeforeType = NodeUpdateType.RENDER; + + } + + getTextureNode() { + + return this._textureNode; + + } + + setSize( width, height ) { + + this._invSize.value.set( 1 / width, 1 / height ); + + width = Math.max( Math.round( width * this.resolution.x ), 1 ); + height = Math.max( Math.round( height * this.resolution.y ), 1 ); + + this._renderTarget.setSize( width, height ); + + } + + updateBefore( frame ) { + + const { renderer } = frame; + + const textureNode = this.textureNode; + const map = textureNode.value; + + this._renderTarget.texture.type = map.type; + + const currentRenderTarget = renderer.getRenderTarget(); + const currentTexture = textureNode.value; + + quadMesh.material = this._material; + + this.setSize( map.image.width, map.image.height ); + + // render + + renderer.setRenderTarget( this._renderTarget ); + + quadMesh.render( renderer ); + + // restore + + renderer.setRenderTarget( currentRenderTarget ); + textureNode.value = currentTexture; + + } + + setup( builder ) { + + const textureNode = this.textureNode; + + if ( textureNode.isTextureNode !== true ) { + + console.error( 'AnamorphNode requires a TextureNode.' ); + + return vec4(); + + } + + // + + const uvNode = textureNode.uvNode || uv(); + + const sampleTexture = ( uv ) => textureNode.cache().context( { getUV: () => uv, forceUVContext: true } ); + + const anamorph = tslFn( () => { + + const samples = this.samples; + const halfSamples = Math.floor( samples / 2 ); + + const total = vec3( 0 ).toVar(); + + loop( { start: - halfSamples, end: halfSamples }, ( { i } ) => { + + const softness = float( i ).abs().div( halfSamples ).oneMinus(); + + const uv = vec2( uvNode.x.add( this._invSize.x.mul( i ).mul( this.scaleNode ) ), uvNode.y ); + const color = sampleTexture( uv ); + const pass = threshold( color, this.tresholdNode ).mul( softness ); + + total.addAssign( pass ); + + } ); + + return total.mul( this.colorNode ); + + } ); + + // + + const material = this._material || ( this._material = builder.createNodeMaterial() ); + material.fragmentNode = anamorph(); + + // + + const properties = builder.getNodeProperties( this ); + properties.textureNode = textureNode; + + // + + return this._textureNode; + + } + +} + +export const anamorphic = ( node, threshold = .9, scale = 3, samples = 32 ) => nodeObject( new AnamorphicNode( nodeObject( node ), nodeObject( threshold ), nodeObject( scale ), samples ) ); + +addNodeElement( 'anamorphic', anamorphic ); + +export default AnamorphicNode; + diff --git a/examples/jsm/nodes/display/ColorAdjustmentNode.js b/examples/jsm/nodes/display/ColorAdjustmentNode.js index ed330d8dc38ad8..fea51a4e120a09 100644 --- a/examples/jsm/nodes/display/ColorAdjustmentNode.js +++ b/examples/jsm/nodes/display/ColorAdjustmentNode.js @@ -89,8 +89,11 @@ export const hue = nodeProxy( ColorAdjustmentNode, ColorAdjustmentNode.HUE ); export const lumaCoeffs = vec3( 0.2125, 0.7154, 0.0721 ); export const luminance = ( color, luma = lumaCoeffs ) => dot( color, luma ); +export const threshold = ( color, threshold ) => mix( vec3( 0.0 ), color, luminance( color ).sub( threshold ).max( 0 ) ); + addNodeElement( 'saturation', saturation ); addNodeElement( 'vibrance', vibrance ); addNodeElement( 'hue', hue ); +addNodeElement( 'threshold', threshold ); addNodeClass( 'ColorAdjustmentNode', ColorAdjustmentNode ); diff --git a/examples/jsm/nodes/display/GaussianBlurNode.js b/examples/jsm/nodes/display/GaussianBlurNode.js index 7b714dfaec1747..4672e78c85d152 100644 --- a/examples/jsm/nodes/display/GaussianBlurNode.js +++ b/examples/jsm/nodes/display/GaussianBlurNode.js @@ -3,7 +3,7 @@ import { nodeObject, addNodeElement, tslFn, float, vec2, vec4 } from '../shadern import { NodeUpdateType } from '../core/constants.js'; import { mul } from '../math/OperatorNode.js'; import { uv } from '../accessors/UVNode.js'; -import { texture } from '../accessors/TextureNode.js'; +import { texturePass } from './PassNode.js'; import { uniform } from '../core/UniformNode.js'; import { Vector2, RenderTarget } from 'three'; import QuadMesh from '../../objects/QuadMesh.js'; @@ -33,6 +33,8 @@ class GaussianBlurNode extends TempNode { this._verticalRT = new RenderTarget(); this._verticalRT.texture.name = 'GaussianBlurNode.vertical'; + this._textureNode = texturePass( this, this._verticalRT.texture ); + this.updateBeforeType = NodeUpdateType.RENDER; this.resolution = new Vector2( 1, 1 ); @@ -65,6 +67,11 @@ class GaussianBlurNode extends TempNode { this.setSize( map.image.width, map.image.height ); + const textureType = map.type; + + this._horizontalRT.texture.type = textureType; + this._verticalRT.texture.type = textureType; + // horizontal renderer.setRenderTarget( this._horizontalRT ); @@ -89,6 +96,12 @@ class GaussianBlurNode extends TempNode { } + getTextureNode() { + + return this._textureNode; + + } + setup( builder ) { const textureNode = this.textureNode; @@ -149,7 +162,7 @@ class GaussianBlurNode extends TempNode { // - return texture( this._verticalRT.texture ); + return this._textureNode; } diff --git a/examples/jsm/nodes/display/PassNode.js b/examples/jsm/nodes/display/PassNode.js index 2d441c94bc85e3..2e4017ca82922e 100644 --- a/examples/jsm/nodes/display/PassNode.js +++ b/examples/jsm/nodes/display/PassNode.js @@ -177,6 +177,7 @@ PassNode.DEPTH = 'depth'; export default PassNode; export const pass = ( scene, camera ) => nodeObject( new PassNode( PassNode.COLOR, scene, camera ) ); +export const texturePass = ( pass, texture ) => nodeObject( new PassTextureNode( pass, texture ) ); export const depthPass = ( scene, camera ) => nodeObject( new PassNode( PassNode.DEPTH, scene, camera ) ); addNodeClass( 'PassNode', PassNode ); diff --git a/examples/screenshots/webgpu_postprocessing_anamorphic.jpg b/examples/screenshots/webgpu_postprocessing_anamorphic.jpg new file mode 100644 index 00000000000000..ad03051c5d8cf7 Binary files /dev/null and b/examples/screenshots/webgpu_postprocessing_anamorphic.jpg differ diff --git a/examples/webgpu_postprocessing_anamorphic.html b/examples/webgpu_postprocessing_anamorphic.html new file mode 100644 index 00000000000000..c77d3a0529eaee --- /dev/null +++ b/examples/webgpu_postprocessing_anamorphic.html @@ -0,0 +1,148 @@ + + + + three.js webgpu - postprocessing anamorphic + + + + + + +
+ three.js webgpu - postprocessing anamorphic
+ Battle Damaged Sci-fi Helmet by + theblueturtle_
+
+ + + + + + +