three.reflector = function ( geometry, options ) { three.mesh.call( this, geometry ); this.type = 'reflector'; var scope = this; options = options || {}; var color = ( options.color !== undefined ) ? new three.color( options.color ) : new three.color( 0x7f7f7f ); var texturewidth = options.texturewidth || 512; var textureheight = options.textureheight || 512; var clipbias = options.clipbias || 0; var shader = options.shader || three.reflector.reflectorshader; // var reflectorplane = new three.plane(); var normal = new three.vector3(); var reflectorworldposition = new three.vector3(); var cameraworldposition = new three.vector3(); var rotationmatrix = new three.matrix4(); var lookatposition = new three.vector3( 0, 0, - 1 ); var clipplane = new three.vector4(); var viewport = new three.vector4(); var view = new three.vector3(); var target = new three.vector3(); var q = new three.vector4(); var texturematrix = new three.matrix4(); var virtualcamera = new three.perspectivecamera(); var parameters = { minfilter: three.linearfilter, magfilter: three.linearfilter, format: three.rgbformat, stencilbuffer: false }; var rendertarget = new three.webglrendertarget( texturewidth, textureheight, parameters ); rendertarget.depthbuffer = true; rendertarget.depthtexture = new three.depthtexture(); rendertarget.depthtexture.type = three.unsignedshorttype; if ( ! three.math.ispoweroftwo( texturewidth ) || ! three.math.ispoweroftwo( textureheight ) ) { rendertarget.texture.generatemipmaps = false; } var material = new three.shadermaterial( { uniforms: three.uniformsutils.clone( shader.uniforms ), fragmentshader: shader.fragmentshader, vertexshader: shader.vertexshader, transparent: true } ); material.uniforms.tdiffuse.value = rendertarget.texture; material.uniforms.tdepth.value = rendertarget.depthtexture; material.uniforms.color.value = color; material.uniforms.texturematrix.value = texturematrix; this.material = material; this.onbeforerender = function ( renderer, scene, camera ) { 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.far = camera.far; // used in webglbackground virtualcamera.updatematrixworld(); virtualcamera.projectionmatrix.copy( camera.projectionmatrix ); this.material.uniforms.cameranear.value = camera.near; this.material.uniforms.camerafar.value = camera.far; // 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 rendertarget.texture.encoding = renderer.outputencoding; scope.visible = false; var currentrendertarget = renderer.getrendertarget(); var currentxrenabled = renderer.xr.enabled; var currentshadowautoupdate = renderer.shadowmap.autoupdate; renderer.xr.enabled = false; // avoid camera modification renderer.shadowmap.autoupdate = false; // avoid re-computing shadows renderer.setrendertarget( rendertarget ); renderer.state.buffers.depth.setmask( true ); // make sure the depth buffer is writable so it can be properly cleared, see #18897 if ( renderer.autoclear === false ) renderer.clear(); renderer.render( scene, virtualcamera ); renderer.xr.enabled = currentxrenabled; 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; }; this.getrendertarget = function () { return rendertarget; }; }; three.reflector.prototype = object.create( three.mesh.prototype ); three.reflector.prototype.constructor = three.reflector; three.reflector.reflectorshader = { uniforms: { 'color': { type: 'c', value: null }, 'tdiffuse': { type: 't', value: null }, 'tdepth': { type: 't', value: null }, 'texturematrix': { type: 'm4', value: null }, 'cameranear': { type: 'f', value: 0 }, 'camerafar': { type: 'f', value: 0 }, }, vertexshader: [ 'uniform mat4 texturematrix;', 'varying vec4 vuv;', 'void main() {', ' vuv = texturematrix * vec4( position, 1.0 );', ' gl_position = projectionmatrix * modelviewmatrix * vec4( position, 1.0 );', '}' ].join( '\n' ), fragmentshader: [ '#include ', 'uniform vec3 color;', 'uniform sampler2d tdiffuse;', 'uniform sampler2d tdepth;', 'uniform float cameranear;', 'uniform float camerafar;', 'varying vec4 vuv;', 'float blendoverlay( float base, float blend ) {', ' return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) );', '}', 'vec3 blendoverlay( vec3 base, vec3 blend ) {', ' return vec3( blendoverlay( base.r, blend.r ), blendoverlay( base.g, blend.g ), blendoverlay( base.b, blend.b ) );', '}', 'float readdepth( sampler2d depthsampler, vec4 coord ) {', ' float fragcoordz = texture2dproj( depthsampler, coord ).x;', ' float viewz = perspectivedepthtoviewz( fragcoordz, cameranear, camerafar );', ' return viewztoorthographicdepth( viewz, cameranear, camerafar );', '}', 'void main() {', ' vec4 base = texture2dproj( tdiffuse, vuv );', ' float depth = readdepth( tdepth, vuv );', ' gl_fragcolor = vec4( blendoverlay( base.rgb, color ), 1.0 - ( depth * 7000.0 ) );', '}' ].join( '\n' ) };