UNPKG

three-reflector2

Version:

Reflection of environment on any item in the scene

393 lines (342 loc) 12.9 kB
import {UniformsUtils, UniformsLib, ShaderChunk} from 'three'; import {Plane, Vector3, Vector4, Matrix4, PerspectiveCamera, WebGLRenderTarget, Math as TMath, LinearFilter, RGBAFormat} from 'three'; import {ShaderMaterial, DoubleSide, Color, TextureLoader, RepeatWrapping, } from 'three'; const ReflectorShader = { uniforms: UniformsUtils.merge( [ UniformsLib[ "ambient" ], UniformsLib['lights'], UniformsLib[ "fog" ],{ 'color': { type: 'c', value: null }, 'tDiffuse': { type: 't', value: null }, 'textureMatrix': { type: 'm4', value: null }, 'intensity': { type: 'f', value: 0.5 }, 'tOneWrapX': { type: 'f', value: 1.0 }, 'tOneWrapY': { type: 'f', value: 1.0 }, 'tTwoWrapX': { type: 'f', value: 1.0 }, 'tTwoWrapY': { type: 'f', value: 1.0 }, 'tOne': { type: 't', value: null }, 'tSec': { type: 't', value: null }, 'tOneFlag': { type: 'b', value: false }, 'tTwoFlag': { type: 'b', value: false }, 'invertedUV': { type: 'b', value: false } }]), vertexShader: [ '#ifdef GL_ES', 'precision highp float;', '#endif', 'uniform bool invertedUV;', 'uniform mat4 textureMatrix;', 'varying vec2 vUv;', 'varying vec4 vUv2;', 'void main()', '{', 'vUv = uv;', 'vUv2 = textureMatrix * vec4( position, 1.0 );', 'if(invertedUV)', '{', 'vUv[0] = uv[0];', 'vUv[1] = 1.0 - uv[1];', '}', 'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', '}', ].join( '\n' ), fragmentShader: [ '// All variables related to Texture one and two', '//Is Texture One available', 'uniform bool tOneFlag;', '//Is Texture Two available', 'uniform bool tTwoFlag;', '//If the model is GLTF sometimes the uv is inverted', 'uniform bool invertedUV;', '//The wrap repeat x and y for texture one', 'uniform float tOneWrapX;', 'uniform float tOneWrapY;', '//The wrap repeat x and y for texture two', 'uniform float tTwoWrapX;', 'uniform float tTwoWrapY;', '//The textures themselves', 'uniform sampler2D tOne;', 'uniform sampler2D tSec;', '//The tDiffuse holds the texture of the scene reflection', 'uniform sampler2D tDiffuse; ' , '//The intensity of the reflection', 'uniform float intensity;', '//The color of the material incase of two textures arent available', 'uniform vec3 color;', '//vUv2 and vUv is coming from uv coordinates and texture matrix projection', 'varying vec2 vUv;', 'varying vec4 vUv2;', ShaderChunk[ "common" ], ShaderChunk[ "fog_pars_fragment" ], 'void main() ', '{', 'vec3 c;', 'vec3 tcolors;', 'vec4 reflection = texture2DProj( tDiffuse, vUv2 );', 'vec4 Ca;', 'vec4 Cb;', 'if(!tOneFlag && !tTwoFlag)', '{', 'c = (reflection.rgb * (reflection.a * intensity)) + (color.rgb * (1.0 - (reflection.a * intensity)));', '}', 'if(tOneFlag && tTwoFlag)', '{', 'Ca = texture2D(tOne, vec2(vUv[0] * tOneWrapX, vUv[1] * tOneWrapY));', 'Cb = texture2D(tSec, vec2(vUv[0] * tTwoWrapY, vUv[1] * tTwoWrapY));', 'tcolors = (Ca.rgb * 0.5) + (Cb.rgb * 0.5);', 'c = (reflection.rgb * (reflection.a * intensity)) + (tcolors.rgb * (1.0 - (reflection.a * intensity)));', '}', 'else if(tOneFlag && !tTwoFlag)', '{', 'Ca = texture2D(tOne, vec2(vUv[0] * tOneWrapX, vUv[1] * tOneWrapY));', 'tcolors = (Ca.rgb * 1.0);', 'c = (reflection.rgb * (reflection.a * intensity)) + (tcolors.rgb * (1.0 - (reflection.a * intensity)));', '}', 'else if(!tOneFlag && tTwoFlag)', '{', 'Cb = texture2D(tSec, vec2(vUv[0] * tTwoWrapY, vUv[1] * tTwoWrapY));', 'tcolors = (Cb.rgb * 1.0);', 'c = (reflection.rgb * (reflection.a * intensity)) + (tcolors.rgb * (1.0 - (reflection.a * intensity)));', '}', 'gl_FragColor += vec4(c, 1.0);', ShaderChunk[ "fog_fragment" ], '}', ].join( '\n' ), }; class GroundSceneReflector { constructor(meshobject, renderer, scene, data) { var three_scene = scene; var mirrorObj = meshobject; if(!mirrorObj) { return; } this.renderer = renderer; this.data = (data) ? data : {}; // this.renderer.alpha = true; this.data.textureWidth = (this.data.textureWidth)? this.data.textureWidth : 256; this.data.textureHeight = (this.data.textureHeight)? this.data.textureHeight : 256; this.data.intensity = (this.data.intensity)? this.data.intensity : 0.5; this.data.invertedUV = (this.data.invertedUV)? this.data.invertedUV : false; this.data.wrapOne = (this.data.wrapOne)? this.data.wrapOne : {x:1, y: 1}; this.data.wrapTwo = (this.data.wrapTwo)? this.data.wrapTwo : {x:1, y: 1}; this.data.color = (this.data.color)? this.data.color : '#848485'; var reflectorPlane = new Plane(); var normal = new Vector3(); var reflectorWorldPosition = new Vector3(); var cameraWorldPosition = new Vector3(); var rotationMatrix = new Matrix4(); var lookAtPosition = new Vector3( 0, 0, - 1 ); var clipPlane = new Vector4(); var viewport = new Vector4(); var view = new Vector3(); var target = new Vector3(); var q = new Vector4(); var textureMatrix = new Matrix4(); var virtualCamera = new PerspectiveCamera(); var renderTarget = new WebGLRenderTarget(this.data.textureWidth, this.data.textureHeight, {minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat, } ); if ( ! TMath.isPowerOfTwo( this.data.textureWidth ) || ! TMath.isPowerOfTwo( this.data.textureHeight ) ) { renderTarget.texture.generateMipmaps = false; } var scope = mirrorObj; var color = this.data.color; var textureWidth = this.data.textureWidth; var textureHeight = this.data.textureHeight; var clipBias = 0; var shader = ReflectorShader; var recursion = 0; var material = undefined; mirrorObj.material = undefined; if(!mirrorObj.material) { material = new ShaderMaterial( { uniforms: UniformsUtils.clone( shader.uniforms ), fragmentShader: shader.fragmentShader, vertexShader: shader.vertexShader, side: DoubleSide, transparent: true, lights: true, }); material.uniforms.intensity.value = this.data.intensity; material.uniforms.tDiffuse.value = renderTarget.texture; material.uniforms.color.value = new Color(this.data.color); material.uniforms.invertedUV.value = this.data.invertedUV; material.uniforms.textureMatrix.value = textureMatrix; if(this.data.textureOne) { var texture = new TextureLoader().load(this.data.textureOne); texture.wrapS = RepeatWrapping; texture.wrapT = RepeatWrapping; texture.repeat.set( this.data.wrapOne.x, this.data.wrapOne.y ); material.uniforms.tOneFlag.value = true; material.uniforms.tOne.value = texture; material.uniforms.tOneWrapX.value = texture.repeat.x; material.uniforms.tOneWrapY.value = texture.repeat.y; } if(this.data.textureTwo) { var texture = new TextureLoader().load(this.data.textureTwo); texture.wrapS = RepeatWrapping; texture.wrapT = RepeatWrapping; texture.repeat.set( this.data.wrapTwo.x, this.data.wrapTwo.y ); material.uniforms.tTwoFlag.value = true; material.uniforms.tSec.value = texture; material.uniforms.tTwoWrapX.value = texture.repeat.x; material.uniforms.tTwoWrapY.value = texture.repeat.y; } mirrorObj.material = material; this.material = material; } mirrorObj.onBeforeRender = function(renderer, scene, camera) { if ( 'recursion' in camera.userData ) { if ( camera.userData.recursion === recursion ) return; camera.userData.recursion ++; } reflectorWorldPosition.setFromMatrixPosition( scope.matrixWorld ); cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld ); rotationMatrix.extractRotation( scope.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.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; // Used in WebGLBackground virtualCamera.fov = camera.fov; virtualCamera.updateMatrixWorld(); virtualCamera.projectionMatrix.copy( camera.projectionMatrix ); virtualCamera.userData.recursion = 0; // Update the texture matrix textureMatrix.set( 0.5, 0.0, 0.0, 0.5, 0.0, 0.5, 0.0, 0.5, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 1.0 ); textureMatrix.multiply( virtualCamera.projectionMatrix ); textureMatrix.multiply( virtualCamera.matrixWorldInverse ); textureMatrix.multiply( scope.matrixWorld ); // 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 ); var 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( 2.0 / clipPlane.dot( q ) ); // Replacing the third row of the projection matrix projectionMatrix.elements[ 2 ] = clipPlane.x; projectionMatrix.elements[ 6 ] = clipPlane.y; projectionMatrix.elements[ 10 ] = clipPlane.z + 1.0 - clipBias; projectionMatrix.elements[ 14 ] = clipPlane.w; // Render scope.visible = false; var currentRenderTarget = renderer.getRenderTarget(); var currentVrEnabled = renderer.vr.enabled; var currentShadowAutoUpdate = renderer.shadowMap.autoUpdate; renderer.vr.enabled = false; // Avoid camera modification and recursion renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows renderer.render( scene, virtualCamera, renderTarget, true ); renderer.vr.enabled = currentVrEnabled; renderer.shadowMap.autoUpdate = currentShadowAutoUpdate; renderer.setRenderTarget( currentRenderTarget ); // Restore viewport var bounds = camera.bounds; if ( bounds !== undefined ) { var size = renderer.getSize(); var pixelRatio = renderer.getPixelRatio(); viewport.x = bounds.x * size.width * pixelRatio; viewport.y = bounds.y * size.height * pixelRatio; viewport.z = bounds.z * size.width * pixelRatio; viewport.w = bounds.w * size.height * pixelRatio; renderer.state.viewport( viewport ); } scope.visible = true; }; } } // 'vUv2 = textureMatrix * vec4( position, 1.0 );', // 'vec4 reflection = texture2DProj( tDiffuse, vUv2 );', // The if condition that was there before calculating colors or textures // 'if(reflection.r == 1.0 && reflection.b == 1.0 && reflection.g == 1.0)', // '{', // 'reflection.a = 0.0;', // '}', // 'gl_FragColor = mix(gl_FragColor, vec4(c, 1.0), 1.0);', //module.exports = {ReflectorShader, GroundSceneReflector}; export {ReflectorShader, GroundSceneReflector};