소스 검색

JSM: Added module and TS file for Water/Water2.

Mugen87 6 년 전
부모
커밋
1caad6127b

+ 2 - 0
docs/manual/en/introduction/Import-via-modules.html

@@ -155,6 +155,8 @@
 						<li>Refractor</li>
 						<li>Refractor</li>
 						<li>ShadowMesh</li>
 						<li>ShadowMesh</li>
 						<li>Sky</li>
 						<li>Sky</li>
+						<li>Water</li>
+						<li>Water2</li>
 					</ul>
 					</ul>
 				</li>
 				</li>
 				<li>pmrem
 				<li>pmrem

+ 29 - 0
examples/jsm/objects/Water.d.ts

@@ -0,0 +1,29 @@
+import {
+  BufferGeometry,
+  Color,
+  Geometry,
+  Mesh,
+  Side,
+  Texture,
+  Vector3
+} from '../../../src/Three';
+
+export interface WaterOptions {
+  textureWidth?: number;
+  textureHeight?: number;
+  clipBias?: number;
+  alpha?: number;
+  time?: number;
+  waterNormals?: Texture;
+  sunDirection?: Vector3;
+  sunColor?: Color | string | number;
+  waterColor?: Color | string | number;
+  eye?: Vector3;
+  distortionScale?: number;
+  side?: Side;
+  fog?: boolean;
+}
+
+export class Water extends Mesh {
+  constructor(geometry: Geometry | BufferGeometry, options: WaterOptions);
+}

+ 331 - 0
examples/jsm/objects/Water.js

@@ -0,0 +1,331 @@
+/**
+ * @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
+ */
+
+import {
+	Color,
+	FrontSide,
+	LinearFilter,
+	Math as _Math,
+	Matrix4,
+	Mesh,
+	PerspectiveCamera,
+	Plane,
+	RGBFormat,
+	ShaderChunk,
+	ShaderMaterial,
+	UniformsLib,
+	UniformsUtils,
+	Vector3,
+	Vector4,
+	WebGLRenderTarget
+} from "../../../build/three.module.js";
+
+var Water = function ( geometry, options ) {
+
+	Mesh.call( this, geometry );
+
+	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 Vector3( 0.70707, 0.70707, 0.0 );
+	var sunColor = new Color( options.sunColor !== undefined ? options.sunColor : 0xffffff );
+	var waterColor = new Color( options.waterColor !== undefined ? options.waterColor : 0x7F7F7F );
+	var eye = options.eye !== undefined ? options.eye : new Vector3( 0, 0, 0 );
+	var distortionScale = options.distortionScale !== undefined ? options.distortionScale : 20.0;
+	var side = options.side !== undefined ? options.side : FrontSide;
+	var fog = options.fog !== undefined ? options.fog : false;
+
+	//
+
+	var mirrorPlane = new Plane();
+	var normal = new Vector3();
+	var mirrorWorldPosition = new Vector3();
+	var cameraWorldPosition = new Vector3();
+	var rotationMatrix = new Matrix4();
+	var lookAtPosition = new Vector3( 0, 0, - 1 );
+	var clipPlane = new Vector4();
+
+	var view = new Vector3();
+	var target = new Vector3();
+	var q = new Vector4();
+
+	var textureMatrix = new Matrix4();
+
+	var mirrorCamera = new PerspectiveCamera();
+
+	var parameters = {
+		minFilter: LinearFilter,
+		magFilter: LinearFilter,
+		format: RGBFormat,
+		stencilBuffer: false
+	};
+
+	var renderTarget = new WebGLRenderTarget( textureWidth, textureHeight, parameters );
+
+	if ( ! _Math.isPowerOfTwo( textureWidth ) || ! _Math.isPowerOfTwo( textureHeight ) ) {
+
+		renderTarget.texture.generateMipmaps = false;
+
+	}
+
+	var mirrorShader = {
+
+		uniforms: UniformsUtils.merge( [
+			UniformsLib[ 'fog' ],
+			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 Matrix4() },
+				"sunColor": { value: new Color( 0x7F7F7F ) },
+				"sunDirection": { value: new Vector3( 0.70707, 0.70707, 0 ) },
+				"eye": { value: new Vector3() },
+				"waterColor": { value: new Color( 0x555555 ) }
+			}
+		] ),
+
+		vertexShader: [
+			'uniform mat4 textureMatrix;',
+			'uniform float time;',
+
+			'varying vec4 mirrorCoord;',
+			'varying vec4 worldPosition;',
+
+			ShaderChunk[ 'fog_pars_vertex' ],
+			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;',
+
+			ShaderChunk[ 'fog_vertex' ],
+			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;',
+			'}',
+
+			ShaderChunk[ 'common' ],
+			ShaderChunk[ 'packing' ],
+			ShaderChunk[ 'bsdfs' ],
+			ShaderChunk[ 'fog_pars_fragment' ],
+			ShaderChunk[ 'lights_pars_begin' ],
+			ShaderChunk[ 'shadowmap_pars_fragment' ],
+			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.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 );',
+
+			ShaderChunk[ 'tonemapping_fragment' ],
+			ShaderChunk[ 'fog_fragment' ],
+
+			'}'
+		].join( '\n' )
+
+	};
+
+	var material = new ShaderMaterial( {
+		fragmentShader: mirrorShader.fragmentShader,
+		vertexShader: mirrorShader.vertexShader,
+		uniforms: 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.setRenderTarget( renderTarget );
+		renderer.clear();
+		renderer.render( scene, mirrorCamera );
+
+		scope.visible = true;
+
+		renderer.vr.enabled = currentVrEnabled;
+		renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
+
+		renderer.setRenderTarget( currentRenderTarget );
+
+	};
+
+};
+
+Water.prototype = Object.create( Mesh.prototype );
+Water.prototype.constructor = Water;
+
+export { Water };

