Browse Source

Merge pull request #12341 from Mugen87/water

Example: New Water Shader
Mr.doob 7 years ago
parent
commit
4d4d1361a0

+ 3 - 1
examples/files.js

@@ -256,7 +256,9 @@ var files = {
 		"webgl_test_memory2",
 		"webgl_tonemapping",
 		"webgl_trails",
-		"webgl_video_panorama_equirectangular"
+		"webgl_video_panorama_equirectangular",
+		"webgl_water",
+		"webgl_water_flowmap"
 	],
 	"webgl / advanced": [
 		"webgl_buffergeometry",

+ 8 - 2
examples/js/objects/Mirror.js

@@ -63,9 +63,9 @@ THREE.Mirror = function ( width, height, options ) {
 	material.uniforms.color.value = color;
 	material.uniforms.textureMatrix.value = textureMatrix;
 
-	scope.material = material;
+	this.material = material;
 
-	scope.onBeforeRender = function ( renderer, scene, camera ) {
+	this.onBeforeRender = function ( renderer, scene, camera ) {
 
 		if ( 'recursion' in camera.userData ) {
 
@@ -190,6 +190,12 @@ THREE.Mirror = function ( width, height, options ) {
 
 	};
 
+	this.getRenderTarget = function () {
+
+		return renderTarget;
+
+	};
+
 };
 
 THREE.Mirror.prototype = Object.create( THREE.Mesh.prototype );

+ 6 - 0
examples/js/objects/Refractor.js

@@ -253,6 +253,12 @@ THREE.Refractor = function ( width, height, options ) {
 
 	};
 
+	this.getRenderTarget = function () {
+
+		return renderTarget;
+
+	};
+
 };
 
 THREE.Refractor.prototype = Object.create( THREE.Mesh.prototype );

+ 337 - 0
examples/js/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.Mirror === undefined ) {
+
+		console.error( 'THREE.Water: Required component THREE.Mirror not found.' );
+		return;
+
+	}
+
+	if ( THREE.Refractor === undefined ) {
+
+		console.error( 'THREE.Water: Required component THREE.Refractor not found.' );
+		return;
+
+	}
+
+	var mirror = new THREE.Mirror( width, height, {
+		textureWidth: textureWidth,
+		textureHeight: textureHeight,
+		clipBias: clipBias
+	} );
+
+	var refractor = new THREE.Refractor( width, height, {
+		textureWidth: textureWidth,
+		textureHeight: textureHeight,
+		clipBias: clipBias
+	} );
+
+	mirror.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 = mirror.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;
+
+		mirror.matrixWorld.copy( scope.matrixWorld );
+		refractor.matrixWorld.copy( scope.matrixWorld );
+
+		mirror.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 <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 <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, 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 <tonemapping_fragment>',
+		'	#include <encodings_fragment>',
+		'	#include <fog_fragment>',
+
+		'}'
+
+	].join( '\n' )
+};

BIN
examples/textures/floors/FloorsCheckerboard_S_Diffuse.jpg


BIN
examples/textures/floors/FloorsCheckerboard_S_Normal.jpg


BIN
examples/textures/water/Water_1_M_Flow.jpg


BIN
examples/textures/water/Water_1_M_Normal.jpg


BIN
examples/textures/water/Water_2_M_Normal.jpg


+ 233 - 0
examples/webgl_water.html

