Archived
Private
Public Access
1
0

Initial commit

This commit is contained in:
2022-09-04 12:45:01 +02:00
commit f4a01d6a69
11601 changed files with 4206660 additions and 0 deletions

View File

@@ -0,0 +1,347 @@
( function () {
class Lensflare extends THREE.Mesh {
constructor() {
super( Lensflare.Geometry, new THREE.MeshBasicMaterial( {
opacity: 0,
transparent: true
} ) );
this.type = 'Lensflare';
this.frustumCulled = false;
this.renderOrder = Infinity; //
const positionScreen = new THREE.Vector3();
const positionView = new THREE.Vector3(); // textures
const tempMap = new THREE.FramebufferTexture( 16, 16, THREE.RGBAFormat );
const occlusionMap = new THREE.FramebufferTexture( 16, 16, THREE.RGBAFormat ); // material
const geometry = Lensflare.Geometry;
const material1a = new THREE.RawShaderMaterial( {
uniforms: {
'scale': {
value: null
},
'screenPosition': {
value: null
}
},
vertexShader:
/* glsl */
`
precision highp float;
uniform vec3 screenPosition;
uniform vec2 scale;
attribute vec3 position;
void main() {
gl_Position = vec4( position.xy * scale + screenPosition.xy, screenPosition.z, 1.0 );
}`,
fragmentShader:
/* glsl */
`
precision highp float;
void main() {
gl_FragColor = vec4( 1.0, 0.0, 1.0, 1.0 );
}`,
depthTest: true,
depthWrite: false,
transparent: false
} );
const material1b = new THREE.RawShaderMaterial( {
uniforms: {
'map': {
value: tempMap
},
'scale': {
value: null
},
'screenPosition': {
value: null
}
},
vertexShader:
/* glsl */
`
precision highp float;
uniform vec3 screenPosition;
uniform vec2 scale;
attribute vec3 position;
attribute vec2 uv;
varying vec2 vUV;
void main() {
vUV = uv;
gl_Position = vec4( position.xy * scale + screenPosition.xy, screenPosition.z, 1.0 );
}`,
fragmentShader:
/* glsl */
`
precision highp float;
uniform sampler2D map;
varying vec2 vUV;
void main() {
gl_FragColor = texture2D( map, vUV );
}`,
depthTest: false,
depthWrite: false,
transparent: false
} ); // the following object is used for occlusionMap generation
const mesh1 = new THREE.Mesh( geometry, material1a ); //
const elements = [];
const shader = LensflareElement.Shader;
const material2 = new THREE.RawShaderMaterial( {
uniforms: {
'map': {
value: null
},
'occlusionMap': {
value: occlusionMap
},
'color': {
value: new THREE.Color( 0xffffff )
},
'scale': {
value: new THREE.Vector2()
},
'screenPosition': {
value: new THREE.Vector3()
}
},
vertexShader: shader.vertexShader,
fragmentShader: shader.fragmentShader,
blending: THREE.AdditiveBlending,
transparent: true,
depthWrite: false
} );
const mesh2 = new THREE.Mesh( geometry, material2 );
this.addElement = function ( element ) {
elements.push( element );
}; //
const scale = new THREE.Vector2();
const screenPositionPixels = new THREE.Vector2();
const validArea = new THREE.Box2();
const viewport = new THREE.Vector4();
this.onBeforeRender = function ( renderer, scene, camera ) {
renderer.getCurrentViewport( viewport );
const invAspect = viewport.w / viewport.z;
const halfViewportWidth = viewport.z / 2.0;
const halfViewportHeight = viewport.w / 2.0;
let size = 16 / viewport.w;
scale.set( size * invAspect, size );
validArea.min.set( viewport.x, viewport.y );
validArea.max.set( viewport.x + ( viewport.z - 16 ), viewport.y + ( viewport.w - 16 ) ); // calculate position in screen space
positionView.setFromMatrixPosition( this.matrixWorld );
positionView.applyMatrix4( camera.matrixWorldInverse );
if ( positionView.z > 0 ) return; // lensflare is behind the camera
positionScreen.copy( positionView ).applyMatrix4( camera.projectionMatrix ); // horizontal and vertical coordinate of the lower left corner of the pixels to copy
screenPositionPixels.x = viewport.x + positionScreen.x * halfViewportWidth + halfViewportWidth - 8;
screenPositionPixels.y = viewport.y + positionScreen.y * halfViewportHeight + halfViewportHeight - 8; // screen cull
if ( validArea.containsPoint( screenPositionPixels ) ) {
// save current RGB to temp texture
renderer.copyFramebufferToTexture( screenPositionPixels, tempMap ); // render pink quad
let uniforms = material1a.uniforms;
uniforms[ 'scale' ].value = scale;
uniforms[ 'screenPosition' ].value = positionScreen;
renderer.renderBufferDirect( camera, null, geometry, material1a, mesh1, null ); // copy result to occlusionMap
renderer.copyFramebufferToTexture( screenPositionPixels, occlusionMap ); // restore graphics
uniforms = material1b.uniforms;
uniforms[ 'scale' ].value = scale;
uniforms[ 'screenPosition' ].value = positionScreen;
renderer.renderBufferDirect( camera, null, geometry, material1b, mesh1, null ); // render elements
const vecX = - positionScreen.x * 2;
const vecY = - positionScreen.y * 2;
for ( let i = 0, l = elements.length; i < l; i ++ ) {
const element = elements[ i ];
const uniforms = material2.uniforms;
uniforms[ 'color' ].value.copy( element.color );
uniforms[ 'map' ].value = element.texture;
uniforms[ 'screenPosition' ].value.x = positionScreen.x + vecX * element.distance;
uniforms[ 'screenPosition' ].value.y = positionScreen.y + vecY * element.distance;
size = element.size / viewport.w;
const invAspect = viewport.w / viewport.z;
uniforms[ 'scale' ].value.set( size * invAspect, size );
material2.uniformsNeedUpdate = true;
renderer.renderBufferDirect( camera, null, geometry, material2, mesh2, null );
}
}
};
this.dispose = function () {
material1a.dispose();
material1b.dispose();
material2.dispose();
tempMap.dispose();
occlusionMap.dispose();
for ( let i = 0, l = elements.length; i < l; i ++ ) {
elements[ i ].texture.dispose();
}
};
}
}
Lensflare.prototype.isLensflare = true; //
class LensflareElement {
constructor( texture, size = 1, distance = 0, color = new THREE.Color( 0xffffff ) ) {
this.texture = texture;
this.size = size;
this.distance = distance;
this.color = color;
}
}
LensflareElement.Shader = {
uniforms: {
'map': {
value: null
},
'occlusionMap': {
value: null
},
'color': {
value: null
},
'scale': {
value: null
},
'screenPosition': {
value: null
}
},
vertexShader:
/* glsl */
`
precision highp float;
uniform vec3 screenPosition;
uniform vec2 scale;
uniform sampler2D occlusionMap;
attribute vec3 position;
attribute vec2 uv;
varying vec2 vUV;
varying float vVisibility;
void main() {
vUV = uv;
vec2 pos = position.xy;
vec4 visibility = texture2D( occlusionMap, vec2( 0.1, 0.1 ) );
visibility += texture2D( occlusionMap, vec2( 0.5, 0.1 ) );
visibility += texture2D( occlusionMap, vec2( 0.9, 0.1 ) );
visibility += texture2D( occlusionMap, vec2( 0.9, 0.5 ) );
visibility += texture2D( occlusionMap, vec2( 0.9, 0.9 ) );
visibility += texture2D( occlusionMap, vec2( 0.5, 0.9 ) );
visibility += texture2D( occlusionMap, vec2( 0.1, 0.9 ) );
visibility += texture2D( occlusionMap, vec2( 0.1, 0.5 ) );
visibility += texture2D( occlusionMap, vec2( 0.5, 0.5 ) );
vVisibility = visibility.r / 9.0;
vVisibility *= 1.0 - visibility.g / 9.0;
vVisibility *= visibility.b / 9.0;
gl_Position = vec4( ( pos * scale + screenPosition.xy ).xy, screenPosition.z, 1.0 );
}`,
fragmentShader:
/* glsl */
`
precision highp float;
uniform sampler2D map;
uniform vec3 color;
varying vec2 vUV;
varying float vVisibility;
void main() {
vec4 texture = texture2D( map, vUV );
texture.a *= vVisibility;
gl_FragColor = texture;
gl_FragColor.rgb *= color;
}`
};
Lensflare.Geometry = function () {
const geometry = new THREE.BufferGeometry();
const float32Array = new Float32Array( [ - 1, - 1, 0, 0, 0, 1, - 1, 0, 1, 0, 1, 1, 0, 1, 1, - 1, 1, 0, 0, 1 ] );
const interleavedBuffer = new THREE.InterleavedBuffer( float32Array, 5 );
geometry.setIndex( [ 0, 1, 2, 0, 2, 3 ] );
geometry.setAttribute( 'position', new THREE.InterleavedBufferAttribute( interleavedBuffer, 3, 0, false ) );
geometry.setAttribute( 'uv', new THREE.InterleavedBufferAttribute( interleavedBuffer, 2, 3, false ) );
return geometry;
}();
THREE.Lensflare = Lensflare;
THREE.LensflareElement = LensflareElement;
} )();