+ 27 - 0
examples/jsm/objects/Water2.d.ts

@@ -0,0 +1,27 @@
+import {
+  BufferGeometry,
+  Color,
+  Geometry,
+  Mesh,
+  Texture,
+  Vector2
+} from '../../../src/Three';
+
+export interface WaterOptions {
+  color?: Color | string | number;
+  textureWidth?: number;
+  textureHeight?: number;
+  clipBias?: number;
+  flowDirection?: Vector2;
+  flowSpeed?: number;
+  reflectivity?: number;
+  scale?: number;
+  shader?: object;
+  flowMap?: Texture;
+  normalMap0?: Texture;
+  normalMap1?: Texture;
+}
+
+export class Water extends Mesh {
+  constructor(geometry: Geometry | BufferGeometry, options: WaterOptions);
+}

+ 356 - 0
examples/jsm/objects/Water2.js

@@ -0,0 +1,356 @@
+/**
+ * @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
+ *
+ */
+
+import {
+	Clock,
+	Color,
+	Matrix4,
+	Mesh,
+	RepeatWrapping,
+	ShaderMaterial,
+	TextureLoader,
+	UniformsLib,
+	UniformsUtils,
+	Vector2,
+	Vector4
+} from "../../../build/three.module.js";
+import { Reflector } from "../objects/Reflector.js";
+import { Refractor } from "../objects/Refractor.js";
+
+var Water = function ( geometry, options ) {
+
+	Mesh.call( this, geometry );
+
+	this.type = 'Water';
+
+	var scope = this;
+
+	options = options || {};
+
+	var color = ( options.color !== undefined ) ? new Color( options.color ) : new Color( 0xFFFFFF );
+	var textureWidth = options.textureWidth || 512;
+	var textureHeight = options.textureHeight || 512;
+	var clipBias = options.clipBias || 0;
+	var flowDirection = options.flowDirection || new Vector2( 1, 0 );
+	var flowSpeed = options.flowSpeed || 0.03;
+	var reflectivity = options.reflectivity || 0.02;
+	var scale = options.scale || 1;
+	var shader = options.shader || Water.WaterShader;
+
+	var textureLoader = new 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 Matrix4();
+	var clock = new Clock();
+
+	// internal components
+
+	if ( Reflector === undefined ) {
+
+		console.error( 'THREE.Water: Required component Reflector not found.' );
+		return;
+
+	}
+
+	if ( Refractor === undefined ) {
+
+		console.error( 'THREE.Water: Required component Refractor not found.' );
+		return;
+
+	}
+
+	var reflector = new Reflector( geometry, {
+		textureWidth: textureWidth,
+		textureHeight: textureHeight,
+		clipBias: clipBias
+	} );
+
+	var refractor = new Refractor( geometry, {
+		textureWidth: textureWidth,
+		textureHeight: textureHeight,
+		clipBias: clipBias
+	} );
+
+	reflector.matrixAutoUpdate = false;
+	refractor.matrixAutoUpdate = false;
+
+	// material
+
+	this.material = new ShaderMaterial( {
+		uniforms: UniformsUtils.merge( [
+			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 = RepeatWrapping;
+	normalMap1.wrapS = normalMap1.wrapT = 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;
+
+	};
+
+};
+
+Water.prototype = Object.create( Mesh.prototype );
+Water.prototype.constructor = Water;
+
+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 Vector4()
+		}
+
+	},
+
+	vertexShader: [
+
+		'#include <fog_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 <fog_vertex>',
+
+		'}'
+
+	].join( '\n' ),
+
+	fragmentShader: [
+
+		'#include <common>',
+		'#include <fog_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() {',
+
+		'	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>',
+
+		'}'
+
+	].join( '\n' )
+};
+
+export { Water };

+ 2 - 0
utils/modularize.js

@@ -68,6 +68,8 @@ var files = [
 	{ path: 'objects/Refractor.js', dependencies: [], ignoreList: [] },
 	{ path: 'objects/Refractor.js', dependencies: [], ignoreList: [] },
 	{ path: 'objects/ShadowMesh.js', dependencies: [], ignoreList: [] },
 	{ path: 'objects/ShadowMesh.js', dependencies: [], ignoreList: [] },
 	{ path: 'objects/Sky.js', dependencies: [], ignoreList: [] },
 	{ path: 'objects/Sky.js', dependencies: [], ignoreList: [] },
+	{ path: 'objects/Water.js', dependencies: [], ignoreList: [] },
+	{ path: 'objects/Water2.js', dependencies: [ { name: 'Reflector', path: 'objects/Reflector.js' }, { name: 'Refractor', path: 'objects/Refractor.js' } ], ignoreList: [] },
 
 
 	{ path: 'pmrem/PMREMCubeUVPacker.js', dependencies: [], ignoreList: [] },
 	{ path: 'pmrem/PMREMCubeUVPacker.js', dependencies: [], ignoreList: [] },
 	{ path: 'pmrem/PMREMGenerator.js', dependencies: [], ignoreList: [] },
 	{ path: 'pmrem/PMREMGenerator.js', dependencies: [], ignoreList: [] },