diff --git a/src/objects/Reflector.js b/src/objects/Reflector.js new file mode 100644 index 000000000..2987f9bb2 --- /dev/null +++ b/src/objects/Reflector.js @@ -0,0 +1,262 @@ +/** + * @author Slayvin / http://slayvin.net + */ + +THREE.Reflector = function ( width, height, options ) { + + THREE.Mesh.call( this, new THREE.PlaneBufferGeometry( width, height ) ); + + 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 recursion = options.recursion !== undefined ? options.recursion : 0; + + // + + 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 ); + + 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, + + } ); + + material.uniforms.tDiffuse.value = renderTarget.texture; + material.uniforms.color.value = color; + material.uniforms.textureMatrix.value = textureMatrix; + + this.material = material; + + this.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.far = camera.far; // Used in WebGLBackground + + 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; + + }; + + 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 + }, + + 'textureMatrix': { + type: 'm4', + value: null + } + + }, + + 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: [ + 'uniform vec3 color;', + 'uniform sampler2D tDiffuse;', + '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 ) );', + + '}', + + 'void main() {', + + ' vec4 base = texture2DProj( tDiffuse, vUv );', + ' gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 );', + + '}' + ].join( '\n' ) +}; diff --git a/src/objects/ReflectorRTT.js b/src/objects/ReflectorRTT.js new file mode 100644 index 000000000..0383f2a75 --- /dev/null +++ b/src/objects/ReflectorRTT.js @@ -0,0 +1,9 @@ +THREE.ReflectorRTT = function ( width, height, options ) { + + THREE.Reflector.call( this, width, height, options ); + + this.geometry.setDrawRange( 0, 0 ); // avoid rendering geometry + +}; + +THREE.ReflectorRTT.prototype = Object.create( THREE.Reflector.prototype ); diff --git a/src/objects/Refractor.js b/src/objects/Refractor.js new file mode 100644 index 000000000..1fda41b30 --- /dev/null +++ b/src/objects/Refractor.js @@ -0,0 +1,332 @@ +/** + * @author Mugen87 / https://github.com/Mugen87 + * + */ + +THREE.Refractor = function ( width, height, options ) { + + THREE.Mesh.call( this, new THREE.PlaneBufferGeometry( width, height ) ); + + this.type = 'Refractor'; + + 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.Refractor.RefractorShader; + + // + + var virtualCamera = new THREE.PerspectiveCamera(); + virtualCamera.matrixAutoUpdate = false; + virtualCamera.userData.refractor = true; + + // + + var refractorPlane = new THREE.Plane(); + var textureMatrix = new THREE.Matrix4(); + + // render target + + var parameters = { + minFilter: THREE.LinearFilter, + magFilter: THREE.LinearFilter, + format: THREE.RGBFormat, + stencilBuffer: false + }; + + var renderTarget = new THREE.WebGLRenderTarget( textureWidth, textureHeight, parameters ); + + if ( ! THREE.Math.isPowerOfTwo( textureWidth ) || ! THREE.Math.isPowerOfTwo( textureHeight ) ) { + + renderTarget.texture.generateMipmaps = false; + + } + + // material + + this.material = new THREE.ShaderMaterial( { + uniforms: THREE.UniformsUtils.clone( shader.uniforms ), + vertexShader: shader.vertexShader, + fragmentShader: shader.fragmentShader, + transparent: true // ensures, refractors are drawn from farthest to closest + } ); + + this.material.uniforms.color.value = color; + this.material.uniforms.tDiffuse.value = renderTarget.texture; + this.material.uniforms.textureMatrix.value = textureMatrix; + + // functions + + var visible = ( function () { + + var refractorWorldPosition = new THREE.Vector3(); + var cameraWorldPosition = new THREE.Vector3(); + var rotationMatrix = new THREE.Matrix4(); + + var view = new THREE.Vector3(); + var normal = new THREE.Vector3(); + + return function visible( camera ) { + + refractorWorldPosition.setFromMatrixPosition( scope.matrixWorld ); + cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld ); + + view.subVectors( refractorWorldPosition, cameraWorldPosition ); + + rotationMatrix.extractRotation( scope.matrixWorld ); + + normal.set( 0, 0, 1 ); + normal.applyMatrix4( rotationMatrix ); + + return view.dot( normal ) < 0; + + }; + + } )(); + + var updateRefractorPlane = ( function () { + + var normal = new THREE.Vector3(); + var position = new THREE.Vector3(); + var quaternion = new THREE.Quaternion(); + var scale = new THREE.Vector3(); + + return function updateRefractorPlane() { + + scope.matrixWorld.decompose( position, quaternion, scale ); + normal.set( 0, 0, 1 ).applyQuaternion( quaternion ).normalize(); + + // flip the normal because we want to cull everything above the plane + + normal.negate(); + + refractorPlane.setFromNormalAndCoplanarPoint( normal, position ); + + }; + + } )(); + + var updateVirtualCamera = ( function () { + + var clipPlane = new THREE.Plane(); + var clipVector = new THREE.Vector4(); + var q = new THREE.Vector4(); + + return function updateVirtualCamera( camera ) { + + virtualCamera.matrixWorld.copy( camera.matrixWorld ); + virtualCamera.matrixWorldInverse.getInverse( virtualCamera.matrixWorld ); + virtualCamera.projectionMatrix.copy( camera.projectionMatrix ); + virtualCamera.far = camera.far; // used in WebGLBackground + + // The following code creates an oblique view frustum for clipping. + // see: Lengyel, Eric. “Oblique View Frustum Depth Projection and Clipping”. + // Journal of Game Development, Vol. 1, No. 2 (2005), Charles River Media, pp. 5–16 + + clipPlane.copy( refractorPlane ); + clipPlane.applyMatrix4( virtualCamera.matrixWorldInverse ); + + clipVector.set( clipPlane.normal.x, clipPlane.normal.y, clipPlane.normal.z, clipPlane.constant ); + + // calculate the clip-space corner point opposite the clipping plane and + // transform it into camera space by multiplying it by the inverse of the projection matrix + + var projectionMatrix = virtualCamera.projectionMatrix; + + q.x = ( Math.sign( clipVector.x ) + projectionMatrix.elements[ 8 ] ) / projectionMatrix.elements[ 0 ]; + q.y = ( Math.sign( clipVector.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 + + clipVector.multiplyScalar( 2.0 / clipVector.dot( q ) ); + + // replacing the third row of the projection matrix + + projectionMatrix.elements[ 2 ] = clipVector.x; + projectionMatrix.elements[ 6 ] = clipVector.y; + projectionMatrix.elements[ 10 ] = clipVector.z + 1.0 - clipBias; + projectionMatrix.elements[ 14 ] = clipVector.w; + + }; + + } )(); + + // This will update the texture matrix that is used for projective texture mapping in the shader. + // see: http://developer.download.nvidia.com/assets/gamedev/docs/projective_texture_mapping.pdf + + function updateTextureMatrix( camera ) { + + // this matrix does range mapping to [ 0, 1 ] + + 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 + ); + + // we use "Object Linear Texgen", so we need to multiply the texture matrix T + // (matrix above) with the projection and view matrix of the virtual camera + // and the model matrix of the refractor + + textureMatrix.multiply( camera.projectionMatrix ); + textureMatrix.multiply( camera.matrixWorldInverse ); + textureMatrix.multiply( scope.matrixWorld ); + + } + + // + + var render = ( function () { + + var viewport = new THREE.Vector4(); + + return function render( renderer, scene, camera ) { + + scope.visible = false; + + var currentRenderTarget = renderer.getRenderTarget(); + var currentVrEnabled = renderer.vr.enabled; + var currentShadowAutoUpdate = renderer.shadowMap.autoUpdate; + + renderer.vr.enabled = false; // avoid camera modification + 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; + + }; + + } )(); + + // + + this.onBeforeRender = function ( renderer, scene, camera ) { + + // ensure refractors are rendered only once per frame + + if ( camera.userData.refractor === true ) return; + + // avoid rendering when the refractor is viewed from behind + + if ( ! visible( camera ) === true ) return; + + // update + + updateRefractorPlane(); + + updateTextureMatrix( camera ); + + updateVirtualCamera( camera ); + + render( renderer, scene, camera ); + + }; + + this.getRenderTarget = function () { + + return renderTarget; + + }; + +}; + +THREE.Refractor.prototype = Object.create( THREE.Mesh.prototype ); +THREE.Refractor.prototype.constructor = THREE.Refractor; + +THREE.Refractor.RefractorShader = { + + uniforms: { + + 'color': { + type: 'c', + value: null + }, + + 'tDiffuse': { + type: 't', + value: null + }, + + 'textureMatrix': { + type: 'm4', + value: null + } + + }, + + 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: [ + + 'uniform vec3 color;', + 'uniform sampler2D tDiffuse;', + + '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 ) );', + + '}', + + 'void main() {', + + ' vec4 base = texture2DProj( tDiffuse, vUv );', + + ' gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 );', + + '}' + + ].join( '\n' ) +}; diff --git a/src/objects/ShadowMesh.js b/src/objects/ShadowMesh.js new file mode 100644 index 000000000..fa3122e0c --- /dev/null +++ b/src/objects/ShadowMesh.js @@ -0,0 +1,69 @@ +/** + * @author erichlof / http://github.com/erichlof + * + * A shadow Mesh that follows a shadow-casting Mesh in the scene, but is confined to a single plane. + */ + +THREE.ShadowMesh = function ( mesh ) { + + var shadowMaterial = new THREE.MeshBasicMaterial( { + + color: 0x000000, + transparent: true, + opacity: 0.6, + depthWrite: false + + } ); + + THREE.Mesh.call( this, mesh.geometry, shadowMaterial ); + + this.meshMatrix = mesh.matrixWorld; + + this.frustumCulled = false; + this.matrixAutoUpdate = false; + +}; + +THREE.ShadowMesh.prototype = Object.create( THREE.Mesh.prototype ); +THREE.ShadowMesh.prototype.constructor = THREE.ShadowMesh; + +THREE.ShadowMesh.prototype.update = function () { + + var shadowMatrix = new THREE.Matrix4(); + + return function ( plane, lightPosition4D ) { + + // based on https://www.opengl.org/archives/resources/features/StencilTalk/tsld021.htm + + var dot = plane.normal.x * lightPosition4D.x + + plane.normal.y * lightPosition4D.y + + plane.normal.z * lightPosition4D.z + + - plane.constant * lightPosition4D.w; + + var sme = shadowMatrix.elements; + + sme[ 0 ] = dot - lightPosition4D.x * plane.normal.x; + sme[ 4 ] = - lightPosition4D.x * plane.normal.y; + sme[ 8 ] = - lightPosition4D.x * plane.normal.z; + sme[ 12 ] = - lightPosition4D.x * - plane.constant; + + sme[ 1 ] = - lightPosition4D.y * plane.normal.x; + sme[ 5 ] = dot - lightPosition4D.y * plane.normal.y; + sme[ 9 ] = - lightPosition4D.y * plane.normal.z; + sme[ 13 ] = - lightPosition4D.y * - plane.constant; + + sme[ 2 ] = - lightPosition4D.z * plane.normal.x; + sme[ 6 ] = - lightPosition4D.z * plane.normal.y; + sme[ 10 ] = dot - lightPosition4D.z * plane.normal.z; + sme[ 14 ] = - lightPosition4D.z * - plane.constant; + + sme[ 3 ] = - lightPosition4D.w * plane.normal.x; + sme[ 7 ] = - lightPosition4D.w * plane.normal.y; + sme[ 11 ] = - lightPosition4D.w * plane.normal.z; + sme[ 15 ] = dot - lightPosition4D.w * - plane.constant; + + this.matrix.multiplyMatrices( shadowMatrix, this.meshMatrix ); + + }; + +}(); diff --git a/src/objects/Sky.js b/src/objects/Sky.js new file mode 100644 index 000000000..79acde098 --- /dev/null +++ b/src/objects/Sky.js @@ -0,0 +1,221 @@ +/** + * @author zz85 / https://github.com/zz85 + * + * Based on "A Practical Analytic Model for Daylight" + * aka The Preetham Model, the de facto standard analytic skydome model + * http://www.cs.utah.edu/~shirley/papers/sunsky/sunsky.pdf + * + * First implemented by Simon Wallner + * http://www.simonwallner.at/projects/atmospheric-scattering + * + * Improved by Martin Upitis + * http://blenderartists.org/forum/showthread.php?245954-preethams-sky-impementation-HDR + * + * Three.js integration by zz85 http://twitter.com/blurspline +*/ + +THREE.Sky = function () { + + var shader = THREE.Sky.SkyShader; + + var material = new THREE.ShaderMaterial( { + fragmentShader: shader.fragmentShader, + vertexShader: shader.vertexShader, + uniforms: THREE.UniformsUtils.clone( shader.uniforms ), + side: THREE.BackSide + } ); + + THREE.Mesh.call( this, new THREE.SphereBufferGeometry( 1, 32, 15 ), material ); + +}; + +THREE.Sky.prototype = Object.create( THREE.Mesh.prototype ); + +THREE.Sky.SkyShader = { + + uniforms: { + luminance: { value: 1 }, + turbidity: { value: 2 }, + rayleigh: { value: 1 }, + mieCoefficient: { value: 0.005 }, + mieDirectionalG: { value: 0.8 }, + sunPosition: { value: new THREE.Vector3() } + }, + + vertexShader: [ + 'uniform vec3 sunPosition;', + 'uniform float rayleigh;', + 'uniform float turbidity;', + 'uniform float mieCoefficient;', + + 'varying vec3 vWorldPosition;', + 'varying vec3 vSunDirection;', + 'varying float vSunfade;', + 'varying vec3 vBetaR;', + 'varying vec3 vBetaM;', + 'varying float vSunE;', + + 'const vec3 up = vec3( 0.0, 1.0, 0.0 );', + + // constants for atmospheric scattering + 'const float e = 2.71828182845904523536028747135266249775724709369995957;', + 'const float pi = 3.141592653589793238462643383279502884197169;', + + // wavelength of used primaries, according to preetham + 'const vec3 lambda = vec3( 680E-9, 550E-9, 450E-9 );', + // this pre-calcuation replaces older TotalRayleigh(vec3 lambda) function: + // (8.0 * pow(pi, 3.0) * pow(pow(n, 2.0) - 1.0, 2.0) * (6.0 + 3.0 * pn)) / (3.0 * N * pow(lambda, vec3(4.0)) * (6.0 - 7.0 * pn)) + 'const vec3 totalRayleigh = vec3( 5.804542996261093E-6, 1.3562911419845635E-5, 3.0265902468824876E-5 );', + + // mie stuff + // K coefficient for the primaries + 'const float v = 4.0;', + 'const vec3 K = vec3( 0.686, 0.678, 0.666 );', + // MieConst = pi * pow( ( 2.0 * pi ) / lambda, vec3( v - 2.0 ) ) * K + 'const vec3 MieConst = vec3( 1.8399918514433978E14, 2.7798023919660528E14, 4.0790479543861094E14 );', + + // earth shadow hack + // cutoffAngle = pi / 1.95; + 'const float cutoffAngle = 1.6110731556870734;', + 'const float steepness = 1.5;', + 'const float EE = 1000.0;', + + 'float sunIntensity( float zenithAngleCos ) {', + ' zenithAngleCos = clamp( zenithAngleCos, -1.0, 1.0 );', + ' return EE * max( 0.0, 1.0 - pow( e, -( ( cutoffAngle - acos( zenithAngleCos ) ) / steepness ) ) );', + '}', + + 'vec3 totalMie( float T ) {', + ' float c = ( 0.2 * T ) * 10E-18;', + ' return 0.434 * c * MieConst;', + '}', + + 'void main() {', + + ' vec4 worldPosition = modelMatrix * vec4( position, 1.0 );', + ' vWorldPosition = worldPosition.xyz;', + + ' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', + + ' vSunDirection = normalize( sunPosition );', + + ' vSunE = sunIntensity( dot( vSunDirection, up ) );', + + ' vSunfade = 1.0 - clamp( 1.0 - exp( ( sunPosition.y / 450000.0 ) ), 0.0, 1.0 );', + + ' float rayleighCoefficient = rayleigh - ( 1.0 * ( 1.0 - vSunfade ) );', + + // extinction (absorbtion + out scattering) + // rayleigh coefficients + ' vBetaR = totalRayleigh * rayleighCoefficient;', + + // mie coefficients + ' vBetaM = totalMie( turbidity ) * mieCoefficient;', + + '}' + ].join( '\n' ), + + fragmentShader: [ + 'varying vec3 vWorldPosition;', + 'varying vec3 vSunDirection;', + 'varying float vSunfade;', + 'varying vec3 vBetaR;', + 'varying vec3 vBetaM;', + 'varying float vSunE;', + + 'uniform float luminance;', + 'uniform float mieDirectionalG;', + + 'const vec3 cameraPos = vec3( 0.0, 0.0, 0.0 );', + + // constants for atmospheric scattering + 'const float pi = 3.141592653589793238462643383279502884197169;', + + 'const float n = 1.0003;', // refractive index of air + 'const float N = 2.545E25;', // number of molecules per unit volume for air at + // 288.15K and 1013mb (sea level -45 celsius) + + // optical length at zenith for molecules + 'const float rayleighZenithLength = 8.4E3;', + 'const float mieZenithLength = 1.25E3;', + 'const vec3 up = vec3( 0.0, 1.0, 0.0 );', + // 66 arc seconds -> degrees, and the cosine of that + 'const float sunAngularDiameterCos = 0.999956676946448443553574619906976478926848692873900859324;', + + // 3.0 / ( 16.0 * pi ) + 'const float THREE_OVER_SIXTEENPI = 0.05968310365946075;', + // 1.0 / ( 4.0 * pi ) + 'const float ONE_OVER_FOURPI = 0.07957747154594767;', + + 'float rayleighPhase( float cosTheta ) {', + ' return THREE_OVER_SIXTEENPI * ( 1.0 + pow( cosTheta, 2.0 ) );', + '}', + + 'float hgPhase( float cosTheta, float g ) {', + ' float g2 = pow( g, 2.0 );', + ' float inverse = 1.0 / pow( 1.0 - 2.0 * g * cosTheta + g2, 1.5 );', + ' return ONE_OVER_FOURPI * ( ( 1.0 - g2 ) * inverse );', + '}', + + // Filmic ToneMapping http://filmicgames.com/archives/75 + 'const float A = 0.15;', + 'const float B = 0.50;', + 'const float C = 0.10;', + 'const float D = 0.20;', + 'const float E = 0.02;', + 'const float F = 0.30;', + + 'const float whiteScale = 1.0748724675633854;', // 1.0 / Uncharted2Tonemap(1000.0) + + 'vec3 Uncharted2Tonemap( vec3 x ) {', + ' return ( ( x * ( A * x + C * B ) + D * E ) / ( x * ( A * x + B ) + D * F ) ) - E / F;', + '}', + + + 'void main() {', + // optical length + // cutoff angle at 90 to avoid singularity in next formula. + ' float zenithAngle = acos( max( 0.0, dot( up, normalize( vWorldPosition - cameraPos ) ) ) );', + ' float inverse = 1.0 / ( cos( zenithAngle ) + 0.15 * pow( 93.885 - ( ( zenithAngle * 180.0 ) / pi ), -1.253 ) );', + ' float sR = rayleighZenithLength * inverse;', + ' float sM = mieZenithLength * inverse;', + + // combined extinction factor + ' vec3 Fex = exp( -( vBetaR * sR + vBetaM * sM ) );', + + // in scattering + ' float cosTheta = dot( normalize( vWorldPosition - cameraPos ), vSunDirection );', + + ' float rPhase = rayleighPhase( cosTheta * 0.5 + 0.5 );', + ' vec3 betaRTheta = vBetaR * rPhase;', + + ' float mPhase = hgPhase( cosTheta, mieDirectionalG );', + ' vec3 betaMTheta = vBetaM * mPhase;', + + ' vec3 Lin = pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * ( 1.0 - Fex ), vec3( 1.5 ) );', + ' Lin *= mix( vec3( 1.0 ), pow( vSunE * ( ( betaRTheta + betaMTheta ) / ( vBetaR + vBetaM ) ) * Fex, vec3( 1.0 / 2.0 ) ), clamp( pow( 1.0 - dot( up, vSunDirection ), 5.0 ), 0.0, 1.0 ) );', + + // nightsky + ' vec3 direction = normalize( vWorldPosition - cameraPos );', + ' float theta = acos( direction.y ); // elevation --> y-axis, [-pi/2, pi/2]', + ' float phi = atan( direction.z, direction.x ); // azimuth --> x-axis [-pi/2, pi/2]', + ' vec2 uv = vec2( phi, theta ) / vec2( 2.0 * pi, pi ) + vec2( 0.5, 0.0 );', + ' vec3 L0 = vec3( 0.1 ) * Fex;', + + // composition + solar disc + ' float sundisk = smoothstep( sunAngularDiameterCos, sunAngularDiameterCos + 0.00002, cosTheta );', + ' L0 += ( vSunE * 19000.0 * Fex ) * sundisk;', + + ' vec3 texColor = ( Lin + L0 ) * 0.04 + vec3( 0.0, 0.0003, 0.00075 );', + + ' vec3 curr = Uncharted2Tonemap( ( log2( 2.0 / pow( luminance, 4.0 ) ) ) * texColor );', + ' vec3 color = curr * whiteScale;', + + ' vec3 retColor = pow( color, vec3( 1.0 / ( 1.2 + ( 1.2 * vSunfade ) ) ) );', + + ' gl_FragColor = vec4( retColor, 1.0 );', + + '}' + ].join( '\n' ) + +}; diff --git a/src/objects/Water.js b/src/objects/Water.js new file mode 100644 index 000000000..c65f48111 --- /dev/null +++ b/src/objects/Water.js @@ -0,0 +1,309 @@ +/** + * @author jbouny / https://github.com/jbouny + * + * Work based on : + * @author Slayvin / http://slayvin.net : Flat mirror for three.js + * @author Stemkoski / http://www.adelphi.edu/~stemkoski : An implementation of water shader based on the flat mirror + * @author Jonas Wagner / http://29a.ch/ && http://29a.ch/slides/2012/webglwater/ : Water shader explanations in WebGL + */ + +THREE.Water = function ( width, height, options ) { + + THREE.Mesh.call( this, new THREE.PlaneBufferGeometry( width, height ) ); + + var scope = this; + + options = options || {}; + + var textureWidth = options.textureWidth !== undefined ? options.textureWidth : 512; + var textureHeight = options.textureHeight !== undefined ? options.textureHeight : 512; + + var clipBias = options.clipBias !== undefined ? options.clipBias : 0.0; + var alpha = options.alpha !== undefined ? options.alpha : 1.0; + var time = options.time !== undefined ? options.time : 0.0; + var normalSampler = options.waterNormals !== undefined ? options.waterNormals : null; + var sunDirection = options.sunDirection !== undefined ? options.sunDirection : new THREE.Vector3( 0.70707, 0.70707, 0.0 ); + var sunColor = new THREE.Color( options.sunColor !== undefined ? options.sunColor : 0xffffff ); + var waterColor = new THREE.Color( options.waterColor !== undefined ? options.waterColor : 0x7F7F7F ); + var eye = options.eye !== undefined ? options.eye : new THREE.Vector3( 0, 0, 0 ); + var distortionScale = options.distortionScale !== undefined ? options.distortionScale : 20.0; + var side = options.side !== undefined ? options.side : THREE.FrontSide; + var fog = options.fog !== undefined ? options.fog : false; + + // + + var mirrorPlane = new THREE.Plane(); + var normal = new THREE.Vector3(); + var mirrorWorldPosition = 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 view = new THREE.Vector3(); + var target = new THREE.Vector3(); + var q = new THREE.Vector4(); + + var textureMatrix = new THREE.Matrix4(); + + var mirrorCamera = new THREE.PerspectiveCamera(); + + var parameters = { + minFilter: THREE.LinearFilter, + magFilter: THREE.LinearFilter, + format: THREE.RGBFormat, + stencilBuffer: false + }; + + var renderTarget = new THREE.WebGLRenderTarget( textureWidth, textureHeight, parameters ); + + if ( ! THREE.Math.isPowerOfTwo( textureWidth ) || ! THREE.Math.isPowerOfTwo( textureHeight ) ) { + + renderTarget.texture.generateMipmaps = false; + + } + + var mirrorShader = { + + uniforms: THREE.UniformsUtils.merge( [ + THREE.UniformsLib[ 'fog' ], + THREE.UniformsLib[ 'lights' ], + { + normalSampler: { value: null }, + mirrorSampler: { value: null }, + alpha: { value: 1.0 }, + time: { value: 0.0 }, + size: { value: 1.0 }, + distortionScale: { value: 20.0 }, + noiseScale: { value: 1.0 }, + textureMatrix: { value: new THREE.Matrix4() }, + sunColor: { value: new THREE.Color( 0x7F7F7F ) }, + sunDirection: { value: new THREE.Vector3( 0.70707, 0.70707, 0 ) }, + eye: { value: new THREE.Vector3() }, + waterColor: { value: new THREE.Color( 0x555555 ) } + } + ] ), + + vertexShader: [ + 'uniform mat4 textureMatrix;', + 'uniform float time;', + + 'varying vec4 mirrorCoord;', + 'varying vec4 worldPosition;', + + THREE.ShaderChunk[ 'fog_pars_vertex' ], + THREE.ShaderChunk[ 'shadowmap_pars_vertex' ], + + 'void main() {', + ' mirrorCoord = modelMatrix * vec4( position, 1.0 );', + ' worldPosition = mirrorCoord.xyzw;', + ' mirrorCoord = textureMatrix * mirrorCoord;', + ' vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );', + ' gl_Position = projectionMatrix * mvPosition;', + + THREE.ShaderChunk[ 'fog_vertex' ], + THREE.ShaderChunk[ 'shadowmap_vertex' ], + + '}' + ].join( '\n' ), + + fragmentShader: [ + 'uniform sampler2D mirrorSampler;', + 'uniform float alpha;', + 'uniform float time;', + 'uniform float size;', + 'uniform float distortionScale;', + 'uniform sampler2D normalSampler;', + 'uniform vec3 sunColor;', + 'uniform vec3 sunDirection;', + 'uniform vec3 eye;', + 'uniform vec3 waterColor;', + + 'varying vec4 mirrorCoord;', + 'varying vec4 worldPosition;', + + 'vec4 getNoise( vec2 uv ) {', + ' vec2 uv0 = ( uv / 103.0 ) + vec2(time / 17.0, time / 29.0);', + ' vec2 uv1 = uv / 107.0-vec2( time / -19.0, time / 31.0 );', + ' vec2 uv2 = uv / vec2( 8907.0, 9803.0 ) + vec2( time / 101.0, time / 97.0 );', + ' vec2 uv3 = uv / vec2( 1091.0, 1027.0 ) - vec2( time / 109.0, time / -113.0 );', + ' vec4 noise = texture2D( normalSampler, uv0 ) +', + ' texture2D( normalSampler, uv1 ) +', + ' texture2D( normalSampler, uv2 ) +', + ' texture2D( normalSampler, uv3 );', + ' return noise * 0.5 - 1.0;', + '}', + + 'void sunLight( const vec3 surfaceNormal, const vec3 eyeDirection, float shiny, float spec, float diffuse, inout vec3 diffuseColor, inout vec3 specularColor ) {', + ' vec3 reflection = normalize( reflect( -sunDirection, surfaceNormal ) );', + ' float direction = max( 0.0, dot( eyeDirection, reflection ) );', + ' specularColor += pow( direction, shiny ) * sunColor * spec;', + ' diffuseColor += max( dot( sunDirection, surfaceNormal ), 0.0 ) * sunColor * diffuse;', + '}', + + THREE.ShaderChunk[ 'common' ], + THREE.ShaderChunk[ 'packing' ], + THREE.ShaderChunk[ 'bsdfs' ], + THREE.ShaderChunk[ 'fog_pars_fragment' ], + THREE.ShaderChunk[ 'lights_pars' ], + THREE.ShaderChunk[ 'shadowmap_pars_fragment' ], + THREE.ShaderChunk[ 'shadowmask_pars_fragment' ], + + 'void main() {', + ' vec4 noise = getNoise( worldPosition.xz * size );', + ' vec3 surfaceNormal = normalize( noise.xzy * vec3( 1.5, 1.0, 1.5 ) );', + + ' vec3 diffuseLight = vec3(0.0);', + ' vec3 specularLight = vec3(0.0);', + + ' vec3 worldToEye = eye-worldPosition.xyz;', + ' vec3 eyeDirection = normalize( worldToEye );', + ' sunLight( surfaceNormal, eyeDirection, 100.0, 2.0, 0.5, diffuseLight, specularLight );', + + ' float distance = length(worldToEye);', + + ' vec2 distortion = surfaceNormal.xz * ( 0.001 + 1.0 / distance ) * distortionScale;', + ' vec3 reflectionSample = vec3( texture2D( mirrorSampler, mirrorCoord.xy / mirrorCoord.z + distortion ) );', + + ' float theta = max( dot( eyeDirection, surfaceNormal ), 0.0 );', + ' float rf0 = 0.3;', + ' float reflectance = rf0 + ( 1.0 - rf0 ) * pow( ( 1.0 - theta ), 5.0 );', + ' vec3 scatter = max( 0.0, dot( surfaceNormal, eyeDirection ) ) * waterColor;', + ' vec3 albedo = mix( ( sunColor * diffuseLight * 0.3 + scatter ) * getShadowMask(), ( vec3( 0.1 ) + reflectionSample * 0.9 + reflectionSample * specularLight ), reflectance);', + ' vec3 outgoingLight = albedo;', + ' gl_FragColor = vec4( outgoingLight, alpha );', + + THREE.ShaderChunk[ 'tonemapping_fragment' ], + THREE.ShaderChunk[ 'fog_fragment' ], + + '}' + ].join( '\n' ) + + }; + + var material = new THREE.ShaderMaterial( { + fragmentShader: mirrorShader.fragmentShader, + vertexShader: mirrorShader.vertexShader, + uniforms: THREE.UniformsUtils.clone( mirrorShader.uniforms ), + transparent: true, + lights: true, + side: side, + fog: fog + } ); + + material.uniforms.mirrorSampler.value = renderTarget.texture; + material.uniforms.textureMatrix.value = textureMatrix; + material.uniforms.alpha.value = alpha; + material.uniforms.time.value = time; + material.uniforms.normalSampler.value = normalSampler; + material.uniforms.sunColor.value = sunColor; + material.uniforms.waterColor.value = waterColor; + material.uniforms.sunDirection.value = sunDirection; + material.uniforms.distortionScale.value = distortionScale; + + material.uniforms.eye.value = eye; + + scope.material = material; + + scope.onBeforeRender = function ( renderer, scene, camera ) { + + mirrorWorldPosition.setFromMatrixPosition( scope.matrixWorld ); + cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld ); + + rotationMatrix.extractRotation( scope.matrixWorld ); + + normal.set( 0, 0, 1 ); + normal.applyMatrix4( rotationMatrix ); + + view.subVectors( mirrorWorldPosition, cameraWorldPosition ); + + // Avoid rendering when mirror is facing away + + if ( view.dot( normal ) > 0 ) return; + + view.reflect( normal ).negate(); + view.add( mirrorWorldPosition ); + + rotationMatrix.extractRotation( camera.matrixWorld ); + + lookAtPosition.set( 0, 0, - 1 ); + lookAtPosition.applyMatrix4( rotationMatrix ); + lookAtPosition.add( cameraWorldPosition ); + + target.subVectors( mirrorWorldPosition, lookAtPosition ); + target.reflect( normal ).negate(); + target.add( mirrorWorldPosition ); + + mirrorCamera.position.copy( view ); + mirrorCamera.up.set( 0, 1, 0 ); + mirrorCamera.up.applyMatrix4( rotationMatrix ); + mirrorCamera.up.reflect( normal ); + mirrorCamera.lookAt( target ); + + mirrorCamera.far = camera.far; // Used in WebGLBackground + + mirrorCamera.updateMatrixWorld(); + mirrorCamera.projectionMatrix.copy( camera.projectionMatrix ); + + // 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( mirrorCamera.projectionMatrix ); + textureMatrix.multiply( mirrorCamera.matrixWorldInverse ); + + // 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 + mirrorPlane.setFromNormalAndCoplanarPoint( normal, mirrorWorldPosition ); + mirrorPlane.applyMatrix4( mirrorCamera.matrixWorldInverse ); + + clipPlane.set( mirrorPlane.normal.x, mirrorPlane.normal.y, mirrorPlane.normal.z, mirrorPlane.constant ); + + var projectionMatrix = mirrorCamera.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; + + eye.setFromMatrixPosition( camera.matrixWorld ); + + // + + var currentRenderTarget = renderer.getRenderTarget(); + + var currentVrEnabled = renderer.vr.enabled; + var currentShadowAutoUpdate = renderer.shadowMap.autoUpdate; + + scope.visible = false; + + renderer.vr.enabled = false; // Avoid camera modification and recursion + renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows + + renderer.render( scene, mirrorCamera, renderTarget, true ); + + scope.visible = true; + + renderer.vr.enabled = currentVrEnabled; + renderer.shadowMap.autoUpdate = currentShadowAutoUpdate; + + renderer.setRenderTarget( currentRenderTarget ); + + }; + +}; + +THREE.Water.prototype = Object.create( THREE.Mesh.prototype ); +THREE.Water.prototype.constructor = THREE.Water; diff --git a/src/objects/Water2.js b/src/objects/Water2.js new file mode 100644 index 000000000..841d0e580 --- /dev/null +++ b/src/objects/Water2.js @@ -0,0 +1,337 @@ +/** + * @author Mugen87 / https://github.com/Mugen87 + * + * References: + * http://www.valvesoftware.com/publications/2010/siggraph2010_vlachos_waterflow.pdf + * http://graphicsrunner.blogspot.de/2010/08/water-using-flow-maps.html + * + */ + +THREE.Water = function ( width, height, options ) { + + THREE.Mesh.call( this, new THREE.PlaneBufferGeometry( width, height ) ); + + this.type = 'Water'; + + var scope = this; + + options = options || {}; + + var color = ( options.color !== undefined ) ? new THREE.Color( options.color ) : new THREE.Color( 0xFFFFFF ); + var textureWidth = options.textureWidth || 512; + var textureHeight = options.textureHeight || 512; + var clipBias = options.clipBias || 0; + var flowDirection = options.flowDirection || new THREE.Vector2( 1, 0 ); + var flowSpeed = options.flowSpeed || 0.03; + var reflectivity = options.reflectivity || 0.02; + var scale = options.scale || 1; + var shader = options.shader || THREE.Water.WaterShader; + + var textureLoader = new THREE.TextureLoader(); + + var flowMap = options.flowMap || undefined; + var normalMap0 = options.normalMap0 || textureLoader.load( 'textures/water/Water_1_M_Normal.jpg' ); + var normalMap1 = options.normalMap1 || textureLoader.load( 'textures/water/Water_2_M_Normal.jpg' ); + + var cycle = 0.15; // a cycle of a flow map phase + var halfCycle = cycle * 0.5; + var textureMatrix = new THREE.Matrix4(); + var clock = new THREE.Clock(); + + // internal components + + if ( THREE.Reflector === undefined ) { + + console.error( 'THREE.Water: Required component THREE.Reflector not found.' ); + return; + + } + + if ( THREE.Refractor === undefined ) { + + console.error( 'THREE.Water: Required component THREE.Refractor not found.' ); + return; + + } + + var reflector = new THREE.Reflector( width, height, { + textureWidth: textureWidth, + textureHeight: textureHeight, + clipBias: clipBias + } ); + + var refractor = new THREE.Refractor( width, height, { + textureWidth: textureWidth, + textureHeight: textureHeight, + clipBias: clipBias + } ); + + reflector.matrixAutoUpdate = false; + refractor.matrixAutoUpdate = false; + + // material + + this.material = new THREE.ShaderMaterial( { + uniforms: THREE.UniformsUtils.merge( [ + THREE.UniformsLib[ 'fog' ], + shader.uniforms + ] ), + vertexShader: shader.vertexShader, + fragmentShader: shader.fragmentShader, + transparent: true, + fog: true + } ); + + if ( flowMap !== undefined ) { + + this.material.defines.USE_FLOWMAP = ''; + this.material.uniforms.tFlowMap = { + type: 't', + value: flowMap + }; + + } else { + + this.material.uniforms.flowDirection = { + type: 'v2', + value: flowDirection + }; + + } + + // maps + + normalMap0.wrapS = normalMap0.wrapT = THREE.RepeatWrapping; + normalMap1.wrapS = normalMap1.wrapT = THREE.RepeatWrapping; + + this.material.uniforms.tReflectionMap.value = reflector.getRenderTarget().texture; + this.material.uniforms.tRefractionMap.value = refractor.getRenderTarget().texture; + this.material.uniforms.tNormalMap0.value = normalMap0; + this.material.uniforms.tNormalMap1.value = normalMap1; + + // water + + this.material.uniforms.color.value = color; + this.material.uniforms.reflectivity.value = reflectivity; + this.material.uniforms.textureMatrix.value = textureMatrix; + + // inital values + + this.material.uniforms.config.value.x = 0; // flowMapOffset0 + this.material.uniforms.config.value.y = halfCycle; // flowMapOffset1 + this.material.uniforms.config.value.z = halfCycle; // halfCycle + this.material.uniforms.config.value.w = scale; // scale + + // functions + + function updateTextureMatrix( camera ) { + + 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( camera.projectionMatrix ); + textureMatrix.multiply( camera.matrixWorldInverse ); + textureMatrix.multiply( scope.matrixWorld ); + + } + + function updateFlow() { + + var delta = clock.getDelta(); + var config = scope.material.uniforms.config; + + config.value.x += flowSpeed * delta; // flowMapOffset0 + config.value.y = config.value.x + halfCycle; // flowMapOffset1 + + // Important: The distance between offsets should be always the value of "halfCycle". + // Moreover, both offsets should be in the range of [ 0, cycle ]. + // This approach ensures a smooth water flow and avoids "reset" effects. + + if ( config.value.x >= cycle ) { + + config.value.x = 0; + config.value.y = halfCycle; + + } else if ( config.value.y >= cycle ) { + + config.value.y = config.value.y - cycle; + + } + + } + + // + + this.onBeforeRender = function ( renderer, scene, camera ) { + + updateTextureMatrix( camera ); + updateFlow(); + + scope.visible = false; + + reflector.matrixWorld.copy( scope.matrixWorld ); + refractor.matrixWorld.copy( scope.matrixWorld ); + + reflector.onBeforeRender( renderer, scene, camera ); + refractor.onBeforeRender( renderer, scene, camera ); + + scope.visible = true; + + }; + +}; + +THREE.Water.prototype = Object.create( THREE.Mesh.prototype ); +THREE.Water.prototype.constructor = THREE.Water; + +THREE.Water.WaterShader = { + + uniforms: { + + 'color': { + type: 'c', + value: null + }, + + 'reflectivity': { + type: 'f', + value: 0 + }, + + 'tReflectionMap': { + type: 't', + value: null + }, + + 'tRefractionMap': { + type: 't', + value: null + }, + + 'tNormalMap0': { + type: 't', + value: null + }, + + 'tNormalMap1': { + type: 't', + value: null + }, + + 'textureMatrix': { + type: 'm4', + value: null + }, + + 'config': { + type: 'v4', + value: new THREE.Vector4() + } + + }, + + vertexShader: [ + + '#include ', + + 'uniform mat4 textureMatrix;', + + 'varying vec4 vCoord;', + 'varying vec2 vUv;', + 'varying vec3 vToEye;', + + 'void main() {', + + ' vUv = uv;', + ' vCoord = textureMatrix * vec4( position, 1.0 );', + + ' vec4 worldPosition = modelMatrix * vec4( position, 1.0 );', + ' vToEye = cameraPosition - worldPosition.xyz;', + + ' vec4 mvPosition = viewMatrix * worldPosition;', // used in fog_vertex + ' gl_Position = projectionMatrix * mvPosition;', + + ' #include ', + + '}' + + ].join( '\n' ), + + fragmentShader: [ + + '#include ', + + 'uniform sampler2D tReflectionMap;', + 'uniform sampler2D tRefractionMap;', + 'uniform sampler2D tNormalMap0;', + 'uniform sampler2D tNormalMap1;', + + '#ifdef USE_FLOWMAP', + ' uniform sampler2D tFlowMap;', + '#else', + ' uniform vec2 flowDirection;', + '#endif', + + 'uniform vec3 color;', + 'uniform float reflectivity;', + 'uniform vec4 config;', + + 'varying vec4 vCoord;', + 'varying vec2 vUv;', + 'varying vec3 vToEye;', + + 'void main() {', + + ' float flowMapOffset0 = config.x;', + ' float flowMapOffset1 = config.y;', + ' float halfCycle = config.z;', + ' float scale = config.w;', + + ' vec3 toEye = normalize( vToEye );', + + // determine flow direction + ' vec2 flow;', + ' #ifdef USE_FLOWMAP', + ' flow = texture2D( tFlowMap, vUv ).rg * 2.0 - 1.0;', + ' #else', + ' flow = flowDirection;', + ' #endif', + ' flow.x *= - 1.0;', + + // sample normal maps (distort uvs with flowdata) + ' vec4 normalColor0 = texture2D( tNormalMap0, ( vUv * scale ) + flow * flowMapOffset0 );', + ' vec4 normalColor1 = texture2D( tNormalMap1, ( vUv * scale ) + flow * flowMapOffset1 );', + + // linear interpolate to get the final normal color + ' float flowLerp = abs( halfCycle - flowMapOffset0 ) / halfCycle;', + ' vec4 normalColor = mix( normalColor0, normalColor1, flowLerp );', + + // calculate normal vector + ' vec3 normal = normalize( vec3( normalColor.r * 2.0 - 1.0, normalColor.b, normalColor.g * 2.0 - 1.0 ) );', + + // calculate the fresnel term to blend reflection and refraction maps + ' float theta = max( dot( toEye, normal ), 0.0 );', + ' float reflectance = reflectivity + ( 1.0 - reflectivity ) * pow( ( 1.0 - theta ), 5.0 );', + + // calculate final uv coords + ' vec3 coord = vCoord.xyz / vCoord.w;', + ' vec2 uv = coord.xy + coord.z * normal.xz * 0.05;', + + ' vec4 reflectColor = texture2D( tReflectionMap, uv );', + ' vec4 refractColor = texture2D( tRefractionMap, uv );', + + // multiply water color with the mix of both textures + ' gl_FragColor = vec4( color, 1.0 ) * mix( refractColor, reflectColor, reflectance );', + + ' #include ', + ' #include ', + ' #include ', + + '}' + + ].join( '\n' ) +};