UNPKG

three

Version:

JavaScript 3D library

330 lines (199 loc) 8.09 kB
import Node from '../core/Node.js'; import TextureNode from '../accessors/TextureNode.js'; import { nodeObject } from '../tsl/TSLBase.js'; import { NodeUpdateType } from '../core/constants.js'; import { screenUV } from '../display/ScreenNode.js'; import { HalfFloatType, LinearMipMapLinearFilter, WebGPUCoordinateSystem } from '../../constants.js'; import { Plane } from '../../math/Plane.js'; import { Object3D } from '../../core/Object3D.js'; import { Vector2 } from '../../math/Vector2.js'; import { Vector3 } from '../../math/Vector3.js'; import { Vector4 } from '../../math/Vector4.js'; import { Matrix4 } from '../../math/Matrix4.js'; import { RenderTarget } from '../../core/RenderTarget.js'; import { DepthTexture } from '../../textures/DepthTexture.js'; 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 = screenUV.flipX(); _defaultRT.depthTexture = new DepthTexture( 1, 1 ); let _inReflector = false; class ReflectorNode extends TextureNode { static get type() { return 'ReflectorNode'; } constructor( parameters = {} ) { super( parameters.defaultTexture || _defaultRT.texture, _defaultUV ); this._reflectorBaseNode = parameters.reflector || new ReflectorBaseNode( this, parameters ); this._depthNode = null; this.setUpdateMatrix( false ); } get reflector() { return this._reflectorBaseNode; } get target() { return this._reflectorBaseNode.target; } getDepthNode() { if ( this._depthNode === null ) { if ( this._reflectorBaseNode.depth !== true ) { throw new Error( 'THREE.ReflectorNode: Depth node can only be requested when the reflector is created with { depth: true }. ' ); } this._depthNode = nodeObject( new ReflectorNode( { defaultTexture: _defaultRT.depthTexture, reflector: this._reflectorBaseNode } ) ); } return this._depthNode; } setup( builder ) { // ignore if used in post-processing if ( ! builder.object.isQuadMesh ) this._reflectorBaseNode.build( builder ); return super.setup( builder ); } clone() { const texture = new this.constructor( this.reflectorNode ); texture._reflectorBaseNode = this._reflectorBaseNode; return texture; } } class ReflectorBaseNode extends Node { static get type() { return 'ReflectorBaseNode'; } constructor( textureNode, parameters = {} ) { super(); const { target = new Object3D(), resolution = 1, generateMipmaps = false, bounces = true, depth = false } = parameters; // this.textureNode = textureNode; this.target = target; this.resolution = resolution; this.generateMipmaps = generateMipmaps; this.bounces = bounces; this.depth = depth; 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 ); } 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; } if ( this.depth === true ) { renderTarget.depthTexture = new DepthTexture(); } this.renderTargets.set( camera, renderTarget ); } return renderTarget; } updateBefore( frame ) { if ( this.bounces === false && _inReflector ) return; _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 ] = ( renderer.coordinateSystem === WebGPUCoordinateSystem ) ? ( clipPlane.z - clipBias ) : ( clipPlane.z + 1.0 - clipBias ); projectionMatrix.elements[ 14 ] = clipPlane.w; // this.textureNode.value = renderTarget.texture; if ( this.depth === true ) { this.textureNode.getDepthNode().value = renderTarget.depthTexture; } material.visible = false; const currentRenderTarget = renderer.getRenderTarget(); const currentMRT = renderer.getMRT(); renderer.setMRT( null ); renderer.setRenderTarget( renderTarget ); renderer.render( scene, virtualCamera ); renderer.setMRT( currentMRT ); renderer.setRenderTarget( currentRenderTarget ); material.visible = true; _inReflector = false; } } export const reflector = ( parameters ) => nodeObject( new ReflectorNode( parameters ) ); export default ReflectorNode;