Selaa lähdekoodia

Merge pull request #12167 from Astrak/watershader-improvements

Watershader improvements
Mr.doob 7 vuotta sitten
vanhempi
commit
70d97a7773
2 muutettua tiedostoa jossa 177 lisäystä ja 142 poistoa
  1. 74 68
      examples/js/WaterShader.js
  2. 103 74
      examples/webgl_shaders_ocean.html

+ 74 - 68
examples/js/WaterShader.js

@@ -7,10 +7,9 @@
  * @author Jonas Wagner / http://29a.ch/ && http://29a.ch/slides/2012/webglwater/ : Water shader explanations in WebGL
  */
 
-THREE.Water = function ( renderer, camera, scene, options ) {
+THREE.Water = function ( options ) {
 
 	THREE.Object3D.call( this );
-	this.name = 'water_' + this.id;
 
 	function optionalParameter( value, defaultValue ) {
 
@@ -20,8 +19,6 @@ THREE.Water = function ( renderer, camera, scene, options ) {
 
 	options = options || {};
 
-	this.matrixNeedsUpdate = true;
-
 	var width = optionalParameter( options.textureWidth, 512 );
 	var height = optionalParameter( options.textureHeight, 512 );
 	this.clipBias = optionalParameter( options.clipBias, 0.0 );
@@ -36,8 +33,6 @@ THREE.Water = function ( renderer, camera, scene, options ) {
 	this.side = optionalParameter( options.side, THREE.FrontSide );
 	this.fog = optionalParameter( options.fog, false );
 
-	this.renderer = renderer;
-	this.scene = scene;
 	this.mirrorPlane = new THREE.Plane();
 	this.normal = new THREE.Vector3( 0, 0, 1 );
 	this.mirrorWorldPosition = new THREE.Vector3();
@@ -46,33 +41,21 @@ THREE.Water = function ( renderer, camera, scene, options ) {
 	this.lookAtPosition = new THREE.Vector3( 0, 0, - 1 );
 	this.clipPlane = new THREE.Vector4();
 
-	if ( camera instanceof THREE.PerspectiveCamera ) {
-
-		this.camera = camera;
-
-	} else {
-
-		this.camera = new THREE.PerspectiveCamera();
-		console.log( this.name + ': camera is not a Perspective Camera!' );
-
-	}
-
 	this.textureMatrix = new THREE.Matrix4();
 
-	this.mirrorCamera = this.camera.clone();
-
 	this.renderTarget = new THREE.WebGLRenderTarget( width, height );
-	this.renderTarget2 = new THREE.WebGLRenderTarget( width, height );
 
 	var mirrorShader = {
 
 		uniforms: THREE.UniformsUtils.merge( [
 			THREE.UniformsLib[ 'fog' ],
+			THREE.UniformsLib[ 'lights' ],
 			{
 				normalSampler: { value: null },
 				mirrorSampler: { value: null },
 				alpha: { value: 1.0 },
 				time: { value: 0.0 },
+				size: { value: 1.0 },
 				distortionScale: { value: 20.0 },
 				noiseScale: { value: 1.0 },
 				textureMatrix: { value: new THREE.Matrix4() },
@@ -88,28 +71,29 @@ THREE.Water = function ( renderer, camera, scene, options ) {
 			'uniform float time;',
 
 			'varying vec4 mirrorCoord;',
-			'varying vec3 worldPosition;',
+			'varying vec4 worldPosition;',
 
 			THREE.ShaderChunk[ 'fog_pars_vertex' ],
+			THREE.ShaderChunk[ 'shadowmap_pars_vertex' ],
 
 			'void main() {',
 			'	mirrorCoord = modelMatrix * vec4( position, 1.0 );',
-			'	worldPosition = mirrorCoord.xyz;',
+			'	worldPosition = mirrorCoord.xyzw;',
 			'	mirrorCoord = textureMatrix * mirrorCoord;',
 			'	vec4 mvPosition =  modelViewMatrix * vec4( position, 1.0 );',
 			'	gl_Position = projectionMatrix * mvPosition;',
 
 			THREE.ShaderChunk[ 'fog_vertex' ],
+			THREE.ShaderChunk[ 'shadowmap_vertex' ],
 
 			'}'
 		].join( '\n' ),
 
 		fragmentShader: [
-			'precision highp float;',
-
 			'uniform sampler2D mirrorSampler;',
 			'uniform float alpha;',
 			'uniform float time;',
+			'uniform float size;',
 			'uniform float distortionScale;',
 			'uniform sampler2D normalSampler;',
 			'uniform vec3 sunColor;',
@@ -118,7 +102,7 @@ THREE.Water = function ( renderer, camera, scene, options ) {
 			'uniform vec3 waterColor;',
 
 			'varying vec4 mirrorCoord;',
-			'varying vec3 worldPosition;',
+			'varying vec4 worldPosition;',
 
 			'vec4 getNoise( vec2 uv ) {',
 			'	vec2 uv0 = ( uv / 103.0 ) + vec2(time / 17.0, time / 29.0);',
@@ -140,16 +124,21 @@ THREE.Water = function ( renderer, camera, scene, options ) {
 			'}',
 
 			THREE.ShaderChunk[ 'common' ],
+			THREE.ShaderChunk[ 'packing' ],
+			THREE.ShaderChunk[ 'bsdfs' ],
 			THREE.ShaderChunk[ 'fog_pars_fragment' ],
+			THREE.ShaderChunk[ 'lights_pars' ],
+			THREE.ShaderChunk[ 'shadowmap_pars_fragment' ],
+			THREE.ShaderChunk[ 'shadowmask_pars_fragment' ],
 
 			'void main() {',
-			'	vec4 noise = getNoise( worldPosition.xz );',
+			'	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;',
+			'	vec3 worldToEye = eye-worldPosition.xyz;',
 			'	vec3 eyeDirection = normalize( worldToEye );',
 			'	sunLight( surfaceNormal, eyeDirection, 100.0, 2.0, 0.5, diffuseLight, specularLight );',
 
@@ -162,10 +151,11 @@ THREE.Water = function ( renderer, camera, scene, options ) {
 			'	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, ( vec3( 0.1 ) + reflectionSample * 0.9 + reflectionSample * specularLight ), reflectance );',
+			'	vec3 albedo = mix( ( sunColor * diffuseLight * 0.3 + scatter ) * getShadowMask(), ( vec3( 0.1 ) + reflectionSample * 0.9 + reflectionSample * specularLight ), reflectance);',
 			'	vec3 outgoingLight = albedo;',
 			'	gl_FragColor = vec4( outgoingLight, alpha );',
 
+			THREE.ShaderChunk[ 'tonemapping_fragment' ],
 			THREE.ShaderChunk[ 'fog_fragment' ],
 
 			'}'
@@ -180,11 +170,12 @@ THREE.Water = function ( renderer, camera, scene, options ) {
 		vertexShader: mirrorShader.vertexShader,
 		uniforms: mirrorUniforms,
 		transparent: true,
+		lights: true,
 		side: this.side,
 		fog: this.fog
 	} );
 
-	this.material.uniforms.mirrorSampler.value = this.renderTarget.texture;
+	this.material.uniforms.mirrorSampler.value = this.renderTarget.texture; 
 	this.material.uniforms.textureMatrix.value = this.textureMatrix;
 	this.material.uniforms.alpha.value = this.alpha;
 	this.material.uniforms.time.value = this.time;
@@ -193,6 +184,7 @@ THREE.Water = function ( renderer, camera, scene, options ) {
 	this.material.uniforms.waterColor.value = this.waterColor;
 	this.material.uniforms.sunDirection.value = this.sunDirection;
 	this.material.uniforms.distortionScale.value = this.distortionScale;
+	this.material.uniforms.size.value = this.size;
 
 	this.material.uniforms.eye.value = this.eye;
 
@@ -200,54 +192,24 @@ THREE.Water = function ( renderer, camera, scene, options ) {
 
 		this.renderTarget.texture.generateMipmaps = false;
 		this.renderTarget.texture.minFilter = THREE.LinearFilter;
-		this.renderTarget2.texture.generateMipmaps = false;
-		this.renderTarget2.texture.minFilter = THREE.LinearFilter;
 
 	}
 
-	this.updateTextureMatrix();
-	this.render();
-
 };
 
 THREE.Water.prototype = Object.create( THREE.Object3D.prototype );
 THREE.Water.prototype.constructor = THREE.Water;
 
-THREE.Water.prototype.render = function () {
-
-	if ( this.matrixNeedsUpdate ) this.updateTextureMatrix();
-
-	this.matrixNeedsUpdate = true;
-
-	// Render the mirrored view of the current scene into the target texture
-	var scene = this;
-
-	while ( scene.parent !== null ) {
-
-		scene = scene.parent;
-
-	}
-
-	if ( scene !== undefined && scene instanceof THREE.Scene ) {
-
-		this.material.visible = false;
-
-		this.renderer.render( scene, this.mirrorCamera, this.renderTarget, true );
-
-		this.material.visible = true;
-
-	}
-
-};
 
+THREE.Water.prototype.updateTextureMatrix = function ( camera ) {
 
-THREE.Water.prototype.updateTextureMatrix = function () {
+	this.mirrorCamera = this.mirrorCamera || camera.clone();
 
-	this.updateMatrixWorld();
-	this.camera.updateMatrixWorld();
+	this.updateMatrixWorld(); 
+	camera.updateMatrixWorld(); 
 
 	this.mirrorWorldPosition.setFromMatrixPosition( this.matrixWorld );
-	this.cameraWorldPosition.setFromMatrixPosition( this.camera.matrixWorld );
+	this.cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld );
 
 	this.rotationMatrix.extractRotation( this.matrixWorld );
 
@@ -258,9 +220,9 @@ THREE.Water.prototype.updateTextureMatrix = function () {
 	view.reflect( this.normal ).negate();
 	view.add( this.mirrorWorldPosition );
 
-	this.rotationMatrix.extractRotation( this.camera.matrixWorld );
+	this.rotationMatrix.extractRotation( camera.matrixWorld );
 
-	this.lookAtPosition.set( 0, 0, - 1 );
+	this.lookAtPosition.set( 0, 0, -1 );
 	this.lookAtPosition.applyMatrix4( this.rotationMatrix );
 	this.lookAtPosition.add( this.cameraWorldPosition );
 
@@ -275,7 +237,7 @@ THREE.Water.prototype.updateTextureMatrix = function () {
 	this.mirrorCamera.position.copy( view );
 	this.mirrorCamera.up = this.up;
 	this.mirrorCamera.lookAt( target );
-	this.mirrorCamera.aspect = this.camera.aspect;
+	this.mirrorCamera.aspect = camera.aspect;
 
 	this.mirrorCamera.updateProjectionMatrix();
 	this.mirrorCamera.updateMatrixWorld();
@@ -315,8 +277,52 @@ THREE.Water.prototype.updateTextureMatrix = function () {
 	projectionMatrix.elements[ 14 ] = c.w;
 
 	var worldCoordinates = new THREE.Vector3();
-	worldCoordinates.setFromMatrixPosition( this.camera.matrixWorld );
+	worldCoordinates.setFromMatrixPosition( camera.matrixWorld );
 	this.eye = worldCoordinates;
 	this.material.uniforms.eye.value = this.eye;
 
 };
+
+THREE.WaterMesh = function ( width, height, options ) {
+
+	var waterMaterial = new THREE.Water( options );
+
+	THREE.Mesh.call( this, new THREE.PlaneBufferGeometry( width, height ), waterMaterial.material );
+
+	var scope = this;
+
+	scope.name = 'water_' + scope.id;
+
+	scope.water = waterMaterial;
+
+	scope.add( waterMaterial );
+
+	scope.onBeforeRender = function ( renderer, scene, camera ) { 
+
+		scope.water.updateTextureMatrix( camera );
+
+		var currentRenderTarget = renderer.getRenderTarget();
+
+		var currentVrEnabled = renderer.vr.enabled;
+		var currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;
+
+		scope.visible = false;
+
+		renderer.vr.enabled = false; // Avoid camera modification and recursion
+		renderer.shadowMap.autoUpdate = false; // Avoid re-computing shadows
+
+		renderer.render( scene, scope.water.mirrorCamera, scope.water.renderTarget, true );
+
+		scope.visible = true;
+
+		renderer.vr.enabled = currentVrEnabled;
+		renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
+
+		renderer.setRenderTarget( currentRenderTarget );
+
+	};
+	
+};
+
+THREE.WaterMesh.prototype = Object.create( THREE.Mesh.prototype );
+THREE.WaterMesh.prototype.constructor = THREE.WaterMesh;

+ 103 - 74
examples/webgl_shaders_ocean.html

@@ -37,6 +37,7 @@
 
 		<script src="js/Detector.js"></script>
 		<script src="js/libs/stats.min.js"></script>
+		<script src="js/libs/dat.gui.min.js"></script>
 
 		<script>
 
@@ -48,17 +49,14 @@
 			}
 
 			var container, stats;
-			var camera, scene, renderer;
-			var controls, water, sphere;
+			var camera, scene, renderer, light;
+			var controls, water, sphere, cubeMap;
 
 			var parameters = {
-				width: 2000,
-				height: 2000,
-				widthSegments: 250,
-				heightSegments: 250,
-				depth: 1500,
-				param: 4,
-				filterparam: 1
+				oceanSide: 2000,
+				size: 0.3,
+				distortionScale: 3.7,
+				alpha: 0.96
 			};
 
 			var waterNormals;
@@ -80,61 +78,103 @@
 				//
 
 				scene = new THREE.Scene();
-				scene.fog = new THREE.FogExp2( 0xaabbbb, 0.0001 );
+				scene.fog = new THREE.FogExp2( 0xaabbbb, 0.001 );
 
 				//
 
-				camera = new THREE.PerspectiveCamera( 55, window.innerWidth / window.innerHeight, 0.5, 3000000 );
-				camera.position.set( 2000, 750, 2000 );
+				camera = new THREE.PerspectiveCamera( 55, window.innerWidth / window.innerHeight, 1, 20000 );
+				camera.position.set( 30, 30, 100 );
 
 				//
 
 				controls = new THREE.OrbitControls( camera, renderer.domElement );
-				controls.enablePan = false;
-				controls.minDistance = 1000.0;
-				controls.maxDistance = 5000.0;
 				controls.maxPolarAngle = Math.PI * 0.495;
-				controls.target.set( 0, 500, 0 );
-				controls.update();
+				controls.target.set( 0, 10, 0 );
+				controls.enablePan = false;
+				controls.minDistance = 40.0;
+				controls.maxDistance = 200.0;
+				camera.lookAt( controls.target );
 
-				scene.add( new THREE.AmbientLight( 0x444444 ) );
+				setLighting();
 
 				//
 
-				var light = new THREE.DirectionalLight( 0xffffbb, 1 );
-				light.position.set( - 1, 1, - 1 );
-				scene.add( light );
+				setWater();
+
+				// 
+
+				setSkybox();
 
 				//
 
-				waterNormals = new THREE.TextureLoader().load( 'textures/waternormals.jpg' );
-				waterNormals.wrapS = waterNormals.wrapT = THREE.RepeatWrapping;
-
-				water = new THREE.Water( renderer, camera, scene, {
-					textureWidth: 512,
-					textureHeight: 512,
-					waterNormals: waterNormals,
-					alpha: 	1.0,
-					sunDirection: light.position.clone().normalize(),
-					sunColor: 0xffffff,
-					waterColor: 0x001e0f,
-					distortionScale: 50.0,
-					fog: scene.fog != undefined
+				var geometry = new THREE.IcosahedronGeometry( 20, 4 );
+
+				for ( var i = 0, j = geometry.faces.length; i < j; i ++ ) {
+
+					geometry.faces[ i ].color.setHex( Math.random() * 0xffffff );
+
+				}
+
+				var material = new THREE.MeshPhongMaterial( {
+					vertexColors: THREE.FaceColors,
+					shininess: 100,
+					envMap: cubeMap,
+					side: THREE.DoubleSide
 				} );
 
+				sphere = new THREE.Mesh( geometry, material );
+				sphere.castShadow = true;
+				scene.add( sphere );
+
+				//
+
+				stats = new Stats();
+				container.appendChild( stats.dom );
+
+				//
+
+				gui = new dat.GUI();
+
+				gui.add( parameters, 'distortionScale', 0, 8, 0.1 );
+				gui.add( parameters, 'size', 0.1, 10, 0.1 );
+				gui.add( parameters, 'alpha', 0.9, 1, .001 );
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+			}
 
-				var mirrorMesh = new THREE.Mesh(
-					new THREE.PlaneBufferGeometry( parameters.width * 500, parameters.height * 500 ),
-					water.material
+			function setWater() {
+
+				water = new THREE.WaterMesh( 
+					parameters.oceanSide * 5, 
+					parameters.oceanSide * 5,
+					{
+						textureWidth: 512,
+						textureHeight: 512,
+						waterNormals: new THREE.TextureLoader().load( 'textures/waternormals.jpg', function ( texture ) {
+							texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
+						}),
+						alpha: 	parameters.alpha,
+						sunDirection: light.position.clone().normalize(),
+						sunColor: 0xffffff,
+						waterColor: 0x001e0f,
+						distortionScale: parameters.distortionScale,
+						fog: scene.fog != undefined
+					}
 				);
 
-				mirrorMesh.add( water );
-				mirrorMesh.rotation.x = - Math.PI * 0.5;
-				scene.add( mirrorMesh );
+				water.rotation.x = - Math.PI / 2;
+				water.receiveShadow = true;
+
+				scene.add( water );
+
+			}
 
-				// skybox
+			function setSkybox() {
 
-				var cubeMap = new THREE.CubeTexture( [] );
+				cubeMap = new THREE.CubeTexture( [] );
 				cubeMap.format = THREE.RGBFormat;
 
 				var loader = new THREE.ImageLoader();
@@ -172,44 +212,32 @@
 					fragmentShader: cubeShader.fragmentShader,
 					vertexShader: cubeShader.vertexShader,
 					uniforms: cubeShader.uniforms,
-					depthWrite: false,
 					side: THREE.BackSide
 				} );
 
 				var skyBox = new THREE.Mesh(
-					new THREE.BoxGeometry( 1000000, 1000000, 1000000 ),
+					new THREE.BoxGeometry( parameters.oceanSide * 5 + 100, parameters.oceanSide * 5 + 100, parameters.oceanSide * 5 + 100 ),
 					skyBoxMaterial
 				);
 
 				scene.add( skyBox );
 
-				//
-
-				var geometry = new THREE.IcosahedronGeometry( 400, 4 );
-
-				for ( var i = 0, j = geometry.faces.length; i < j; i ++ ) {
-
-					geometry.faces[ i ].color.setHex( Math.random() * 0xffffff );
-
-				}
-
-				var material = new THREE.MeshPhongMaterial( {
-					vertexColors: THREE.FaceColors,
-					shininess: 100,
-					envMap: cubeMap
-				} );
-
-				sphere = new THREE.Mesh( geometry, material );
-				scene.add( sphere );
+			}
 
-				//
+			function setLighting() {
 
-				stats = new Stats();
-				container.appendChild( stats.dom );
+				renderer.shadowMap.enabled = true;
 
-				//
+				light = new THREE.DirectionalLight( 0xffffbb, 1 );
+				light.position.set( - 30, 30, - 30 );
+				light.castShadow = true;
+				light.shadow.camera.top = 45;
+				light.shadow.camera.right = 40;
+				light.shadow.camera.left = light.shadow.camera.bottom = -40;
+				light.shadow.camera.near = 1; 
+				light.shadow.camera.far = 200;
 
-				window.addEventListener( 'resize', onWindowResize, false );
+				scene.add( light, new THREE.AmbientLight( 0x444444 ) );
 
 			}
 
@@ -222,8 +250,6 @@
 
 			}
 
-			//
-
 			function animate() {
 
 				requestAnimationFrame( animate );
@@ -234,14 +260,17 @@
 
 			function render() {
 
-				var time = performance.now() * 0.001;
+				var time = performance.now() * 0.003;
 
-				sphere.position.y = Math.sin( time ) * 500 + 250;
-				sphere.rotation.x = time * 0.5;
-				sphere.rotation.z = time * 0.51;
+				sphere.position.y = Math.sin( time ) * 2 + 5;
+				sphere.rotation.x = time * 0.1;
+				sphere.rotation.z = time * 0.11;
 
 				water.material.uniforms.time.value += 1.0 / 60.0;
-				water.render();
+				water.material.uniforms.size.value = parameters.size;
+				water.material.uniforms.distortionScale.value = parameters.distortionScale;
+				water.material.uniforms.alpha.value = parameters.alpha;
+
 				renderer.render( scene, camera );
 
 			}