Selaa lähdekoodia

Water: Initial Commit

Mugen87 7 vuotta sitten
vanhempi
commit
e9d3b67259

+ 2 - 1
examples/files.js

@@ -256,7 +256,8 @@ var files = {
 		"webgl_test_memory2",
 		"webgl_tonemapping",
 		"webgl_trails",
-		"webgl_video_panorama_equirectangular"
+		"webgl_video_panorama_equirectangular",
+		"webgl_water"
 	],
 	"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 );

+ 310 - 0
examples/js/objects/Water2.js

@@ -0,0 +1,310 @@
+/**
+ * @author Mugen87 / https://github.com/Mugen87
+ *
+ */
+
+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 ) !== undefined ? new THREE.Color( options.color ) : new THREE.Color( 0x7F7F7F );
+	var textureWidth = options.textureWidth || 512;
+	var textureHeight = options.textureHeight || 512;
+	var clipBias = options.clipBias || 0;
+	var speed = options.speed || 0.03; // water flow speed
+	var reflectivity = options.reflectivity || 0.02; // water reflectivity
+	var segments = options.segments || 4; // the amount of segments of the water geometry
+	var shader = options.shader || THREE.Water.WaterShader;
+
+	var textureLoader = new THREE.TextureLoader();
+
+	var flowMap = options.flowMap || textureLoader.load( 'textures/water/Water_1_M_Flow.jpg' );
+	var noiseMap = options.noiseMap || textureLoader.load( 'textures/water/Water_1_M_Noise.jpg' );
+	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
+
+	var mirror = new THREE.Mirror( width, height, {
+		color: color,
+		textureWidth: textureWidth,
+		textureHeight: textureHeight,
+		clipBias: clipBias
+	} );
+
+	var refractor = new THREE.Refractor( width, height, {
+		color: color,
+		textureWidth: textureWidth,
+		textureHeight: textureHeight,
+		clipBias: clipBias
+	} );
+
+	mirror.matrixAutoUpdate = false;
+	refractor.matrixAutoUpdate = false;
+
+	// material
+
+	this.material = new THREE.ShaderMaterial( {
+		uniforms: THREE.UniformsUtils.clone( shader.uniforms ),
+		vertexShader: shader.vertexShader,
+		fragmentShader: shader.fragmentShader,
+		transparent: true
+	} );
+
+	// 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.tFlowMap.value = flowMap;
+	this.material.uniforms.tNoiseMap.value = noiseMap;
+	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 = segments; // segments
+
+	// 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( delta ) {
+
+		var config = scope.material.uniforms.config;
+
+		config.value.x += speed * delta; // flowMapOffset0
+		config.value.y += speed * delta; // flowMapOffset1
+
+		// reset properties if necessary
+
+		if ( config.value.x >= cycle ) {
+
+			config.value.x = 0;
+
+			// avoid 'reset' effect when both offset are set to zero
+
+			if ( config.value.y >= cycle ) {
+
+				config.value.y = halfCycle;
+
+				return;
+
+			}
+
+		}
+
+		if ( config.value.y >= cycle ) {
+
+			config.value.y = 0;
+
+		}
+
+	}
+
+	//
+
+	this.onBeforeRender = function ( renderer, scene, camera ) {
+
+		var delta = clock.getDelta();
+
+		updateTextureMatrix( camera );
+		updateFlow( delta );
+
+		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
+		},
+
+		'tFlowMap': {
+			type: 't',
+			value: null
+		},
+
+		'tNoiseMap': {
+			type: 't',
+			value: null
+		},
+
+		'tNormalMap0': {
+			type: 't',
+			value: null
+		},
+
+		'tNormalMap1': {
+			type: 't',
+			value: null
+		},
+
+		'config': {
+			type: 'v4',
+			value: new THREE.Vector4()
+		},
+
+		'textureMatrix': {
+			type: 'm4',
+			value: null
+		}
+
+	},
+
+	vertexShader: [
+
+		'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;',
+
+		'	gl_Position = projectionMatrix * viewMatrix * worldPosition;',
+
+		'}'
+
+	].join( '\n' ),
+
+	fragmentShader: [
+
+		'uniform sampler2D tReflectionMap;',
+		'uniform sampler2D tRefractionMap;',
+		'uniform sampler2D tFlowMap;',
+		'uniform sampler2D tNoiseMap;',
+		'uniform sampler2D tNormalMap0;',
+		'uniform sampler2D tNormalMap1;',
+
+		'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 segments = config.w;',
+
+		'	vec3 toEye = normalize( vToEye );',
+
+		// sample flow map
+		'	vec2 flow = texture2D( tFlowMap, vUv ).rg * 2.0 - 1.0;',
+		'	flow.r *= -1.0;',
+
+		// sample noise map
+		'	float cycleOffset = texture2D( tNoiseMap, vUv ).r;',
+
+		// calculate current phases
+		'	float phase0 = cycleOffset * 0.5 + flowMapOffset0;',
+		'	float phase1 = cycleOffset * 0.5 + flowMapOffset1;',
+
+		// sample normal maps
+		'	vec4 normalColor0 = texture2D( tNormalMap0, ( vUv * segments ) + flow * phase0 );',
+		'	vec4 normalColor1 = texture2D( tNormalMap1, ( vUv * segments ) + flow * phase1 );',
+
+		// 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 ) );',
+
+		// fresnel effect
+		'	float theta = max( dot( toEye, normal ), 0.0 );',
+		'	float reflectance = reflectivity + ( 1.0 - reflectivity ) * pow( ( 1.0 - theta ), 5.0 );',
+
+		// sample textures
+		'	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. then add lighting
+		'	gl_FragColor = vec4( color, 1.0 ) * mix( refractColor, reflectColor, reflectance );',
+
+		'}'
+
+	].join( '\n' )
+};

BIN
examples/textures/water/Water_1_M_Flow.jpg


BIN
examples/textures/water/Water_1_M_Noise.jpg


BIN
examples/textures/water/Water_1_M_Normal.jpg


BIN
examples/textures/water/Water_2_M_Normal.jpg


+ 192 - 0
examples/webgl_water.html

@@ -0,0 +1,192 @@
+<!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>
+
+	</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;
+
+		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( 10, 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 );
+
+			var textureLoader = new THREE.TextureLoader();
+
+			var groundGeometry = new THREE.PlaneBufferGeometry( 20, 20 );
+			var groundMaterial = new THREE.MeshStandardMaterial( { roughness: 0.7 } );
+			var ground = new THREE.Mesh( groundGeometry, groundMaterial );
+			ground.rotation.x = Math.PI * - 0.5;
+			scene.add( ground );
+
+			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, {
+				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 );
+
+			//
+
+			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>