@@ -0,0 +1,233 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js - water</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+			body {
+				background:#777;
+				padding:0;
+				margin:0;
+				font-weight: bold;
+				overflow:hidden;
+			}
+
+			#info {
+				position: absolute;
+				top: 0px;
+				width: 100%;
+				color: #ffffff;
+				padding: 5px;
+				font-family:Monospace;
+				font-size:13px;
+				text-align:center;
+			}
+
+			a {
+				color: #ffffff;
+			}
+		</style>
+
+		<script src="../build/three.js"></script>
+		<script src="js/controls/OrbitControls.js"></script>
+		<script src="js/objects/Mirror.js"></script>
+		<script src="js/objects/Refractor.js"></script>
+		<script src="js/objects/Water2.js"></script>
+		<script src="js/Detector.js"></script>
+		<script src="js/libs/dat.gui.min.js"></script>
+
+	</head>
+	<body>
+
+		<div id="container"></div>
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener noreferrer">three.js</a> - water
+		</div>
+
+		<script>
+
+		if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
+
+		var scene, camera, clock, renderer, water;
+
+		var torusKnot;
+
+		var params = {
+			color: '#ffffff',
+			scale: 4,
+			flowX: 1,
+			flowY: 1
+		};
+
+		init();
+		animate();
+
+		function init() {
+
+			// scene
+
+			scene = new THREE.Scene();
+
+			// camera
+
+			camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 1000 );
+			camera.position.set( - 15, 7, 15 );
+			camera.lookAt( scene.position );
+
+			// clock
+
+			clock = new THREE.Clock();
+
+			// mesh
+
+			var torusKnotGeometry = new THREE.TorusKnotBufferGeometry( 3, 1, 256, 32 );
+			var torusKnotMaterial = new THREE.MeshNormalMaterial();
+
+			torusKnot = new THREE.Mesh( torusKnotGeometry, torusKnotMaterial );
+			torusKnot.position.y = 4;
+			torusKnot.scale.set( 0.5, 0.5, 0.5 );
+			scene.add( torusKnot );
+
+			// ground
+
+			var groundGeometry = new THREE.PlaneBufferGeometry( 20, 20 );
+			var groundMaterial = new THREE.MeshStandardMaterial( { roughness: 0.8, metalness: 0.4 } );
+			var ground = new THREE.Mesh( groundGeometry, groundMaterial );
+			ground.rotation.x = Math.PI * - 0.5;
+			scene.add( ground );
+
+			var textureLoader = new THREE.TextureLoader();
+			textureLoader.load( 'textures/hardwood2_diffuse.jpg', function( map ) {
+				map.wrapS = THREE.RepeatWrapping;
+				map.wrapT = THREE.RepeatWrapping;
+				map.anisotropy = 16;
+				map.repeat.set( 4, 4 );
+				groundMaterial.map = map;
+				groundMaterial.needsUpdate = true;
+			} );
+
+			// water
+
+			water = new THREE.Water( 20, 20, {
+				color: params.color,
+				scale: params.scale,
+				flowDirection: new THREE.Vector2( params.flowX, params.flowY ),
+				textureWidth: 1024,
+				textureHeight: 1024
+			} );
+
+			water.position.y = 1;
+			water.rotation.x = Math.PI * - 0.5;
+			scene.add( water );
+
+			// skybox
+
+			var cubeTextureLoader = new THREE.CubeTextureLoader();
+			cubeTextureLoader.setPath( 'textures/cube/skybox/' );
+
+			var cubeTexture = cubeTextureLoader.load( [
+				'px.jpg', 'nx.jpg',
+				'py.jpg', 'ny.jpg',
+				'pz.jpg', 'nz.jpg',
+			] );
+
+			var cubeShader = THREE.ShaderLib[ 'cube' ];
+			cubeShader.uniforms[ 'tCube' ].value = cubeTexture;
+
+			var skyBoxMaterial = new THREE.ShaderMaterial( {
+				fragmentShader: cubeShader.fragmentShader,
+				vertexShader: cubeShader.vertexShader,
+				uniforms: cubeShader.uniforms,
+				side: THREE.BackSide
+			} );
+
+			var skyBox = new THREE.Mesh( new THREE.BoxBufferGeometry( 1000, 1000, 1000 ), skyBoxMaterial );
+			scene.add( skyBox );
+
+			// light
+
+			var ambientLight = new THREE.AmbientLight( 0xcccccc, 0.4 );
+			scene.add( ambientLight );
+
+			var directionalLight = new THREE.DirectionalLight( 0xffffff, 0.6 );
+			directionalLight.position.set( - 1, 1, 1 );
+			scene.add( directionalLight );
+
+			// renderer
+
+			renderer = new THREE.WebGLRenderer( { antialias: true } );
+			renderer.setSize( window.innerWidth, window.innerHeight );
+			renderer.setPixelRatio( window.devicePixelRatio );
+			document.body.appendChild( renderer.domElement );
+
+			// dat.gui
+
+			var gui = new dat.GUI();
+
+			gui.addColor( params, 'color' ).onChange( function( value ) {
+
+				water.material.uniforms.color.value.set( value );
+
+			} );
+			gui.add( params, 'scale', 1, 10 ).onChange( function( value ) {
+
+				water.material.uniforms.config.value.w = value;
+
+			} );
+			gui.add( params, 'flowX', - 1, 1 ).step( 0.01 ).onChange( function( value ) {
+
+				water.material.uniforms.flowDirection.value.x = value;
+				water.material.uniforms.flowDirection.value.normalize();
+
+			} );
+			gui.add( params, 'flowY', - 1, 1 ).step( 0.01 ).onChange( function( value ) {
+
+				water.material.uniforms.flowDirection.value.y = value;
+				water.material.uniforms.flowDirection.value.normalize();
+
+			} );
+
+			gui.open();
+
+			//
+
+			controls = new THREE.OrbitControls( camera, renderer.domElement );
+
+			//
+
+			window.addEventListener( 'resize', onResize, false );
+
+		}
+
+		function onResize() {
+
+			camera.aspect = window.innerWidth / window.innerHeight;
+			camera.updateProjectionMatrix();
+			renderer.setSize( window.innerWidth, window.innerHeight );
+
+		}
+
+		function animate() {
+
+			requestAnimationFrame( animate );
+
+			render();
+
+		}
+
+		function render() {
+
+			var delta = clock.getDelta();
+
+			torusKnot.rotation.x += delta;
+			torusKnot.rotation.y += delta * 0.5;
+
+			renderer.render( scene, camera );
+
+		}
+
+	</script>
+
+</body>
+</html>

