Selaa lähdekoodia

Added refractor example

Mugen87 7 vuotta sitten
vanhempi
commit
2f84f189ef

+ 1 - 0
examples/files.js

@@ -231,6 +231,7 @@ var files = {
 		"webgl_postprocessing_unreal_bloom",
 		"webgl_raycast_texture",
 		"webgl_read_float_buffer",
+		"webgl_refraction",
 		"webgl_rtt",
 		"webgl_sandbox",
 		"webgl_shader",

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

@@ -0,0 +1,324 @@
+/**
+ * @author Mugen87 / https://github.com/Mugen87
+ *
+ */
+
+THREE.Refractor = function ( width, height, options ) {
+
+	THREE.Mesh.call( this, new THREE.PlaneBufferGeometry( width, height ) );
+
+	this.type = 'Refractor';
+
+	var scope = this;
+
+	var color = options.color || new THREE.Color( 0x7f7f7f );
+	var textureWidth = options.textureWidth || 512;
+	var textureHeight = options.textureHeight || 512;
+	var clipBias = options.clipBias || 0;
+	var shader = options.shader || THREE.DefaultRefractionShader;
+
+	//
+
+	var virtualCamera = new THREE.PerspectiveCamera();
+	virtualCamera.matrixAutoUpdate = false;
+	virtualCamera.userData.refractor = true;
+
+	//
+
+	var refractorPlane = new THREE.Plane();
+	var textureMatrix = new THREE.Matrix4();
+
+	// render target
+
+	var parameters = {
+		minFilter: THREE.LinearFilter,
+		magFilter: THREE.LinearFilter,
+		format: THREE.RGBFormat,
+		stencilBuffer: false
+	};
+
+	var renderTarget = new THREE.WebGLRenderTarget( textureWidth, textureHeight, parameters );
+
+	if ( ! THREE.Math.isPowerOfTwo( textureWidth ) || ! THREE.Math.isPowerOfTwo( textureHeight ) ) {
+
+		renderTarget.texture.generateMipmaps = false;
+
+	}
+
+	// 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
+
+	var visible = ( function () {
+
+		var refractorWorldPosition = new THREE.Vector3();
+		var cameraWorldPosition = new THREE.Vector3();
+		var rotationMatrix = new THREE.Matrix4();
+
+		var view = new THREE.Vector3();
+		var normal = new THREE.Vector3();
+
+		return function updateRefractorPlane( 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;
+
+		};
+
+	} )();
+
+	var updateRefractorPlane = ( function () {
+
+		var normal = new THREE.Vector3();
+		var position = new THREE.Vector3();
+		var quaternion = new THREE.Quaternion();
+		var 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 );
+
+		};
+
+	} )();
+
+	var updateVirtualCamera = ( function () {
+
+		var clipPlane = new THREE.Plane();
+		var clipVector = new THREE.Vector4();
+		var q = new THREE.Vector4();
+
+		return function updateVirtualCamera( camera ) {
+
+			virtualCamera.matrixWorld.copy( camera.matrixWorld );
+			virtualCamera.matrixWorldInverse.getInverse( virtualCamera.matrixWorld );
+			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. 5–16
+
+			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
+
+			var 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 );
+
+	}
+
+	//
+
+	var render = ( function () {
+
+		var viewport = new THREE.Vector4();
+
+		return function render( renderer, scene, camera ) {
+
+			scope.visible = false;
+
+			var currentRenderTarget = renderer.getRenderTarget();
+			var currentVrEnabled = renderer.vr.enabled;
+			var currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;
+
+			renderer.vr.enabled = false; // avoid camera modification
+			renderer.shadowMap.autoUpdate = false; // avoid re-computing shadows
+
+			renderer.render( scene, virtualCamera, renderTarget, true );
+
+			renderer.vr.enabled = currentVrEnabled;
+			renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
+			renderer.setRenderTarget( currentRenderTarget );
+
+			// restore viewport
+
+			var bounds = camera.bounds;
+
+			if ( bounds !== undefined ) {
+
+				var size = renderer.getSize();
+				var pixelRatio = renderer.getPixelRatio();
+
+				viewport.x = bounds.x * size.width * pixelRatio;
+				viewport.y = bounds.y * size.height * pixelRatio;
+				viewport.z = bounds.z * size.width * pixelRatio;
+				viewport.w = bounds.w * size.height * pixelRatio;
+
+				renderer.state.viewport( viewport );
+
+			}
+
+			scope.visible = true;
+
+		};
+
+	} )();
+
+	//
+
+	this.onBeforeRender = function ( renderer, scene, camera ) {
+
+		// 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( camera );
+
+		updateTextureMatrix( camera );
+
+		updateVirtualCamera( camera );
+
+		render( renderer, scene, camera );
+
+	};
+
+};
+
+THREE.Refractor.prototype = Object.create( THREE.Mesh.prototype );
+THREE.Refractor.prototype.constructor = THREE.Refractor;
+
+THREE.DefaultRefractionShader = {
+
+	uniforms: {
+
+		'color': {
+			type: 'c',
+			value: null
+		},
+
+		'tDiffuse': {
+			type: 't',
+			value: null
+		},
+
+		'textureMatrix': {
+			type: 'm4',
+			value: null
+		}
+
+	},
+
+	vertexShader: [
+
+		'uniform mat4 textureMatrix;',
+
+		'varying vec4 vUv;',
+
+		'void main() {',
+
+		'	vUv = textureMatrix * vec4( position, 1.0 );',
+
+		'	gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
+
+		'}'
+
+	].join( '\n' ),
+
+	fragmentShader: [
+
+		'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 );',
+
+		'}'
+
+	].join( '\n' )
+};

