diff --git a/examples/files.json b/examples/files.json index 737eceba99865a..a6c47df1812b25 100644 --- a/examples/files.json +++ b/examples/files.json @@ -356,6 +356,7 @@ "webgpu_occlusion", "webgpu_particles", "webgpu_portal", + "webgpu_reflection", "webgpu_rtt", "webgpu_sandbox", "webgpu_shadertoy", @@ -369,6 +370,7 @@ "webgpu_tsl_transpiler", "webgpu_video_panorama", "webgpu_postprocessing_afterimage", + "webgpu_mirror", "webgpu_multisampled_renderbuffers", "webgpu_materials_texture_anisotropy" ], diff --git a/examples/jsm/nodes/Nodes.js b/examples/jsm/nodes/Nodes.js index ba7b60404c4725..9ea7994cb80524 100644 --- a/examples/jsm/nodes/Nodes.js +++ b/examples/jsm/nodes/Nodes.js @@ -68,6 +68,7 @@ export { default as SplitNode } from './utils/SplitNode.js'; export { default as SpriteSheetUVNode, spritesheetUV } from './utils/SpriteSheetUVNode.js'; export { default as TimerNode, timerLocal, timerGlobal, timerDelta, frameId } from './utils/TimerNode.js'; export { default as TriplanarTexturesNode, triplanarTextures, triplanarTexture } from './utils/TriplanarTexturesNode.js'; +export { default as ReflectorNode, reflector } from './utils/ReflectorNode.js'; // shadernode export * from './shadernode/ShaderNode.js'; @@ -146,7 +147,7 @@ export { default as DirectionalLightNode } from './lighting/DirectionalLightNode export { default as SpotLightNode } from './lighting/SpotLightNode.js'; export { default as IESSpotLightNode } from './lighting/IESSpotLightNode.js'; export { default as AmbientLightNode } from './lighting/AmbientLightNode.js'; -export { default as LightsNode, lights, lightNodes, addLightNode } from './lighting/LightsNode.js'; +export { default as LightsNode, lights, lightsNode, addLightNode } from './lighting/LightsNode.js'; export { default as LightingNode /* @TODO: lighting (abstract), light */ } from './lighting/LightingNode.js'; export { default as LightingContextNode, lightingContext } from './lighting/LightingContextNode.js'; export { default as HemisphereLightNode } from './lighting/HemisphereLightNode.js'; diff --git a/examples/jsm/nodes/core/NodeFrame.js b/examples/jsm/nodes/core/NodeFrame.js index d7081c8b3c49e4..c415158d827918 100644 --- a/examples/jsm/nodes/core/NodeFrame.js +++ b/examples/jsm/nodes/core/NodeFrame.js @@ -53,9 +53,11 @@ class NodeFrame { if ( frameMap.get( node ) !== this.frameId ) { - frameMap.set( node, this.frameId ); + if ( node.updateBefore( this ) !== false ) { - node.updateBefore( this ); + frameMap.set( node, this.frameId ); + + } } @@ -65,9 +67,11 @@ class NodeFrame { if ( renderMap.get( node ) !== this.renderId ) { - renderMap.set( node, this.renderId ); + if ( node.updateBefore( this ) !== false ) { + + renderMap.set( node, this.renderId ); - node.updateBefore( this ); + } } @@ -90,9 +94,11 @@ class NodeFrame { if ( frameMap.get( node ) !== this.frameId ) { - frameMap.set( node, this.frameId ); + if ( node.update( this ) !== false ) { - node.update( this ); + frameMap.set( node, this.frameId ); + + } } @@ -102,9 +108,11 @@ class NodeFrame { if ( renderMap.get( node ) !== this.renderId ) { - renderMap.set( node, this.renderId ); + if ( node.update( this ) !== false ) { + + renderMap.set( node, this.renderId ); - node.update( this ); + } } diff --git a/examples/jsm/nodes/display/GaussianBlurNode.js b/examples/jsm/nodes/display/GaussianBlurNode.js index f3354db88479f8..7b714dfaec1747 100644 --- a/examples/jsm/nodes/display/GaussianBlurNode.js +++ b/examples/jsm/nodes/display/GaussianBlurNode.js @@ -18,7 +18,7 @@ class GaussianBlurNode extends TempNode { constructor( textureNode, sigma = 2 ) { - super( textureNode ); + super( 'vec4' ); this.textureNode = textureNode; this.sigma = sigma; diff --git a/examples/jsm/nodes/lighting/LightsNode.js b/examples/jsm/nodes/lighting/LightsNode.js index 1e79e5f8d7fb13..c1a6e992c7ef85 100644 --- a/examples/jsm/nodes/lighting/LightsNode.js +++ b/examples/jsm/nodes/lighting/LightsNode.js @@ -169,7 +169,7 @@ class LightsNode extends Node { export default LightsNode; export const lights = ( lights ) => nodeObject( new LightsNode().fromLights( lights ) ); -export const lightNodes = nodeProxy( LightsNode ); +export const lightsNode = nodeProxy( LightsNode ); export function addLightNode( lightClass, lightNodeClass ) { diff --git a/examples/jsm/nodes/materials/NodeMaterial.js b/examples/jsm/nodes/materials/NodeMaterial.js index 6d764c33740858..9016ef3becce3b 100644 --- a/examples/jsm/nodes/materials/NodeMaterial.js +++ b/examples/jsm/nodes/materials/NodeMaterial.js @@ -11,7 +11,7 @@ import { skinning } from '../accessors/SkinningNode.js'; import { morph } from '../accessors/MorphNode.js'; import { texture } from '../accessors/TextureNode.js'; import { cubeTexture } from '../accessors/CubeTextureNode.js'; -import { lightNodes } from '../lighting/LightsNode.js'; +import { lightsNode } from '../lighting/LightsNode.js'; import { mix } from '../math/MathNode.js'; import { float, vec3, vec4 } from '../shadernode/ShaderNode.js'; import AONode from '../lighting/AONode.js'; @@ -289,15 +289,15 @@ class NodeMaterial extends ShaderMaterial { } - let lightsNode = this.lightsNode || builder.lightsNode; + let lightsN = this.lightsNode || builder.lightsNode; if ( materialLightsNode.length > 0 ) { - lightsNode = lightNodes( [ ...lightsNode.lightNodes, ...materialLightsNode ] ); + lightsN = lightsNode( [ ...lightsN.lightNodes, ...materialLightsNode ] ); } - return lightsNode; + return lightsN; } diff --git a/examples/jsm/nodes/utils/ReflectorNode.js b/examples/jsm/nodes/utils/ReflectorNode.js new file mode 100644 index 00000000000000..cd6b1414d67937 --- /dev/null +++ b/examples/jsm/nodes/utils/ReflectorNode.js @@ -0,0 +1,227 @@ +import TextureNode from '../accessors/TextureNode.js'; +import { nodeObject, vec2 } from '../shadernode/ShaderNode.js'; +import { NodeUpdateType } from '../core/constants.js'; +import { viewportTopLeft } from '../display/ViewportNode.js'; +import { Matrix4, Vector2, Vector3, Vector4, Object3D, Plane, RenderTarget, HalfFloatType, LinearMipMapLinearFilter } from 'three'; + +const _reflectorPlane = new Plane(); +const _normal = new Vector3(); +const _reflectorWorldPosition = new Vector3(); +const _cameraWorldPosition = new Vector3(); +const _rotationMatrix = new Matrix4(); +const _lookAtPosition = new Vector3( 0, 0, - 1 ); +const clipPlane = new Vector4(); + +const _view = new Vector3(); +const _target = new Vector3(); +const _q = new Vector4(); + +const _size = new Vector2(); + +const _defaultRT = new RenderTarget(); +const _defaultUV = vec2( viewportTopLeft.x.oneMinus(), viewportTopLeft.y ); + +let _inReflector = false; + +class ReflectorNode extends TextureNode { + + constructor( parameters = {} ) { + + super( _defaultRT.texture, _defaultUV ); + + const { + target = new Object3D(), + resolution = 1, + generateMipmaps = false, + bounces = true + } = parameters; + + // + + this.target = target; + this.resolution = resolution; + this.generateMipmaps = generateMipmaps; + this.bounces = bounces; + + this.updateBeforeType = bounces ? NodeUpdateType.RENDER : NodeUpdateType.FRAME; + + this.virtualCameras = new WeakMap(); + this.renderTargets = new WeakMap(); + + + } + + _updateResolution( renderTarget, renderer ) { + + const resolution = this.resolution; + + renderer.getDrawingBufferSize( _size ); + + renderTarget.setSize( Math.round( _size.width * resolution ), Math.round( _size.height * resolution ) ); + + } + + setup( builder ) { + + this._updateResolution( _defaultRT, builder.renderer ); + + return super.setup( builder ); + + } + + getTextureNode() { + + return this.textureNode; + + } + + getVirtualCamera( camera ) { + + let virtualCamera = this.virtualCameras.get( camera ); + + if ( virtualCamera === undefined ) { + + virtualCamera = camera.clone(); + + this.virtualCameras.set( camera, virtualCamera ); + + } + + return virtualCamera; + + } + + getRenderTarget( camera ) { + + let renderTarget = this.renderTargets.get( camera ); + + if ( renderTarget === undefined ) { + + renderTarget = new RenderTarget( 0, 0, { type: HalfFloatType } ); + + if ( this.generateMipmaps === true ) { + + renderTarget.texture.minFilter = LinearMipMapLinearFilter; + renderTarget.texture.generateMipmaps = true; + + } + + this.renderTargets.set( camera, renderTarget ); + + } + + return renderTarget; + + } + + updateBefore( frame ) { + + if ( this.bounces === false && _inReflector ) return false; + + _inReflector = true; + + const { scene, camera, renderer, material } = frame; + const { target } = this; + + const virtualCamera = this.getVirtualCamera( camera ); + const renderTarget = this.getRenderTarget( virtualCamera ); + + renderer.getDrawingBufferSize( _size ); + + this._updateResolution( renderTarget, renderer ); + + // + + _reflectorWorldPosition.setFromMatrixPosition( target.matrixWorld ); + _cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld ); + + _rotationMatrix.extractRotation( target.matrixWorld ); + + _normal.set( 0, 0, 1 ); + _normal.applyMatrix4( _rotationMatrix ); + + _view.subVectors( _reflectorWorldPosition, _cameraWorldPosition ); + + // Avoid rendering when reflector is facing away + + if ( _view.dot( _normal ) > 0 ) return; + + _view.reflect( _normal ).negate(); + _view.add( _reflectorWorldPosition ); + + _rotationMatrix.extractRotation( camera.matrixWorld ); + + _lookAtPosition.set( 0, 0, - 1 ); + _lookAtPosition.applyMatrix4( _rotationMatrix ); + _lookAtPosition.add( _cameraWorldPosition ); + + _target.subVectors( _reflectorWorldPosition, _lookAtPosition ); + _target.reflect( _normal ).negate(); + _target.add( _reflectorWorldPosition ); + + // + + virtualCamera.coordinateSystem = camera.coordinateSystem; + virtualCamera.position.copy( _view ); + virtualCamera.up.set( 0, 1, 0 ); + virtualCamera.up.applyMatrix4( _rotationMatrix ); + virtualCamera.up.reflect( _normal ); + virtualCamera.lookAt( _target ); + + virtualCamera.near = camera.near; + virtualCamera.far = camera.far; + + virtualCamera.updateMatrixWorld(); + virtualCamera.projectionMatrix.copy( camera.projectionMatrix ); + + // Now update projection matrix with new clip plane, implementing code from: http://www.terathon.com/code/oblique.html + // Paper explaining this technique: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf + _reflectorPlane.setFromNormalAndCoplanarPoint( _normal, _reflectorWorldPosition ); + _reflectorPlane.applyMatrix4( virtualCamera.matrixWorldInverse ); + + clipPlane.set( _reflectorPlane.normal.x, _reflectorPlane.normal.y, _reflectorPlane.normal.z, _reflectorPlane.constant ); + + const projectionMatrix = virtualCamera.projectionMatrix; + + _q.x = ( Math.sign( clipPlane.x ) + projectionMatrix.elements[ 8 ] ) / projectionMatrix.elements[ 0 ]; + _q.y = ( Math.sign( clipPlane.y ) + projectionMatrix.elements[ 9 ] ) / projectionMatrix.elements[ 5 ]; + _q.z = - 1.0; + _q.w = ( 1.0 + projectionMatrix.elements[ 10 ] ) / projectionMatrix.elements[ 14 ]; + + // Calculate the scaled plane vector + clipPlane.multiplyScalar( 1.0 / clipPlane.dot( _q ) ); + + const clipBias = 0; + + // Replacing the third row of the projection matrix + projectionMatrix.elements[ 2 ] = clipPlane.x; + projectionMatrix.elements[ 6 ] = clipPlane.y; + projectionMatrix.elements[ 10 ] = clipPlane.z - clipBias; + projectionMatrix.elements[ 14 ] = clipPlane.w; + + // + + this.value = renderTarget.texture; + + material.visible = false; + + const currentRenderTarget = renderer.getRenderTarget(); + + renderer.setRenderTarget( renderTarget ); + + renderer.render( scene, virtualCamera ); + + renderer.setRenderTarget( currentRenderTarget ); + + material.visible = true; + + _inReflector = false; + + } + +} + +export const reflector = ( parameters ) => nodeObject( new ReflectorNode( parameters ) ); + +export default ReflectorNode; + diff --git a/examples/screenshots/webgpu_mirror.jpg b/examples/screenshots/webgpu_mirror.jpg new file mode 100644 index 00000000000000..57325d0be92519 Binary files /dev/null and b/examples/screenshots/webgpu_mirror.jpg differ diff --git a/examples/screenshots/webgpu_reflection.jpg b/examples/screenshots/webgpu_reflection.jpg new file mode 100644 index 00000000000000..58982c762c25af Binary files /dev/null and b/examples/screenshots/webgpu_reflection.jpg differ diff --git a/examples/webgpu_mirror.html b/examples/webgpu_mirror.html new file mode 100644 index 00000000000000..1f86c632a5b37c --- /dev/null +++ b/examples/webgpu_mirror.html @@ -0,0 +1,224 @@ + + + + three.js webgpu - mirror + + + + + + +
+ three.js webgpu - mirror +
+ + + + + + diff --git a/examples/webgpu_reflection.html b/examples/webgpu_reflection.html new file mode 100644 index 00000000000000..a9c5a6c8da1ca9 --- /dev/null +++ b/examples/webgpu_reflection.html @@ -0,0 +1,206 @@ + + + + three.js webgpu - reflection + + + + + + +
+ three.js webgpu - reflection +
+ + + + + +