+ 160 - 0
examples/webgl_water_flowmap.html

@@ -0,0 +1,160 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js - water flow map</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+			body {
+				background:#777;
+				padding:0;
+				margin:0;
+				font-weight: bold;
+				overflow:hidden;
+			}
+
+			#info {
+				position: absolute;
+				top: 0px;
+				width: 100%;
+				color: #ffffff;
+				padding: 5px;
+				font-family:Monospace;
+				font-size:13px;
+				text-align:center;
+			}
+
+			a {
+				color: #ffffff;
+			}
+		</style>
+
+		<script src="../build/three.js"></script>
+		<script src="js/controls/OrbitControls.js"></script>
+		<script src="js/objects/Mirror.js"></script>
+		<script src="js/objects/Refractor.js"></script>
+		<script src="js/objects/Water2.js"></script>
+		<script src="js/Detector.js"></script>
+		<script src="js/libs/dat.gui.min.js"></script>
+
+	</head>
+	<body>
+
+		<div id="container"></div>
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener noreferrer">three.js</a> - water flow map
+		</div>
+
+		<script>
+
+		if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
+
+		var scene, camera, renderer, water;
+
+		init();
+		animate();
+
+		function init() {
+
+			// scene
+
+			scene = new THREE.Scene();
+
+			// camera
+
+			camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 1000 );
+			camera.position.set( 0, 25, 0 );
+			camera.lookAt( scene.position );
+
+			// ground
+
+			var groundGeometry = new THREE.PlaneBufferGeometry( 20, 20, 10, 10 );
+			var groundMaterial = new THREE.MeshBasicMaterial( { color: 0xcccccc } );
+			var ground = new THREE.Mesh( groundGeometry, groundMaterial );
+			ground.rotation.x = Math.PI * - 0.5;
+			scene.add( ground );
+
+			var textureLoader = new THREE.TextureLoader();
+			textureLoader.load( 'textures/floors/FloorsCheckerboard_S_Diffuse.jpg', function( map ) {
+				map.wrapS = THREE.RepeatWrapping;
+				map.wrapT = THREE.RepeatWrapping;
+				map.anisotropy = 16;
+				map.repeat.set( 4, 4 );
+				groundMaterial.map = map;
+				groundMaterial.needsUpdate = true;
+			} );
+
+			// water
+
+			var flowMap = textureLoader.load( 'textures/water/Water_1_M_Flow.jpg' );
+
+			water = new THREE.Water( 20, 20, {
+				scale: 2,
+				textureWidth: 1024,
+				textureHeight: 1024,
+				flowMap: flowMap
+			} );
+
+			water.position.y = 1;
+			water.rotation.x = Math.PI * - 0.5;
+			scene.add( water );
+
+			// flow map helper
+
+			var helperGeometry = new THREE.PlaneBufferGeometry( 20, 20 );
+			var helperMaterial = new THREE.MeshBasicMaterial( { map: flowMap } );
+			var helper = new THREE.Mesh( helperGeometry, helperMaterial );
+			helper.position.y = 1.01;
+			helper.rotation.x = Math.PI * - 0.5;
+			helper.visible = false;
+			scene.add( helper );
+
+			// renderer
+
+			renderer = new THREE.WebGLRenderer( { antialias: true } );
+			renderer.setSize( window.innerWidth, window.innerHeight );
+			renderer.setPixelRatio( window.devicePixelRatio );
+			document.body.appendChild( renderer.domElement );
+
+			//
+
+			var gui = new dat.GUI();
+			gui.add( helper, 'visible' ).name( 'Show Flow Map');
+			gui.open();
+
+			//
+
+			controls = new THREE.OrbitControls( camera, renderer.domElement );
+
+			//
+
+			window.addEventListener( 'resize', onResize, false );
+
+		}
+
+		function onResize() {
+
+			camera.aspect = window.innerWidth / window.innerHeight;
+			camera.updateProjectionMatrix();
+			renderer.setSize( window.innerWidth, window.innerHeight );
+
+		}
+
+		function animate() {
+
+			requestAnimationFrame( animate );
+
+			render();
+
+		}
+
+		function render() {
+
+			renderer.render( scene, camera );
+
+		}
+
+	</script>
+
+</body>
+</html>