View File

@@ -0,0 +1,210 @@
( function () {
/**
* @fileoverview Lightning strike object generator
*
*
* Usage
*
* const myStorm = new LightningStorm( paramsObject );
* myStorm.position.set( ... );
* scene.add( myStorm );
* ...
* myStorm.update( currentTime );
*
* The "currentTime" can only go forwards or be stopped.
*
*
* LightningStorm parameters:
*
* @param {double} size Size of the storm. If no 'onRayPosition' parameter is defined, it means the side of the rectangle the storm covers.
*
* @param {double} minHeight Minimum height a ray can start at. If no 'onRayPosition' parameter is defined, it means the height above plane y = 0.
*
* @param {double} maxHeight Maximum height a ray can start at. If no 'onRayPosition' parameter is defined, it means the height above plane y = 0.
*
* @param {double} maxSlope The maximum inclination slope of a ray. If no 'onRayPosition' parameter is defined, it means the slope relative to plane y = 0.
*
* @param {integer} maxLightnings Greater than 0. The maximum number of simultaneous rays.
*
* @param {double} lightningMinPeriod minimum time between two consecutive rays.
*
* @param {double} lightningMaxPeriod maximum time between two consecutive rays.
*
* @param {double} lightningMinDuration The minimum time a ray can last.
*
* @param {double} lightningMaxDuration The maximum time a ray can last.
*
* @param {Object} lightningParameters The parameters for created rays. See THREE.LightningStrike (geometry)
*
* @param {Material} lightningMaterial The THREE.Material used for the created rays.
*
* @param {function} onRayPosition Optional callback with two Vector3 parameters (source, dest). You can set here the start and end points for each created ray, using the standard size, minHeight, etc parameters and other values in your algorithm.
*
* @param {function} onLightningDown This optional callback is called with one parameter (lightningStrike) when a ray ends propagating, so it has hit the ground.
*
*
*/
class LightningStorm extends THREE.Object3D {
constructor( stormParams = {} ) {
super(); // Parameters
this.stormParams = stormParams;
stormParams.size = stormParams.size !== undefined ? stormParams.size : 1000.0;
stormParams.minHeight = stormParams.minHeight !== undefined ? stormParams.minHeight : 80.0;
stormParams.maxHeight = stormParams.maxHeight !== undefined ? stormParams.maxHeight : 100.0;
stormParams.maxSlope = stormParams.maxSlope !== undefined ? stormParams.maxSlope : 1.1;
stormParams.maxLightnings = stormParams.maxLightnings !== undefined ? stormParams.maxLightnings : 3;
stormParams.lightningMinPeriod = stormParams.lightningMinPeriod !== undefined ? stormParams.lightningMinPeriod : 3.0;
stormParams.lightningMaxPeriod = stormParams.lightningMaxPeriod !== undefined ? stormParams.lightningMaxPeriod : 7.0;
stormParams.lightningMinDuration = stormParams.lightningMinDuration !== undefined ? stormParams.lightningMinDuration : 1.0;
stormParams.lightningMaxDuration = stormParams.lightningMaxDuration !== undefined ? stormParams.lightningMaxDuration : 2.5;
this.lightningParameters = THREE.LightningStrike.copyParameters( stormParams.lightningParameters, stormParams.lightningParameters );
this.lightningParameters.isEternal = false;
this.lightningMaterial = stormParams.lightningMaterial !== undefined ? stormParams.lightningMaterial : new THREE.MeshBasicMaterial( {
color: 0xB0FFFF
} );
if ( stormParams.onRayPosition !== undefined ) {
this.onRayPosition = stormParams.onRayPosition;
} else {
this.onRayPosition = function ( source, dest ) {
dest.set( ( Math.random() - 0.5 ) * stormParams.size, 0, ( Math.random() - 0.5 ) * stormParams.size );
const height = THREE.MathUtils.lerp( stormParams.minHeight, stormParams.maxHeight, Math.random() );
source.set( stormParams.maxSlope * ( 2 * Math.random() - 1 ), 1, stormParams.maxSlope * ( 2 * Math.random() - 1 ) ).multiplyScalar( height ).add( dest );
};
}
this.onLightningDown = stormParams.onLightningDown; // Internal state
this.inited = false;
this.nextLightningTime = 0;
this.lightningsMeshes = [];
this.deadLightningsMeshes = [];
for ( let i = 0; i < this.stormParams.maxLightnings; i ++ ) {
const lightning = new THREE.LightningStrike( THREE.LightningStrike.copyParameters( {}, this.lightningParameters ) );
const mesh = new THREE.Mesh( lightning, this.lightningMaterial );
this.deadLightningsMeshes.push( mesh );
}
}
update( time ) {
if ( ! this.inited ) {
this.nextLightningTime = this.getNextLightningTime( time ) * Math.random();
this.inited = true;
}
if ( time >= this.nextLightningTime ) {
// Lightning creation
const lightningMesh = this.deadLightningsMeshes.pop();
if ( lightningMesh ) {
const lightningParams1 = THREE.LightningStrike.copyParameters( lightningMesh.geometry.rayParameters, this.lightningParameters );
lightningParams1.birthTime = time;
lightningParams1.deathTime = time + THREE.MathUtils.lerp( this.stormParams.lightningMinDuration, this.stormParams.lightningMaxDuration, Math.random() );
this.onRayPosition( lightningParams1.sourceOffset, lightningParams1.destOffset );
lightningParams1.noiseSeed = Math.random();
this.add( lightningMesh );
this.lightningsMeshes.push( lightningMesh );
} // Schedule next lightning
this.nextLightningTime = this.getNextLightningTime( time );
}
let i = 0,
il = this.lightningsMeshes.length;
while ( i < il ) {
const mesh = this.lightningsMeshes[ i ];
const lightning = mesh.geometry;
const prevState = lightning.state;
lightning.update( time );
if ( prevState === THREE.LightningStrike.RAY_PROPAGATING && lightning.state > prevState ) {
if ( this.onLightningDown ) {
this.onLightningDown( lightning );
}
}
if ( lightning.state === THREE.LightningStrike.RAY_EXTINGUISHED ) {
// Lightning is to be destroyed
this.lightningsMeshes.splice( this.lightningsMeshes.indexOf( mesh ), 1 );
this.deadLightningsMeshes.push( mesh );
this.remove( mesh );
il --;
} else {
i ++;
}
}
}
getNextLightningTime( currentTime ) {
return currentTime + THREE.MathUtils.lerp( this.stormParams.lightningMinPeriod, this.stormParams.lightningMaxPeriod, Math.random() ) / ( this.stormParams.maxLightnings + 1 );
}
copy( source ) {
super.copy( source );
this.stormParams.size = source.stormParams.size;
this.stormParams.minHeight = source.stormParams.minHeight;
this.stormParams.maxHeight = source.stormParams.maxHeight;
this.stormParams.maxSlope = source.stormParams.maxSlope;
this.stormParams.maxLightnings = source.stormParams.maxLightnings;
this.stormParams.lightningMinPeriod = source.stormParams.lightningMinPeriod;
this.stormParams.lightningMaxPeriod = source.stormParams.lightningMaxPeriod;
this.stormParams.lightningMinDuration = source.stormParams.lightningMinDuration;
this.stormParams.lightningMaxDuration = source.stormParams.lightningMaxDuration;
this.lightningParameters = THREE.LightningStrike.copyParameters( {}, source.lightningParameters );
this.lightningMaterial = source.stormParams.lightningMaterial;
this.onLightningDown = source.onLightningDown;
return this;
}
clone() {
return new this.constructor( this.stormParams ).copy( this );
}
}
LightningStorm.prototype.isLightningStorm = true;
THREE.LightningStorm = LightningStorm;
} )();

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,204 @@
( function () {
class Reflector extends THREE.Mesh {
constructor( geometry, options = {} ) {
super( geometry );
this.type = 'Reflector';
const scope = this;
const color = options.color !== undefined ? new THREE.Color( options.color ) : new THREE.Color( 0x7F7F7F );
const textureWidth = options.textureWidth || 512;
const textureHeight = options.textureHeight || 512;
const clipBias = options.clipBias || 0;
const shader = options.shader || Reflector.ReflectorShader;
const multisample = options.multisample !== undefined ? options.multisample : 4; //
const reflectorPlane = new THREE.Plane();
const normal = new THREE.Vector3();
const reflectorWorldPosition = new THREE.Vector3();
const cameraWorldPosition = new THREE.Vector3();
const rotationMatrix = new THREE.Matrix4();
const lookAtPosition = new THREE.Vector3( 0, 0, - 1 );
const clipPlane = new THREE.Vector4();
const view = new THREE.Vector3();
const target = new THREE.Vector3();
const q = new THREE.Vector4();
const textureMatrix = new THREE.Matrix4();
const virtualCamera = new THREE.PerspectiveCamera();
const renderTarget = new THREE.WebGLRenderTarget( textureWidth, textureHeight, {
samples: multisample
} );
const 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 ) {
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 ); // 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 );
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( 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;
const currentRenderTarget = renderer.getRenderTarget();
const currentXrEnabled = renderer.xr.enabled;
const 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
const viewport = camera.viewport;
if ( viewport !== undefined ) {
renderer.state.viewport( viewport );
}
scope.visible = true;
};
this.getRenderTarget = function () {
return renderTarget;
};
this.dispose = function () {
renderTarget.dispose();
scope.material.dispose();
};
}
}
Reflector.prototype.isReflector = true;
Reflector.ReflectorShader = {
uniforms: {
'color': {
value: null
},
'tDiffuse': {
value: null
},
'textureMatrix': {
value: null
}
},
vertexShader:
/* glsl */
`
uniform mat4 textureMatrix;
varying vec4 vUv;
#include <common>
#include <logdepthbuf_pars_vertex>
void main() {
vUv = textureMatrix * vec4( position, 1.0 );
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
#include <logdepthbuf_vertex>
}`,
fragmentShader:
/* glsl */
`
uniform vec3 color;
uniform sampler2D tDiffuse;
varying vec4 vUv;
#include <logdepthbuf_pars_fragment>
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() {
#include <logdepthbuf_fragment>
vec4 base = texture2DProj( tDiffuse, vUv );
gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 );
}`
};
THREE.Reflector = Reflector;
} )();

