Sfoglia il codice sorgente

Merge pull request #16516 from Mugen87/dev30

 JSM: Added module and TS file for Reflector, Refractor and Lensflare.
Michael Herzog 6 anni fa
parent
commit
b635f0baf0

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

@@ -143,6 +143,13 @@
 						<li>VRMLLoader</li>
 					</ul>
 				</li>
+				<li>objects
+					<ul>
+						<li>Lensflare</li>
+						<li>Reflector</li>
+						<li>Refractor</li>
+					</ul>
+				</li>
 				<li>pmrem
 					<ul>
 						<li>PMREMCubeUVPacker</li>
@@ -163,6 +170,7 @@
 					<ul>
 						<li>BokehShader2</li>
 						<li>UnpackDepthRGBAShader</li>
+						<li>WaterRefractionShader</li>
 					</ul>
 				</li>
 				<li>utils

+ 0 - 5
examples/js/objects/Reflector.js

@@ -28,12 +28,10 @@ THREE.Reflector = function ( geometry, options ) {
 	var rotationMatrix = new THREE.Matrix4();
 	var lookAtPosition = new THREE.Vector3( 0, 0, - 1 );
 	var clipPlane = new THREE.Vector4();
-	var viewport = new THREE.Vector4();
 
 	var view = new THREE.Vector3();
 	var target = new THREE.Vector3();
 	var q = new THREE.Vector4();
-	var size = new THREE.Vector2();
 
 	var textureMatrix = new THREE.Matrix4();
 	var virtualCamera = new THREE.PerspectiveCamera();
@@ -198,17 +196,14 @@ THREE.Reflector.ReflectorShader = {
 	uniforms: {
 
 		'color': {
-			type: 'c',
 			value: null
 		},
 
 		'tDiffuse': {
-			type: 't',
 			value: null
 		},
 
 		'textureMatrix': {
-			type: 'm4',
 			value: null
 		}
 

+ 19 - 29
examples/js/objects/Refractor.js

@@ -184,43 +184,36 @@ THREE.Refractor = function ( geometry, options ) {
 
 	//
 
-	var render = ( function () {
+	function render( renderer, scene, camera ) {
 
-		var viewport = new THREE.Vector4();
-		var size = new THREE.Vector2();
+		scope.visible = false;
 
-		return function render( renderer, scene, 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
+		renderer.shadowMap.autoUpdate = false; // avoid re-computing shadows
 
-			var currentRenderTarget = renderer.getRenderTarget();
-			var currentVrEnabled = renderer.vr.enabled;
-			var currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;
+		renderer.setRenderTarget( renderTarget );
+		renderer.clear();
+		renderer.render( scene, virtualCamera );
 
-			renderer.vr.enabled = false; // avoid camera modification
-			renderer.shadowMap.autoUpdate = false; // avoid re-computing shadows
+		renderer.vr.enabled = currentVrEnabled;
+		renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
+		renderer.setRenderTarget( currentRenderTarget );
 
-			renderer.setRenderTarget( renderTarget );
-			renderer.clear();
-			renderer.render( scene, virtualCamera );
+		// restore viewport
 
-			renderer.vr.enabled = currentVrEnabled;
-			renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
-			renderer.setRenderTarget( currentRenderTarget );
+		if ( camera.isArrayCamera ) {
 
-			// restore viewport
+			renderer.state.viewport( camera.viewport );
 
-			if ( camera.isArrayCamera ) {
-
-				renderer.state.viewport( camera.viewport );
-
-			}
+		}
 
-			scope.visible = true;
+		scope.visible = true;
 
-		};
-
-	} )();
+	}
 
 	//
 
@@ -262,17 +255,14 @@ THREE.Refractor.RefractorShader = {
 	uniforms: {
 
 		'color': {
-			type: 'c',
 			value: null
 		},
 
 		'tDiffuse': {
-			type: 't',
 			value: null
 		},
 
 		'textureMatrix': {
-			type: 'm4',
 			value: null
 		}
 

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

@@ -8,27 +8,22 @@ 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
 		}
 

+ 21 - 0
examples/jsm/objects/Lensflare.d.ts

@@ -0,0 +1,21 @@
+import {
+  Mesh,
+  Texture,
+  Color
+} from '../../../src/Three';
+
+export class LensflareElement {
+  constructor(texture: Texture, size?: number, distance?: number, color?: Color );
+  texture: Texture;
+  size: number;
+  distance: number;
+  color: Color;
+}
+
+export class Lensflare extends Mesh {
+  constructor();
+  isLensflare: boolean;
+
+  addElement(element: LensflareElement): void;
+  dispose(): void;
+}

+ 400 - 0
examples/jsm/objects/Lensflare.js

@@ -0,0 +1,400 @@
+/**
+ * @author Mugen87 / https://github.com/Mugen87
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+import {
+	AdditiveBlending,
+	Box2,
+	BufferGeometry,
+	ClampToEdgeWrapping,
+	Color,
+	DataTexture,
+	InterleavedBuffer,
+	InterleavedBufferAttribute,
+	Mesh,
+	MeshBasicMaterial,
+	NearestFilter,
+	RGBFormat,
+	RawShaderMaterial,
+	Vector2,
+	Vector3,
+	Vector4
+} from "../../../build/three.module.js";
+
+var Lensflare = function () {
+
+	Mesh.call( this, Lensflare.Geometry, new MeshBasicMaterial( { opacity: 0, transparent: true } ) );
+
+	this.type = 'Lensflare';
+	this.frustumCulled = false;
+	this.renderOrder = Infinity;
+
+	//
+
+	var positionScreen = new Vector3();
+	var positionView = new Vector3();
+
+	// textures
+
+	var tempMap = new DataTexture( new Uint8Array( 16 * 16 * 3 ), 16, 16, RGBFormat );
+	tempMap.minFilter = NearestFilter;
+	tempMap.magFilter = NearestFilter;
+	tempMap.wrapS = ClampToEdgeWrapping;
+	tempMap.wrapT = ClampToEdgeWrapping;
+	tempMap.needsUpdate = true;
+
+	var occlusionMap = new DataTexture( new Uint8Array( 16 * 16 * 3 ), 16, 16, RGBFormat );
+	occlusionMap.minFilter = NearestFilter;
+	occlusionMap.magFilter = NearestFilter;
+	occlusionMap.wrapS = ClampToEdgeWrapping;
+	occlusionMap.wrapT = ClampToEdgeWrapping;
+	occlusionMap.needsUpdate = true;
+
+	// material
+
+	var geometry = Lensflare.Geometry;
+
+	var material1a = new RawShaderMaterial( {
+		uniforms: {
+			'scale': { value: null },
+			'screenPosition': { value: null }
+		},
+		vertexShader: [
+
+			'precision highp float;',
+
+			'uniform vec3 screenPosition;',
+			'uniform vec2 scale;',
+
+			'attribute vec3 position;',
+
+			'void main() {',
+
+			'	gl_Position = vec4( position.xy * scale + screenPosition.xy, screenPosition.z, 1.0 );',
+
+			'}'
+
+		].join( '\n' ),
+		fragmentShader: [
+
+			'precision highp float;',
+
+			'void main() {',
+
+			'	gl_FragColor = vec4( 1.0, 0.0, 1.0, 1.0 );',
+
+			'}'
+
+		].join( '\n' ),
+		depthTest: true,
+		depthWrite: false,
+		transparent: false
+	} );
+
+	var material1b = new RawShaderMaterial( {
+		uniforms: {
+			'map': { value: tempMap },
+			'scale': { value: null },
+			'screenPosition': { value: null }
+		},
+		vertexShader: [
+
+			'precision highp float;',
+
+			'uniform vec3 screenPosition;',
+			'uniform vec2 scale;',
+
+			'attribute vec3 position;',
+			'attribute vec2 uv;',
+
+			'varying vec2 vUV;',
+
+			'void main() {',
+
+			'	vUV = uv;',
+
+			'	gl_Position = vec4( position.xy * scale + screenPosition.xy, screenPosition.z, 1.0 );',
+
+			'}'
+
+		].join( '\n' ),
+		fragmentShader: [
+
+			'precision highp float;',
+
+			'uniform sampler2D map;',
+
+			'varying vec2 vUV;',
+
+			'void main() {',
+
+			'	gl_FragColor = texture2D( map, vUV );',
+
+			'}'
+
+		].join( '\n' ),
+		depthTest: false,
+		depthWrite: false,
+		transparent: false
+	} );
+
+	// the following object is used for occlusionMap generation
+
+	var mesh1 = new Mesh( geometry, material1a );
+
+	//
+
+	var elements = [];
+
+	var shader = LensflareElement.Shader;
+
+	var material2 = new RawShaderMaterial( {
+		uniforms: {
+			'map': { value: null },
+			'occlusionMap': { value: occlusionMap },
+			'color': { value: new Color( 0xffffff ) },
+			'scale': { value: new Vector2() },
+			'screenPosition': { value: new Vector3() }
+		},
+		vertexShader: shader.vertexShader,
+		fragmentShader: shader.fragmentShader,
+		blending: AdditiveBlending,
+		transparent: true,
+		depthWrite: false
+	} );
+
+	var mesh2 = new Mesh( geometry, material2 );
+
+	this.addElement = function ( element ) {
+
+		elements.push( element );
+
+	};
+
+	//
+
+	var scale = new Vector2();
+	var screenPositionPixels = new Vector2();
+	var validArea = new Box2();
+	var viewport = new Vector4();
+
+	this.onBeforeRender = function ( renderer, scene, camera ) {
+
+		renderer.getCurrentViewport( viewport );
+
+		var invAspect = viewport.w / viewport.z;
+		var halfViewportWidth = viewport.z / 2.0;
+		var halfViewportHeight = viewport.w / 2.0;
+
+		var size = 16 / viewport.w;
+		scale.set( size * invAspect, size );
+
+		validArea.min.set( viewport.x, viewport.y );
+		validArea.max.set( viewport.x + ( viewport.z - 16 ), viewport.y + ( viewport.w - 16 ) );
+
+		// calculate position in screen space
+
+		positionView.setFromMatrixPosition( this.matrixWorld );
+		positionView.applyMatrix4( camera.matrixWorldInverse );
+
+		if ( positionView.z > 0 ) return; // lensflare is behind the camera
+
+		positionScreen.copy( positionView ).applyMatrix4( camera.projectionMatrix );
+
+		// horizontal and vertical coordinate of the lower left corner of the pixels to copy
+
+		screenPositionPixels.x = viewport.x + ( positionScreen.x * halfViewportWidth ) + halfViewportWidth - 8;
+		screenPositionPixels.y = viewport.y + ( positionScreen.y * halfViewportHeight ) + halfViewportHeight - 8;
+
+		// screen cull
+
+		if ( validArea.containsPoint( screenPositionPixels ) ) {
+
+			// save current RGB to temp texture
+
+			renderer.copyFramebufferToTexture( screenPositionPixels, tempMap );
+
+			// render pink quad
+
+			var uniforms = material1a.uniforms;
+			uniforms[ "scale" ].value = scale;
+			uniforms[ "screenPosition" ].value = positionScreen;
+
+			renderer.renderBufferDirect( camera, null, geometry, material1a, mesh1, null );
+
+			// copy result to occlusionMap
+
+			renderer.copyFramebufferToTexture( screenPositionPixels, occlusionMap );
+
+			// restore graphics
+
+			var uniforms = material1b.uniforms;
+			uniforms[ "scale" ].value = scale;
+			uniforms[ "screenPosition" ].value = positionScreen;
+
+			renderer.renderBufferDirect( camera, null, geometry, material1b, mesh1, null );
+
+			// render elements
+
+			var vecX = - positionScreen.x * 2;
+			var vecY = - positionScreen.y * 2;
+
+			for ( var i = 0, l = elements.length; i < l; i ++ ) {
+
+				var element = elements[ i ];
+
+				var uniforms = material2.uniforms;
+
+				uniforms[ "color" ].value.copy( element.color );
+				uniforms[ "map" ].value = element.texture;
+				uniforms[ "screenPosition" ].value.x = positionScreen.x + vecX * element.distance;
+				uniforms[ "screenPosition" ].value.y = positionScreen.y + vecY * element.distance;
+
+				var size = element.size / viewport.w;
+				var invAspect = viewport.w / viewport.z;
+
+				uniforms[ "scale" ].value.set( size * invAspect, size );
+
+				material2.uniformsNeedUpdate = true;
+
+				renderer.renderBufferDirect( camera, null, geometry, material2, mesh2, null );
+
+			}
+
+		}
+
+	};
+
+	this.dispose = function () {
+
+		material1a.dispose();
+		material1b.dispose();
+		material2.dispose();
+
+		tempMap.dispose();
+		occlusionMap.dispose();
+
+		for ( var i = 0, l = elements.length; i < l; i ++ ) {
+
+			elements[ i ].texture.dispose();
+
+		}
+
+	};
+
+};
+
+Lensflare.prototype = Object.create( Mesh.prototype );
+Lensflare.prototype.constructor = Lensflare;
+Lensflare.prototype.isLensflare = true;
+
+//
+
+var LensflareElement = function ( texture, size, distance, color ) {
+
+	this.texture = texture;
+	this.size = size || 1;
+	this.distance = distance || 0;
+	this.color = color || new Color( 0xffffff );
+
+};
+
+LensflareElement.Shader = {
+
+	uniforms: {
+
+		'map': { value: null },
+		'occlusionMap': { value: null },
+		'color': { value: null },
+		'scale': { value: null },
+		'screenPosition': { value: null }
+
+	},
+
+	vertexShader: [
+
+		'precision highp float;',
+
+		'uniform vec3 screenPosition;',
+		'uniform vec2 scale;',
+
+		'uniform sampler2D occlusionMap;',
+
+		'attribute vec3 position;',
+		'attribute vec2 uv;',
+
+		'varying vec2 vUV;',
+		'varying float vVisibility;',
+
+		'void main() {',
+
+		'	vUV = uv;',
+
+		'	vec2 pos = position.xy;',
+
+		'	vec4 visibility = texture2D( occlusionMap, vec2( 0.1, 0.1 ) );',
+		'	visibility += texture2D( occlusionMap, vec2( 0.5, 0.1 ) );',
+		'	visibility += texture2D( occlusionMap, vec2( 0.9, 0.1 ) );',
+		'	visibility += texture2D( occlusionMap, vec2( 0.9, 0.5 ) );',
+		'	visibility += texture2D( occlusionMap, vec2( 0.9, 0.9 ) );',
+		'	visibility += texture2D( occlusionMap, vec2( 0.5, 0.9 ) );',
+		'	visibility += texture2D( occlusionMap, vec2( 0.1, 0.9 ) );',
+		'	visibility += texture2D( occlusionMap, vec2( 0.1, 0.5 ) );',
+		'	visibility += texture2D( occlusionMap, vec2( 0.5, 0.5 ) );',
+
+		'	vVisibility =        visibility.r / 9.0;',
+		'	vVisibility *= 1.0 - visibility.g / 9.0;',
+		'	vVisibility *=       visibility.b / 9.0;',
+
+		'	gl_Position = vec4( ( pos * scale + screenPosition.xy ).xy, screenPosition.z, 1.0 );',
+
+		'}'
+
+	].join( '\n' ),
+
+	fragmentShader: [
+
+		'precision highp float;',
+
+		'uniform sampler2D map;',
+		'uniform vec3 color;',
+
+		'varying vec2 vUV;',
+		'varying float vVisibility;',
+
+		'void main() {',
+
+		'	vec4 texture = texture2D( map, vUV );',
+		'	texture.a *= vVisibility;',
+		'	gl_FragColor = texture;',
+		'	gl_FragColor.rgb *= color;',
+
+		'}'
+
+	].join( '\n' )
+
+};
+
+Lensflare.Geometry = ( function () {
+
+	var geometry = new BufferGeometry();
+
+	var float32Array = new Float32Array( [
+		- 1, - 1, 0, 0, 0,
+		1, - 1, 0, 1, 0,
+		1, 1, 0, 1, 1,
+		- 1, 1, 0, 0, 1
+	] );
+
+	var interleavedBuffer = new InterleavedBuffer( float32Array, 5 );
+
+	geometry.setIndex( [ 0, 1, 2,	0, 2, 3 ] );
+	geometry.addAttribute( 'position', new InterleavedBufferAttribute( interleavedBuffer, 3, 0, false ) );
+	geometry.addAttribute( 'uv', new InterleavedBufferAttribute( interleavedBuffer, 2, 3, false ) );
+
+	return geometry;
+
+} )();
+
+export { Lensflare, LensflareElement };

+ 21 - 0
examples/jsm/objects/Reflector.d.ts

@@ -0,0 +1,21 @@
+import {
+  Mesh,
+  BufferGeometry,
+  Color,
+  WebGLRenderTarget
+} from '../../../src/Three';
+
+export interface ReflectorOptions {
+  color?: Color;
+  textureWidth?: number;
+  textureHeight?: number;
+  clipBias?: number;
+  shader?: object;
+  recursion?: number;
+}
+
+export class Reflector extends Mesh {
+  constructor(geometry?: BufferGeometry, options?: ReflectorOptions);
+
+  getRenderTarget(): WebGLRenderTarget;
+}

+ 267 - 0
examples/jsm/objects/Reflector.js

@@ -0,0 +1,267 @@
+/**
+ * @author Slayvin / http://slayvin.net
+ */
+
+import {
+	Color,
+	LinearFilter,
+	Math as _Math,
+	Matrix4,
+	Mesh,
+	PerspectiveCamera,
+	Plane,
+	RGBFormat,
+	ShaderMaterial,
+	UniformsUtils,
+	Vector3,
+	Vector4,
+	WebGLRenderTarget
+} from "../../../build/three.module.js";
+
+var Reflector = function ( geometry, options ) {
+
+	Mesh.call( this, geometry );
+
+	this.type = 'Reflector';
+
+	var scope = this;
+
+	options = options || {};
+
+	var color = ( options.color !== undefined ) ? new Color( options.color ) : new Color( 0x7F7F7F );
+	var textureWidth = options.textureWidth || 512;
+	var textureHeight = options.textureHeight || 512;
+	var clipBias = options.clipBias || 0;
+	var shader = options.shader || Reflector.ReflectorShader;
+	var recursion = options.recursion !== undefined ? options.recursion : 0;
+
+	//
+
+	var reflectorPlane = new Plane();
+	var normal = new Vector3();
+	var reflectorWorldPosition = 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 virtualCamera = 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 material = new ShaderMaterial( {
+		uniforms: UniformsUtils.clone( shader.uniforms ),
+		fragmentShader: shader.fragmentShader,
+		vertexShader: shader.vertexShader
+	} );
+
+	material.uniforms[ "tDiffuse" ].value = renderTarget.texture;
+	material.uniforms[ "color" ].value = color;
+	material.uniforms[ "textureMatrix" ].value = textureMatrix;
+
+	this.material = material;
+
+	this.onBeforeRender = function ( renderer, scene, camera ) {
+
+		if ( 'recursion' in camera.userData ) {
+
+			if ( camera.userData.recursion === recursion ) return;
+
+			camera.userData.recursion ++;
+
+		}
+
+		reflectorWorldPosition.setFromMatrixPosition( scope.matrixWorld );
+		cameraWorldPosition.setFromMatrixPosition( camera.matrixWorld );
+
+		rotationMatrix.extractRotation( scope.matrixWorld );
+
+		normal.set( 0, 0, 1 );
+		normal.applyMatrix4( rotationMatrix );
+
+		view.subVectors( reflectorWorldPosition, cameraWorldPosition );
+
+		// Avoid rendering when reflector is facing away
+
+		if ( view.dot( normal ) > 0 ) return;
+
+		view.reflect( normal ).negate();
+		view.add( reflectorWorldPosition );
+
+		rotationMatrix.extractRotation( camera.matrixWorld );
+
+		lookAtPosition.set( 0, 0, - 1 );
+		lookAtPosition.applyMatrix4( rotationMatrix );
+		lookAtPosition.add( cameraWorldPosition );
+
+		target.subVectors( reflectorWorldPosition, lookAtPosition );
+		target.reflect( normal ).negate();
+		target.add( reflectorWorldPosition );
+
+		virtualCamera.position.copy( view );
+		virtualCamera.up.set( 0, 1, 0 );
+		virtualCamera.up.applyMatrix4( rotationMatrix );
+		virtualCamera.up.reflect( normal );
+		virtualCamera.lookAt( target );
+
+		virtualCamera.far = camera.far; // Used in WebGLBackground
+
+		virtualCamera.updateMatrixWorld();
+		virtualCamera.projectionMatrix.copy( camera.projectionMatrix );
+
+		virtualCamera.userData.recursion = 0;
+
+		// 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( virtualCamera.projectionMatrix );
+		textureMatrix.multiply( virtualCamera.matrixWorldInverse );
+		textureMatrix.multiply( scope.matrixWorld );
+
+		// 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
+		reflectorPlane.setFromNormalAndCoplanarPoint( normal, reflectorWorldPosition );
+		reflectorPlane.applyMatrix4( virtualCamera.matrixWorldInverse );
+
+		clipPlane.set( reflectorPlane.normal.x, reflectorPlane.normal.y, reflectorPlane.normal.z, reflectorPlane.constant );
+
+		var projectionMatrix = virtualCamera.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;
+
+		// Render
+
+		scope.visible = false;
+
+		var currentRenderTarget = renderer.getRenderTarget();
+
+		var currentVrEnabled = renderer.vr.enabled;
+		var currentShadowAutoUpdate = renderer.shadowMap.autoUpdate;
+
+		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, virtualCamera );
+
+		renderer.vr.enabled = currentVrEnabled;
+		renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
+
+		renderer.setRenderTarget( currentRenderTarget );
+
+		// Restore viewport
+
+		if ( camera.isArrayCamera ) {
+
+			renderer.state.viewport( camera.viewport );
+
+		}
+
+		scope.visible = true;
+
+	};
+
+	this.getRenderTarget = function () {
+
+		return renderTarget;
+
+	};
+
+};
+
+Reflector.prototype = Object.create( Mesh.prototype );
+Reflector.prototype.constructor = Reflector;
+
+Reflector.ReflectorShader = {
+
+	uniforms: {
+
+		'color': {
+			value: null
+		},
+
+		'tDiffuse': {
+			value: null
+		},
+
+		'textureMatrix': {
+			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' )
+};
+
+export { Reflector };

+ 20 - 0
examples/jsm/objects/Refractor.d.ts

@@ -0,0 +1,20 @@
+import {
+  Mesh,
+  BufferGeometry,
+  Color,
+  WebGLRenderTarget
+} from '../../../src/Three';
+
+export interface RefractorOptions {
+  color?: Color;
+  textureWidth?: number;
+  textureHeight?: number;
+  clipBias?: number;
+  shader?: object;
+}
+
+export class Refractor extends Mesh {
+  constructor(geometry?: BufferGeometry, options?: RefractorOptions);
+
+  getRenderTarget(): WebGLRenderTarget;
+}

+ 334 - 0
examples/jsm/objects/Refractor.js

@@ -0,0 +1,334 @@
+/**
+ * @author Mugen87 / https://github.com/Mugen87
+ *
+ */
+
+import {
+	Color,
+	LinearFilter,
+	Math as _Math,
+	Matrix4,
+	Mesh,
+	PerspectiveCamera,
+	Plane,
+	Quaternion,
+	RGBFormat,
+	ShaderMaterial,
+	UniformsUtils,
+	Vector3,
+	Vector4,
+	WebGLRenderTarget
+} from "../../../build/three.module.js";
+
+var Refractor = function ( geometry, options ) {
+
+	Mesh.call( this, geometry );
+
+	this.type = 'Refractor';
+
+	var scope = this;
+
+	options = options || {};
+
+	var color = ( options.color !== undefined ) ? new Color( options.color ) : new Color( 0x7F7F7F );
+	var textureWidth = options.textureWidth || 512;
+	var textureHeight = options.textureHeight || 512;
+	var clipBias = options.clipBias || 0;
+	var shader = options.shader || Refractor.RefractorShader;
+
+	//
+
+	var virtualCamera = new PerspectiveCamera();
+	virtualCamera.matrixAutoUpdate = false;
+	virtualCamera.userData.refractor = true;
+
+	//
+
+	var refractorPlane = new Plane();
+	var textureMatrix = new Matrix4();
+
+	// render target
+
+	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;
+
+	}
+
+	// material
+
+	this.material = new ShaderMaterial( {
+		uniforms: 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 Vector3();
+		var cameraWorldPosition = new Vector3();
+		var rotationMatrix = new Matrix4();
+
+		var view = new Vector3();
+		var normal = new Vector3();
+
+		return function visible( 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 Vector3();
+		var position = new Vector3();
+		var quaternion = new Quaternion();
+		var scale = new 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 Plane();
+		var clipVector = new Vector4();
+		var q = new 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 );
+
+	}
+
+	//
+
+	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.setRenderTarget( renderTarget );
+		renderer.clear();
+		renderer.render( scene, virtualCamera );
+
+		renderer.vr.enabled = currentVrEnabled;
+		renderer.shadowMap.autoUpdate = currentShadowAutoUpdate;
+		renderer.setRenderTarget( currentRenderTarget );
+
+		// restore viewport
+
+		if ( camera.isArrayCamera ) {
+
+			renderer.state.viewport( camera.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();
+
+		updateTextureMatrix( camera );
+
+		updateVirtualCamera( camera );
+
+		render( renderer, scene, camera );
+
+	};
+
+	this.getRenderTarget = function () {
+
+		return renderTarget;
+
+	};
+
+};
+
+Refractor.prototype = Object.create( Mesh.prototype );
+Refractor.prototype.constructor = Refractor;
+
+Refractor.RefractorShader = {
+
+	uniforms: {
+
+		'color': {
+			value: null
+		},
+
+		'tDiffuse': {
+			value: null
+		},
+
+		'textureMatrix': {
+			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' )
+};
+
+export { Refractor };

+ 15 - 0
examples/jsm/shaders/WaterRefractionShader.d.ts

@@ -0,0 +1,15 @@
+import {
+  Uniform
+} from '../../../src/Three';
+
+export interface WaterRefractionShader {
+  uniforms: {
+    color: Uniform;
+    time: Uniform;
+    tDiffuse: Uniform;
+    tDudv: Uniform;
+    textureMatrix: Uniform;
+  };
+  vertexShader: string;
+  fragmentShader:string;
+}

+ 100 - 0
examples/jsm/shaders/WaterRefractionShader.js

@@ -0,0 +1,100 @@
+/**
+ * @author Mugen87 / https://github.com/Mugen87
+ *
+ */
+
+
+
+var WaterRefractionShader = {
+
+	uniforms: {
+
+		'color': {
+			value: null
+		},
+
+		'time': {
+			value: 0
+		},
+
+		'tDiffuse': {
+			value: null
+		},
+
+		'tDudv': {
+			value: null
+		},
+
+		'textureMatrix': {
+			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' )
+};
+
+export { WaterRefractionShader };

+ 5 - 0
utils/modularize.js

@@ -60,11 +60,16 @@ var files = [
 	{ path: 'loaders/TGALoader.js', dependencies: [], ignoreList: [] },
 	{ path: 'loaders/VRMLLoader.js', dependencies: [], ignoreList: [] },
 
+	{ path: 'objects/Lensflare.js', dependencies: [], ignoreList: [] },
+	{ path: 'objects/Reflector.js', dependencies: [], ignoreList: [] },
+	{ path: 'objects/Refractor.js', dependencies: [], ignoreList: [] },
+
 	{ path: 'pmrem/PMREMCubeUVPacker.js', dependencies: [], ignoreList: [] },
 	{ path: 'pmrem/PMREMGenerator.js', dependencies: [], ignoreList: [] },
 
 	{ path: 'shaders/BokehShader2.js', dependencies: [], ignoreList: [] },
 	{ path: 'shaders/UnpackDepthRGBAShader.js', dependencies: [], ignoreList: [] },
+	{ path: 'shaders/WaterRefractionShader.js', dependencies: [], ignoreList: [] },
 
 	{ path: 'renderers/CSS2DRenderer.js', dependencies: [], ignoreList: [] },
 	{ path: 'renderers/CSS3DRenderer.js', dependencies: [], ignoreList: [] },