|
| 1 | +import { |
| 2 | + Color, |
| 3 | + LinearFilter, |
| 4 | + MathUtils, |
| 5 | + Matrix4, |
| 6 | + Mesh, |
| 7 | + PerspectiveCamera, |
| 8 | + Plane, |
| 9 | + RGBFormat, |
| 10 | + ShaderMaterial, |
| 11 | + UniformsUtils, |
| 12 | + Vector3, |
| 13 | + Vector4, |
| 14 | + WebGLRenderTarget, |
| 15 | + DepthTexture, |
| 16 | + UnsignedShortType, |
| 17 | + NearestFilter |
| 18 | +} from '../../../build/three.module.js'; |
| 19 | + |
| 20 | +var Reflector = function ( geometry, options ) { |
| 21 | + |
| 22 | + Mesh.call( this, geometry ); |
| 23 | + |
| 24 | + this.type = 'Reflector'; |
| 25 | + |
| 26 | + var scope = this; |
| 27 | + |
| 28 | + options = options || {}; |
| 29 | + |
| 30 | + var color = ( options.color !== undefined ) ? new Color( options.color ) : new Color( 0x7F7F7F ); |
| 31 | + var textureWidth = options.textureWidth || 512; |
| 32 | + var textureHeight = options.textureHeight || 512; |
| 33 | + var clipBias = options.clipBias || 0; |
| 34 | + var shader = options.shader || Reflector.ReflectorShader; |
| 35 | + var useDepthTexture = options.useDepthTexture |
| 36 | + var yAxis = new Vector3(0, 1, 0); |
| 37 | + var vecTemp0 = new Vector3(); |
| 38 | + var vecTemp1 = new Vector3(); |
| 39 | + |
| 40 | + // |
| 41 | + |
| 42 | + scope.needsUpdate = false; |
| 43 | + scope.maxDistance = Reflector.ReflectorShader.uniforms.maxDistance.value |
| 44 | + scope.opacity = Reflector.ReflectorShader.uniforms.opacity.value |
| 45 | + |
| 46 | + scope._isDistanceAttenuation = Reflector.ReflectorShader.defines.isDistanceAttenuation |
| 47 | + Object.defineProperty(scope, 'isDistanceAttenuation', { |
| 48 | + get() { |
| 49 | + return scope._isDistanceAttenuation |
| 50 | + }, |
| 51 | + set(val) { |
| 52 | + if (scope._isDistanceAttenuation === val) return |
| 53 | + scope._isDistanceAttenuation = val |
| 54 | + scope.material.defines.isDistanceAttenuation = val |
| 55 | + scope.material.needsUpdate = true |
| 56 | + } |
| 57 | + }) |
| 58 | + |
| 59 | + scope._isFresnel = Reflector.ReflectorShader.defines.isFresnel |
| 60 | + Object.defineProperty(scope, 'isFresnel', { |
| 61 | + get() { |
| 62 | + return scope._isFresnel |
| 63 | + }, |
| 64 | + set(val) { |
| 65 | + if (scope._isFresnel === val) return |
| 66 | + scope._isFresnel = val |
| 67 | + scope.material.defines.isFresnel = val |
| 68 | + scope.material.needsUpdate = true |
| 69 | + } |
| 70 | + }) |
| 71 | + |
| 72 | + var reflectorPlane = new Plane(); |
| 73 | + var normal = new Vector3(); |
| 74 | + var reflectorWorldPosition = new Vector3(); |
| 75 | + var cameraWorldPosition = new Vector3(); |
| 76 | + var rotationMatrix = new Matrix4(); |
| 77 | + var lookAtPosition = new Vector3( 0, 0, - 1 ); |
| 78 | + var clipPlane = new Vector4(); |
| 79 | + |
| 80 | + var view = new Vector3(); |
| 81 | + var target = new Vector3(); |
| 82 | + var q = new Vector4(); |
| 83 | + |
| 84 | + var textureMatrix = new Matrix4(); |
| 85 | + var virtualCamera = new PerspectiveCamera(); |
| 86 | + |
| 87 | + if( useDepthTexture ){ |
| 88 | + var depthTexture = new DepthTexture(); |
| 89 | + depthTexture.type = UnsignedShortType; |
| 90 | + depthTexture.minFilter = NearestFilter; |
| 91 | + depthTexture.maxFilter = NearestFilter; |
| 92 | + } |
| 93 | + |
| 94 | + var parameters = { |
| 95 | + minFilter: LinearFilter, |
| 96 | + magFilter: LinearFilter, |
| 97 | + format: RGBFormat, |
| 98 | + depthTexture: useDepthTexture ? depthTexture : null, |
| 99 | + }; |
| 100 | + |
| 101 | + var renderTarget = new WebGLRenderTarget( textureWidth, textureHeight, parameters ); |
| 102 | + |
| 103 | + if ( ! MathUtils.isPowerOfTwo( textureWidth ) || ! MathUtils.isPowerOfTwo( textureHeight ) ) { |
| 104 | + |
| 105 | + renderTarget.texture.generateMipmaps = false; |
| 106 | + |
| 107 | + } |
| 108 | + |
| 109 | + var material = new ShaderMaterial( { |
| 110 | + transparent: useDepthTexture, |
| 111 | + defines: Object.assign({ |
| 112 | + useDepthTexture: useDepthTexture |
| 113 | + }, Reflector.ReflectorShader.defines), |
| 114 | + uniforms: UniformsUtils.clone( shader.uniforms ), |
| 115 | + fragmentShader: shader.fragmentShader, |
| 116 | + vertexShader: shader.vertexShader |
| 117 | + } ); |
| 118 | + |
| 119 | + material.uniforms[ 'tDiffuse' ].value = renderTarget.texture; |
| 120 | + material.uniforms[ 'color' ].value = color; |
| 121 | + material.uniforms[ 'textureMatrix' ].value = textureMatrix; |
| 122 | + if (useDepthTexture) { |
| 123 | + material.uniforms[ 'tDepth' ].value = renderTarget.depthTexture; |
| 124 | + } |
| 125 | + |
| 126 | + this.material = material; |
| 127 | + |
| 128 | + this.doRender = function ( renderer, scene, camera ) { |
| 129 | + |
| 130 | + material.uniforms['maxDistance'].value = scope.maxDistance * (camera.position.length() / camera.position.y); |
| 131 | + ///todo: Temporary hack, |
| 132 | + // need precise calculation like this https://github.com/mrdoob/three.js/pull/20156/commits/8181946068e386d14a283cbd4f8877bc7ae066d3 , |
| 133 | + // after fully understand http://www.terathon.com/lengyel/Lengyel-Oblique.pdf . |
| 134 | + |
| 135 | + material.uniforms['opacity'].value = scope.opacity; |
| 136 | + |
| 137 | + vecTemp0.copy(camera.position).normalize(); |
| 138 | + vecTemp1.copy(vecTemp0).reflect(yAxis); |
| 139 | + material.uniforms['fresnel'].value = (vecTemp0.dot( vecTemp1 ) + 1.) / 2.; ///todo: Also need to use glsl viewPosition and viewNormal per pixel. |
| 140 | + // console.log(material.uniforms['fresnel'].value) |
| 141 | + |
| 142 | + reflectorWorldPosition.setFromMatrixPosition( scope.matrixWorld ); |
| 143 | + cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld ); |
| 144 | + |
| 145 | + rotationMatrix.extractRotation( scope.matrixWorld ); |
| 146 | + |
| 147 | + normal.set( 0, 0, 1 ); |
| 148 | + normal.applyMatrix4( rotationMatrix ); |
| 149 | + |
| 150 | + view.subVectors( reflectorWorldPosition, cameraWorldPosition ); |
| 151 | + |
| 152 | + // Avoid rendering when reflector is facing away |
| 153 | + |
| 154 | + if ( view.dot( normal ) > 0 ) return; |
| 155 | + |
| 156 | + view.reflect( normal ).negate(); |
| 157 | + view.add( reflectorWorldPosition ); |
| 158 | + |
| 159 | + rotationMatrix.extractRotation( camera.matrixWorld ); |
| 160 | + |
| 161 | + lookAtPosition.set( 0, 0, - 1 ); |
| 162 | + lookAtPosition.applyMatrix4( rotationMatrix ); |
| 163 | + lookAtPosition.add( cameraWorldPosition ); |
| 164 | + |
| 165 | + target.subVectors( reflectorWorldPosition, lookAtPosition ); |
| 166 | + target.reflect( normal ).negate(); |
| 167 | + target.add( reflectorWorldPosition ); |
| 168 | + |
| 169 | + virtualCamera.position.copy( view ); |
| 170 | + virtualCamera.up.set( 0, 1, 0 ); |
| 171 | + virtualCamera.up.applyMatrix4( rotationMatrix ); |
| 172 | + virtualCamera.up.reflect( normal ); |
| 173 | + virtualCamera.lookAt( target ); |
| 174 | + |
| 175 | + virtualCamera.far = camera.far; // Used in WebGLBackground |
| 176 | + |
| 177 | + virtualCamera.updateMatrixWorld(); |
| 178 | + virtualCamera.projectionMatrix.copy( camera.projectionMatrix ); |
| 179 | + |
| 180 | + // Update the texture matrix |
| 181 | + textureMatrix.set( |
| 182 | + 0.5, 0.0, 0.0, 0.5, |
| 183 | + 0.0, 0.5, 0.0, 0.5, |
| 184 | + 0.0, 0.0, 0.5, 0.5, |
| 185 | + 0.0, 0.0, 0.0, 1.0 |
| 186 | + ); |
| 187 | + textureMatrix.multiply( virtualCamera.projectionMatrix ); |
| 188 | + textureMatrix.multiply( virtualCamera.matrixWorldInverse ); |
| 189 | + textureMatrix.multiply( scope.matrixWorld ); |
| 190 | + |
| 191 | + // Now update projection matrix with new clip plane, implementing code from: http://www.terathon.com/code/oblique.html |
| 192 | + // Paper explaining this technique: http://www.terathon.com/lengyel/Lengyel-Oblique.pdf |
| 193 | + reflectorPlane.setFromNormalAndCoplanarPoint( normal, reflectorWorldPosition ); |
| 194 | + reflectorPlane.applyMatrix4( virtualCamera.matrixWorldInverse ); |
| 195 | + |
| 196 | + clipPlane.set( reflectorPlane.normal.x, reflectorPlane.normal.y, reflectorPlane.normal.z, reflectorPlane.constant ); |
| 197 | + |
| 198 | + var projectionMatrix = virtualCamera.projectionMatrix; |
| 199 | + |
| 200 | + q.x = ( Math.sign( clipPlane.x ) + projectionMatrix.elements[ 8 ] ) / projectionMatrix.elements[ 0 ]; |
| 201 | + q.y = ( Math.sign( clipPlane.y ) + projectionMatrix.elements[ 9 ] ) / projectionMatrix.elements[ 5 ]; |
| 202 | + q.z = - 1.0; |
| 203 | + q.w = ( 1.0 + projectionMatrix.elements[ 10 ] ) / projectionMatrix.elements[ 14 ]; |
| 204 | + |
| 205 | + // Calculate the scaled plane vector |
| 206 | + clipPlane.multiplyScalar( 2.0 / clipPlane.dot( q ) ); |
| 207 | + |
| 208 | + // Replacing the third row of the projection matrix |
| 209 | + projectionMatrix.elements[ 2 ] = clipPlane.x; |
| 210 | + projectionMatrix.elements[ 6 ] = clipPlane.y; |
| 211 | + projectionMatrix.elements[ 10 ] = clipPlane.z + 1.0 - clipBias; |
| 212 | + projectionMatrix.elements[ 14 ] = clipPlane.w; |
| 213 | + |
| 214 | + // Render |
| 215 | + |
| 216 | + renderTarget.texture.encoding = renderer.outputEncoding; |
| 217 | + |
| 218 | + // scope.visible = false; |
| 219 | + |
| 220 | + var currentRenderTarget = renderer.getRenderTarget(); |
| 221 | + |
| 222 | + var currentXrEnabled = renderer.xr.enabled; |
| 223 | + var currentShadowAutoUpdate = renderer.shadowMap.autoUpdate; |
| 224 | + |
| 225 | + renderer.xr.enabled = false; // Avoid camera modification |
| 226 | + renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows |
| 227 | + |
| 228 | + renderer.setRenderTarget( renderTarget ); |
| 229 | + |
| 230 | + renderer.state.buffers.depth.setMask( true ); // make sure the depth buffer is writable so it can be properly cleared, see #18897 |
| 231 | + |
| 232 | + if ( renderer.autoClear === false ) renderer.clear(); |
| 233 | + renderer.render( scene, virtualCamera ); |
| 234 | + |
| 235 | + renderer.xr.enabled = currentXrEnabled; |
| 236 | + renderer.shadowMap.autoUpdate = currentShadowAutoUpdate; |
| 237 | + |
| 238 | + renderer.setRenderTarget( currentRenderTarget ); |
| 239 | + |
| 240 | + // Restore viewport |
| 241 | + |
| 242 | + var viewport = camera.viewport; |
| 243 | + |
| 244 | + if ( viewport !== undefined ) { |
| 245 | + |
| 246 | + renderer.state.viewport( viewport ); |
| 247 | + |
| 248 | + } |
| 249 | + |
| 250 | + // scope.visible = true; |
| 251 | + |
| 252 | + }; |
| 253 | + |
| 254 | + this.getRenderTarget = function () { |
| 255 | + |
| 256 | + return renderTarget; |
| 257 | + |
| 258 | + }; |
| 259 | + |
| 260 | +}; |
| 261 | + |
| 262 | +Reflector.prototype = Object.create( Mesh.prototype ); |
| 263 | +Reflector.prototype.constructor = Reflector; |
| 264 | + |
| 265 | +Reflector.ReflectorShader = { ///todo: Will conflict with Reflector.js? |
| 266 | + |
| 267 | + defines: { |
| 268 | + isDistanceAttenuation: true, |
| 269 | + isFresnel: true, |
| 270 | + }, |
| 271 | + |
| 272 | + uniforms: { |
| 273 | + |
| 274 | + color: { value: null }, |
| 275 | + tDiffuse: { value: null }, |
| 276 | + tDepth: { value: null }, |
| 277 | + textureMatrix: { value: null }, |
| 278 | + maxDistance: { value: 180 }, |
| 279 | + opacity: { value: .5 }, |
| 280 | + fresnel: { value: null }, |
| 281 | + |
| 282 | + }, |
| 283 | + |
| 284 | + vertexShader: [ |
| 285 | + 'uniform mat4 textureMatrix;', |
| 286 | + 'varying vec4 vUv;', |
| 287 | + |
| 288 | + 'void main() {', |
| 289 | + |
| 290 | + ' vUv = textureMatrix * vec4( position, 1.0 );', |
| 291 | + |
| 292 | + ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', |
| 293 | + |
| 294 | + '}' |
| 295 | + ].join( '\n' ), |
| 296 | + |
| 297 | + fragmentShader: ` |
| 298 | + uniform vec3 color; |
| 299 | + uniform sampler2D tDiffuse; |
| 300 | + uniform sampler2D tDepth; |
| 301 | + uniform float maxDistance; |
| 302 | + uniform float opacity; |
| 303 | + uniform float fresnel; |
| 304 | + varying vec4 vUv; |
| 305 | + float blendOverlay( float base, float blend ) { |
| 306 | + return( base < 0.5 ? ( 2.0 * base * blend ) : ( 1.0 - 2.0 * ( 1.0 - base ) * ( 1.0 - blend ) ) ); |
| 307 | + } |
| 308 | + vec3 blendOverlay( vec3 base, vec3 blend ) { |
| 309 | + return vec3( blendOverlay( base.r, blend.r ), blendOverlay( base.g, blend.g ), blendOverlay( base.b, blend.b ) ); |
| 310 | + } |
| 311 | + void main() { |
| 312 | + vec4 base = texture2DProj( tDiffuse, vUv ); |
| 313 | + #ifdef useDepthTexture |
| 314 | + float op=opacity; |
| 315 | + float depth = texture2DProj( tDepth, vUv ).r; |
| 316 | + if(depth>maxDistance) discard; |
| 317 | + #ifdef isDistanceAttenuation |
| 318 | + float ratio=1.-(depth/maxDistance); |
| 319 | + float attenuation=ratio*ratio; |
| 320 | + op=opacity*attenuation; |
| 321 | + #endif |
| 322 | + #ifdef isFresnel |
| 323 | + op*=fresnel; |
| 324 | + #endif |
| 325 | + gl_FragColor = vec4( blendOverlay( base.rgb, color ), op ); |
| 326 | + #else |
| 327 | + gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 ); |
| 328 | + #endif |
| 329 | + } |
| 330 | + `, |
| 331 | +}; |
| 332 | + |
| 333 | +export { Reflector }; |
0 commit comments