View File

@@ -0,0 +1,320 @@
( function () {
class ReflectorForSSRPass extends THREE.Mesh {
constructor( geometry, options = {} ) {
super( geometry );
this.type = 'ReflectorForSSRPass';
const scope = this;
const color = options.color !== undefined ? new THREE.Color( options.color ) : new THREE.Color( 0x7F7F7F );
const textureWidth = options.textureWidth || 512;
const textureHeight = options.textureHeight || 512;
const clipBias = options.clipBias || 0;
const shader = options.shader || ReflectorForSSRPass.ReflectorShader;
const useDepthTexture = options.useDepthTexture === true;
const yAxis = new THREE.Vector3( 0, 1, 0 );
const vecTemp0 = new THREE.Vector3();
const vecTemp1 = new THREE.Vector3(); //
scope.needsUpdate = false;
scope.maxDistance = ReflectorForSSRPass.ReflectorShader.uniforms.maxDistance.value;
scope.opacity = ReflectorForSSRPass.ReflectorShader.uniforms.opacity.value;
scope.color = color;
scope.resolution = options.resolution || new THREE.Vector2( window.innerWidth, window.innerHeight );
scope._distanceAttenuation = ReflectorForSSRPass.ReflectorShader.defines.DISTANCE_ATTENUATION;
Object.defineProperty( scope, 'distanceAttenuation', {
get() {
return scope._distanceAttenuation;
},
set( val ) {
if ( scope._distanceAttenuation === val ) return;
scope._distanceAttenuation = val;
scope.material.defines.DISTANCE_ATTENUATION = val;
scope.material.needsUpdate = true;
}
} );
scope._fresnel = ReflectorForSSRPass.ReflectorShader.defines.FRESNEL;
Object.defineProperty( scope, 'fresnel', {
get() {
return scope._fresnel;
},
set( val ) {
if ( scope._fresnel === val ) return;
scope._fresnel = val;
scope.material.defines.FRESNEL = val;
scope.material.needsUpdate = true;
}
} );
const normal = new THREE.Vector3();
const reflectorWorldPosition = new THREE.Vector3();
const cameraWorldPosition = new THREE.Vector3();
const rotationMatrix = new THREE.Matrix4();
const lookAtPosition = new THREE.Vector3( 0, 0, - 1 );
const view = new THREE.Vector3();
const target = new THREE.Vector3();
const textureMatrix = new THREE.Matrix4();
const virtualCamera = new THREE.PerspectiveCamera();
let depthTexture;
if ( useDepthTexture ) {
depthTexture = new THREE.DepthTexture();
depthTexture.type = THREE.UnsignedShortType;
depthTexture.minFilter = THREE.NearestFilter;
depthTexture.magFilter = THREE.NearestFilter;
}
const parameters = {
depthTexture: useDepthTexture ? depthTexture : null
};
const renderTarget = new THREE.WebGLRenderTarget( textureWidth, textureHeight, parameters );
const material = new THREE.ShaderMaterial( {
transparent: useDepthTexture,
defines: Object.assign( {}, ReflectorForSSRPass.ReflectorShader.defines, {
useDepthTexture
} ),
uniforms: THREE.UniformsUtils.clone( shader.uniforms ),
fragmentShader: shader.fragmentShader,
vertexShader: shader.vertexShader
} );
material.uniforms[ 'tDiffuse' ].value = renderTarget.texture;
material.uniforms[ 'color' ].value = scope.color;
material.uniforms[ 'textureMatrix' ].value = textureMatrix;
if ( useDepthTexture ) {
material.uniforms[ 'tDepth' ].value = renderTarget.depthTexture;
}
this.material = material;
const globalPlane = new THREE.Plane( new THREE.Vector3( 0, 1, 0 ), clipBias );
const globalPlanes = [ globalPlane ];
this.doRender = function ( renderer, scene, camera ) {
material.uniforms[ 'maxDistance' ].value = scope.maxDistance;
material.uniforms[ 'color' ].value = scope.color;
material.uniforms[ 'opacity' ].value = scope.opacity;
vecTemp0.copy( camera.position ).normalize();
vecTemp1.copy( vecTemp0 ).reflect( yAxis );
material.uniforms[ 'fresnelCoe' ].value = ( vecTemp0.dot( vecTemp1 ) + 1. ) / 2.; // TODO: Also need to use glsl viewPosition and viewNormal per pixel.
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 );
material.uniforms[ 'virtualCameraNear' ].value = camera.near;
material.uniforms[ 'virtualCameraFar' ].value = camera.far;
material.uniforms[ 'virtualCameraMatrixWorld' ].value = virtualCamera.matrixWorld;
material.uniforms[ 'virtualCameraProjectionMatrix' ].value = camera.projectionMatrix;
material.uniforms[ 'virtualCameraProjectionMatrixInverse' ].value = camera.projectionMatrixInverse;
material.uniforms[ 'resolution' ].value = scope.resolution; // 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 ); // Render
renderTarget.texture.encoding = renderer.outputEncoding; // scope.visible = false;
const currentRenderTarget = renderer.getRenderTarget();
const currentXrEnabled = renderer.xr.enabled;
const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;
const currentClippingPlanes = renderer.clippingPlanes;
renderer.xr.enabled = false; // Avoid camera modification
renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows
renderer.clippingPlanes = globalPlanes;
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.clippingPlanes = currentClippingPlanes;
renderer.setRenderTarget( currentRenderTarget ); // Restore viewport
const viewport = camera.viewport;
if ( viewport !== undefined ) {
renderer.state.viewport( viewport );
} // scope.visible = true;
};
this.getRenderTarget = function () {
return renderTarget;
};
}
}
ReflectorForSSRPass.prototype.isReflectorForSSRPass = true;
ReflectorForSSRPass.ReflectorShader = {
defines: {
DISTANCE_ATTENUATION: true,
FRESNEL: true
},
uniforms: {
color: {
value: null
},
tDiffuse: {
value: null
},
tDepth: {
value: null
},
textureMatrix: {
value: new THREE.Matrix4()
},
maxDistance: {
value: 180
},
opacity: {
value: 0.5
},
fresnelCoe: {
value: null
},
virtualCameraNear: {
value: null
},
virtualCameraFar: {
value: null
},
virtualCameraProjectionMatrix: {
value: new THREE.Matrix4()
},
virtualCameraMatrixWorld: {
value: new THREE.Matrix4()
},
virtualCameraProjectionMatrixInverse: {
value: new THREE.Matrix4()
},
resolution: {
value: new THREE.Vector2()
}
},
vertexShader:
/* glsl */
`
uniform mat4 textureMatrix;
varying vec4 vUv;
void main() {
vUv = textureMatrix * vec4( position, 1.0 );
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader:
/* glsl */
`
uniform vec3 color;
uniform sampler2D tDiffuse;
uniform sampler2D tDepth;
uniform float maxDistance;
uniform float opacity;
uniform float fresnelCoe;
uniform float virtualCameraNear;
uniform float virtualCameraFar;
uniform mat4 virtualCameraProjectionMatrix;
uniform mat4 virtualCameraProjectionMatrixInverse;
uniform mat4 virtualCameraMatrixWorld;
uniform vec2 resolution;
varying vec4 vUv;
#include <packing>
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 getDepth( const in vec2 uv ) {
return texture2D( tDepth, uv ).x;
}
float getViewZ( const in float depth ) {
return perspectiveDepthToViewZ( depth, virtualCameraNear, virtualCameraFar );
}
vec3 getViewPosition( const in vec2 uv, const in float depth/*clip space*/, const in float clipW ) {
vec4 clipPosition = vec4( ( vec3( uv, depth ) - 0.5 ) * 2.0, 1.0 );//ndc
clipPosition *= clipW; //clip
return ( virtualCameraProjectionMatrixInverse * clipPosition ).xyz;//view
}
void main() {
vec4 base = texture2DProj( tDiffuse, vUv );
#ifdef useDepthTexture
vec2 uv=(gl_FragCoord.xy-.5)/resolution.xy;
uv.x=1.-uv.x;
float depth = texture2DProj( tDepth, vUv ).r;
float viewZ = getViewZ( depth );
float clipW = virtualCameraProjectionMatrix[2][3] * viewZ+virtualCameraProjectionMatrix[3][3];
vec3 viewPosition=getViewPosition( uv, depth, clipW );
vec3 worldPosition=(virtualCameraMatrixWorld*vec4(viewPosition,1)).xyz;
if(worldPosition.y>maxDistance) discard;
float op=opacity;
#ifdef DISTANCE_ATTENUATION
float ratio=1.-(worldPosition.y/maxDistance);
float attenuation=ratio*ratio;
op=opacity*attenuation;
#endif
#ifdef FRESNEL
op*=fresnelCoe;
#endif
gl_FragColor = vec4( blendOverlay( base.rgb, color ), op );
#else
gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 );
#endif
}
`
};
THREE.ReflectorForSSRPass = ReflectorForSSRPass;
} )();

View File

@@ -0,0 +1,252 @@
( function () {
class Refractor extends THREE.Mesh {
constructor( geometry, options = {} ) {
super( geometry );
this.type = 'Refractor';
const scope = this;
const color = options.color !== undefined ? new THREE.Color( options.color ) : new THREE.Color( 0x7F7F7F );
const textureWidth = options.textureWidth || 512;
const textureHeight = options.textureHeight || 512;
const clipBias = options.clipBias || 0;
const shader = options.shader || Refractor.RefractorShader;
const multisample = options.multisample !== undefined ? options.multisample : 4; //
const virtualCamera = new THREE.PerspectiveCamera();
virtualCamera.matrixAutoUpdate = false;
virtualCamera.userData.refractor = true; //
const refractorPlane = new THREE.Plane();
const textureMatrix = new THREE.Matrix4(); // render target
const renderTarget = new THREE.WebGLRenderTarget( textureWidth, textureHeight, {
samples: multisample
} ); // 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
const visible = function () {
const refractorWorldPosition = new THREE.Vector3();
const cameraWorldPosition = new THREE.Vector3();
const rotationMatrix = new THREE.Matrix4();
const view = new THREE.Vector3();
const 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;
};
}();
const updateRefractorPlane = function () {
const normal = new THREE.Vector3();
const position = new THREE.Vector3();
const quaternion = new THREE.Quaternion();
const 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 );
};
}();
const updateVirtualCamera = function () {
const clipPlane = new THREE.Plane();
const clipVector = new THREE.Vector4();
const q = new THREE.Vector4();
return function updateVirtualCamera( camera ) {
virtualCamera.matrixWorld.copy( camera.matrixWorld );
virtualCamera.matrixWorldInverse.copy( virtualCamera.matrixWorld ).invert();
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. 516
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
const 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 );
} //
function render( renderer, scene, camera ) {
scope.visible = false;
const currentRenderTarget = renderer.getRenderTarget();
const currentXrEnabled = renderer.xr.enabled;
const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;
renderer.xr.enabled = false; // avoid camera modification
renderer.shadowMap.autoUpdate = false; // avoid re-computing shadows
renderer.setRenderTarget( renderTarget );
if ( renderer.autoClear === false ) renderer.clear();
renderer.render( scene, virtualCamera );
renderer.xr.enabled = currentXrEnabled;
renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
renderer.setRenderTarget( currentRenderTarget ); // restore viewport
const viewport = camera.viewport;
if ( viewport !== undefined ) {
renderer.state.viewport( viewport );
}
scope.visible = true;
} //
this.onBeforeRender = function ( renderer, scene, camera ) {
// Render
renderTarget.texture.encoding = renderer.outputEncoding; // 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;
};
this.dispose = function () {
renderTarget.dispose();
scope.material.dispose();
};
}
}
Refractor.prototype.isRefractor = true;
Refractor.RefractorShader = {
uniforms: {
'color': {
value: null
},
'tDiffuse': {
value: null
},
'textureMatrix': {
value: null
}
},
vertexShader:
/* glsl */
`
uniform mat4 textureMatrix;
varying vec4 vUv;
void main() {
vUv = textureMatrix * vec4( position, 1.0 );
gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
}`,
fragmentShader:
/* glsl */
`
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 );
}`
};
THREE.Refractor = Refractor;
} )();

View File

@@ -0,0 +1,57 @@
( function () {
/**
* A shadow THREE.Mesh that follows a shadow-casting THREE.Mesh in the scene, but is confined to a single plane.
*/
const _shadowMatrix = new THREE.Matrix4();
class ShadowMesh extends THREE.Mesh {
constructor( mesh ) {
const shadowMaterial = new THREE.MeshBasicMaterial( {
color: 0x000000,
transparent: true,
opacity: 0.6,
depthWrite: false
} );
super( mesh.geometry, shadowMaterial );
this.meshMatrix = mesh.matrixWorld;
this.frustumCulled = false;
this.matrixAutoUpdate = false;
}
update( plane, lightPosition4D ) {
// based on https://www.opengl.org/archives/resources/features/StencilTalk/tsld021.htm
const dot = plane.normal.x * lightPosition4D.x + plane.normal.y * lightPosition4D.y + plane.normal.z * lightPosition4D.z + - plane.constant * lightPosition4D.w;
const 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 );
}
}
ShadowMesh.prototype.isShadowMesh = true;
THREE.ShadowMesh = ShadowMesh;
} )();

View File

@@ -0,0 +1,223 @@
( function () {
/**
* Based on "A Practical Analytic Model for Daylight"
* aka The Preetham Model, the de facto standard analytic skydome model
* https://www.researchgate.net/publication/220720443_A_Practical_Analytic_Model_for_Daylight
*
* First implemented by Simon Wallner
* http://simonwallner.at/project/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
*/
class Sky extends THREE.Mesh {
constructor() {
const shader = Sky.SkyShader;
const material = new THREE.ShaderMaterial( {
name: 'SkyShader',
fragmentShader: shader.fragmentShader,
vertexShader: shader.vertexShader,
uniforms: THREE.UniformsUtils.clone( shader.uniforms ),
side: THREE.BackSide,
depthWrite: false
} );
super( new THREE.BoxGeometry( 1, 1, 1 ), material );
}
}
Sky.prototype.isSky = true;
Sky.SkyShader = {
uniforms: {
'turbidity': {
value: 2
},
'rayleigh': {
value: 1
},
'mieCoefficient': {
value: 0.005
},
'mieDirectionalG': {
value: 0.8
},
'sunPosition': {
value: new THREE.Vector3()
},
'up': {
value: new THREE.Vector3( 0, 1, 0 )
}
},
vertexShader:
/* glsl */
`
uniform vec3 sunPosition;
uniform float rayleigh;
uniform float turbidity;
uniform float mieCoefficient;
uniform vec3 up;
varying vec3 vWorldPosition;
varying vec3 vSunDirection;
varying float vSunfade;
varying vec3 vBetaR;
varying vec3 vBetaM;
varying float vSunE;
// 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 );
gl_Position.z = gl_Position.w; // set z to camera.far
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;
}`,
fragmentShader:
/* glsl */
`
varying vec3 vWorldPosition;
varying vec3 vSunDirection;
varying float vSunfade;
varying vec3 vBetaR;
varying vec3 vBetaM;
varying float vSunE;
uniform float mieDirectionalG;
uniform vec3 up;
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;
// 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 );
}
void main() {
vec3 direction = normalize( vWorldPosition - cameraPos );
// optical length
// cutoff angle at 90 to avoid singularity in next formula.
float zenithAngle = acos( max( 0.0, dot( up, direction ) ) );
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( direction, 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
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 retColor = pow( texColor, vec3( 1.0 / ( 1.2 + ( 1.2 * vSunfade ) ) ) );
gl_FragColor = vec4( retColor, 1.0 );
#include <tonemapping_fragment>
#include <encodings_fragment>
}`
};
THREE.Sky = Sky;
} )();

View File

@@ -0,0 +1,288 @@
( function () {
/**
* Work based on :
* https://github.com/Slayvin: Flat mirror for three.js
* https://home.adelphi.edu/~stemkoski/ : An implementation of water shader based on the flat mirror
* http://29a.ch/ && http://29a.ch/slides/2012/webglwater/ : Water shader explanations in WebGL
*/
class Water extends THREE.Mesh {
constructor( geometry, options = {} ) {
super( geometry );
const scope = this;
const textureWidth = options.textureWidth !== undefined ? options.textureWidth : 512;
const textureHeight = options.textureHeight !== undefined ? options.textureHeight : 512;
const clipBias = options.clipBias !== undefined ? options.clipBias : 0.0;
const alpha = options.alpha !== undefined ? options.alpha : 1.0;
const time = options.time !== undefined ? options.time : 0.0;
const normalSampler = options.waterNormals !== undefined ? options.waterNormals : null;
const sunDirection = options.sunDirection !== undefined ? options.sunDirection : new THREE.Vector3( 0.70707, 0.70707, 0.0 );
const sunColor = new THREE.Color( options.sunColor !== undefined ? options.sunColor : 0xffffff );
const waterColor = new THREE.Color( options.waterColor !== undefined ? options.waterColor : 0x7F7F7F );
const eye = options.eye !== undefined ? options.eye : new THREE.Vector3( 0, 0, 0 );
const distortionScale = options.distortionScale !== undefined ? options.distortionScale : 20.0;
const side = options.side !== undefined ? options.side : THREE.FrontSide;
const fog = options.fog !== undefined ? options.fog : false; //
const mirrorPlane = new THREE.Plane();
const normal = new THREE.Vector3();
const mirrorWorldPosition = new THREE.Vector3();
const cameraWorldPosition = new THREE.Vector3();
const rotationMatrix = new THREE.Matrix4();
const lookAtPosition = new THREE.Vector3( 0, 0, - 1 );
const clipPlane = new THREE.Vector4();
const view = new THREE.Vector3();
const target = new THREE.Vector3();
const q = new THREE.Vector4();
const textureMatrix = new THREE.Matrix4();
const mirrorCamera = new THREE.PerspectiveCamera();
const renderTarget = new THREE.WebGLRenderTarget( textureWidth, textureHeight );
const 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
},
'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:
/* glsl */
`
uniform mat4 textureMatrix;
uniform float time;
varying vec4 mirrorCoord;
varying vec4 worldPosition;
#include <common>
#include <fog_pars_vertex>
#include <shadowmap_pars_vertex>
#include <logdepthbuf_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;
#include <beginnormal_vertex>
#include <defaultnormal_vertex>
#include <logdepthbuf_vertex>
#include <fog_vertex>
#include <shadowmap_vertex>
}`,
fragmentShader:
/* glsl */
`
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;
}
#include <common>
#include <packing>
#include <bsdfs>
#include <fog_pars_fragment>
#include <logdepthbuf_pars_fragment>
#include <lights_pars_begin>
#include <shadowmap_pars_fragment>
#include <shadowmask_pars_fragment>
void main() {
#include <logdepthbuf_fragment>
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.w + 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 );
#include <tonemapping_fragment>
#include <fog_fragment>
}`
};
const material = new THREE.ShaderMaterial( {
fragmentShader: mirrorShader.fragmentShader,
vertexShader: mirrorShader.vertexShader,
uniforms: THREE.UniformsUtils.clone( mirrorShader.uniforms ),
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 );
const 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 ); // Render
const currentRenderTarget = renderer.getRenderTarget();
const currentXrEnabled = renderer.xr.enabled;
const currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;
scope.visible = false;
renderer.xr.enabled = false; // Avoid camera modification and recursion
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, mirrorCamera );
scope.visible = true;
renderer.xr.enabled = currentXrEnabled;
renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
renderer.setRenderTarget( currentRenderTarget ); // Restore viewport
const viewport = camera.viewport;
if ( viewport !== undefined ) {
renderer.state.viewport( viewport );
}
};
}
}
Water.prototype.isWater = true;
THREE.Water = Water;
} )();

View File

@@ -0,0 +1,306 @@
( function () {
/**
* References:
* https://alex.vlachos.com/graphics/Vlachos-SIGGRAPH10-WaterFlow.pdf
* http://graphicsrunner.blogspot.de/2010/08/water-using-flow-maps.html
*
*/
class Water extends THREE.Mesh {
constructor( geometry, options = {} ) {
super( geometry );
this.type = 'Water';
const scope = this;
const color = options.color !== undefined ? new THREE.Color( options.color ) : new THREE.Color( 0xFFFFFF );
const textureWidth = options.textureWidth || 512;
const textureHeight = options.textureHeight || 512;
const clipBias = options.clipBias || 0;
const flowDirection = options.flowDirection || new THREE.Vector2( 1, 0 );
const flowSpeed = options.flowSpeed || 0.03;
const reflectivity = options.reflectivity || 0.02;
const scale = options.scale || 1;
const shader = options.shader || Water.WaterShader;
const textureLoader = new THREE.TextureLoader();
const flowMap = options.flowMap || undefined;
const normalMap0 = options.normalMap0 || textureLoader.load( 'textures/water/Water_1_M_Normal.jpg' );
const normalMap1 = options.normalMap1 || textureLoader.load( 'textures/water/Water_2_M_Normal.jpg' );
const cycle = 0.15; // a cycle of a flow map phase
const halfCycle = cycle * 0.5;
const textureMatrix = new THREE.Matrix4();
const 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;
}
const reflector = new THREE.Reflector( geometry, {
textureWidth: textureWidth,
textureHeight: textureHeight,
clipBias: clipBias
} );
const refractor = new THREE.Refractor( geometry, {
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() {
const delta = clock.getDelta();
const 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;
};
}
}
Water.prototype.isWater = true;
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:
/* glsl */
`
#include <common>
#include <fog_pars_vertex>
#include <logdepthbuf_pars_vertex>
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 <logdepthbuf_vertex>
#include <fog_vertex>
}`,
fragmentShader:
/* glsl */
`
#include <common>
#include <fog_pars_fragment>
#include <logdepthbuf_pars_fragment>
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() {
#include <logdepthbuf_fragment>
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, vec2( 1.0 - uv.x, uv.y ) );
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 <tonemapping_fragment>
#include <encodings_fragment>
#include <fog_fragment>
}`
};
THREE.Water = Water;
} )();