+ 101 - 0
examples/js/shaders/WaterRefractionShader.js

@@ -0,0 +1,101 @@
+/**
+ * @author Mugen87 / https://github.com/Mugen87
+ *
+ */
+
+THREE.WaterRefractionShader = {
+
+	uniforms: {
+
+		'color': {
+			type: 'c',
+			value: null
+		},
+
+		'time': {
+			type: 'f',
+			value: 0
+		},
+
+		'tDiffuse': {
+			type: 't',
+			value: null
+		},
+
+		'tDudv': {
+			type: 't',
+			value: null
+		},
+
+		'textureMatrix': {
+			type: 'm4',
+			value: null
+		}
+
+	},
+
+	vertexShader: [
+
+		'uniform mat4 textureMatrix;',
+
+		'varying vec2 vUv;',
+		'varying vec4 vUvRefraction;',
+
+		'void main() {',
+
+		'	vUv = uv;',
+
+		'	vUvRefraction = textureMatrix * vec4( position, 1.0 );',
+
+		'	gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
+
+		'}'
+
+	].join( '\n' ),
+
+	fragmentShader: [
+
+		'uniform vec3 color;',
+		'uniform float time;',
+		'uniform sampler2D tDiffuse;',
+		'uniform sampler2D tDudv;',
+
+		'varying vec2 vUv;',
+		'varying vec4 vUvRefraction;',
+
+		'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() {',
+
+		' float waveStrength = 0.1;',
+		' float waveSpeed = 0.03;',
+
+		// simple distortion (ripple) via dudv map (see https://www.youtube.com/watch?v=6B7IF6GOu7s)
+
+		'	vec2 distortedUv = texture2D( tDudv, vec2( vUv.x + time * waveSpeed, vUv.y ) ).rg * waveStrength;',
+		'	distortedUv = vUv.xy + vec2( distortedUv.x, distortedUv.y + time * waveSpeed );',
+		'	vec2 distortion = ( texture2D( tDudv, distortedUv ).rg * 2.0 - 1.0 ) * waveStrength;',
+
+		// new uv coords
+
+		' vec4 uv = vec4( vUvRefraction );',
+		' uv.xy += distortion;',
+
+		'	vec4 base = texture2DProj( tDiffuse, uv );',
+
+		'	gl_FragColor = vec4( blendOverlay( base.rgb, color ), 1.0 );',
+
+		'}'
+
+	].join( '\n' )
+};

BIN
examples/textures/waterdudv.jpg


+ 156 - 0
examples/webgl_refraction.html

@@ -0,0 +1,156 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js refraction</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/shaders/WaterRefractionShader.js"></script>
+		<script src="js/objects/Refractor.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> refraction
+		</div>
+
+		<script>
+
+		if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
+
+		var scene, camera, clock, renderer, refractor;
+
+		init();
+
+		function init() {
+
+			// scene
+
+			scene = new THREE.Scene();
+
+			// camera
+
+			camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );
+			camera.position.set( - 10, 0, 15 );
+			camera.lookAt( scene.position );
+
+			// clock
+
+			clock = new THREE.Clock();
+
+			// mesh
+
+			var geometry = new THREE.TorusKnotBufferGeometry( 3, 1, 256, 32 );
+			var material = new THREE.MeshStandardMaterial( { color: 0x6083c2 } );
+
+			var mesh = new THREE.Mesh( geometry, material );
+			scene.add( mesh );
+
+			// refractor
+
+			refractor = new THREE.Refractor( 10, 10, {
+				color: new THREE.Color( 0x999999 ),
+				textureWidth: 1024,
+				textureHeight: 1024,
+				shader: THREE.WaterRefractionShader
+			} );
+
+			refractor.position.set( 0, 0, 5 );
+
+			scene.add( refractor );
+
+			// load dudv map for distortion effect
+
+			var dudvMap = new THREE.TextureLoader().load( 'textures/waterdudv.jpg', function () {
+
+				animate();
+
+			} );
+
+			dudvMap.wrapS = dudvMap.wrapT = THREE.RepeatWrapping;
+			refractor.material.uniforms.tDudv.value = dudvMap;
+
+			// light
+
+			var ambientLight = new THREE.AmbientLight( 0xcccccc, 0.4 );
+			scene.add( ambientLight );
+
+			var pointLight = new THREE.PointLight( 0xffffff, 0.8 );
+			camera.add( pointLight );
+			scene.add( camera );
+
+			// renderer
+
+			renderer = new THREE.WebGLRenderer( { antialias: true } );
+			renderer.setSize( window.innerWidth, window.innerHeight );
+			renderer.setClearColor( 0x20252f );
+			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() {
+
+			refractor.material.uniforms.time.value += clock.getDelta();
+
+			renderer.render( scene, camera );
+
+		}
+
+	</script>
+
+</body>
+</html>