浏览代码

Merge pull request #8178 from bhouston/taa

Temporal Anti-Aliasing at a cost of 1 render per frame
Mr.doob 9 年之前
父节点
当前提交
95c2f07af0

+ 1 - 0
examples/files.js

@@ -181,6 +181,7 @@ var files = {
 		"webgl_postprocessing_nodes",
 		"webgl_postprocessing_smaa",
 		"webgl_postprocessing_ssao",
+		"webgl_postprocessing_taa",
 		"webgl_raycast_texture",
 		"webgl_read_float_buffer",
 		"webgl_rtt",

+ 30 - 8
examples/js/postprocessing/ManualMSAARenderPass.js

@@ -1,6 +1,14 @@
 /**
- * @author bhouston / http://clara.io/ *
- */
+*
+* Manual Multi-Sample Anti-Aliasing Render Pass
+*
+* @author bhouston / http://clara.io/
+*
+* This manual approach to MSAA re-renders the scene ones for each sample with camera jitter and accumulates the results.
+*
+* References: https://en.wikipedia.org/wiki/Multisample_anti-aliasing
+*
+*/
 
 THREE.ManualMSAARenderPass = function ( scene, camera, params ) {
 
@@ -18,22 +26,24 @@ THREE.ManualMSAARenderPass = function ( scene, camera, params ) {
 
 	if ( THREE.CompositeShader === undefined ) {
 
-		console.error( "THREE.MSAAPass relies on THREE.CompositeShader" );
+		console.error( "THREE.ManualMSAARenderPass relies on THREE.CompositeShader" );
 
 	}
 
 	var compositeShader = THREE.CompositeShader;
-	this.uniforms = THREE.UniformsUtils.clone( compositeShader.uniforms );
+	this.compositeUniforms = THREE.UniformsUtils.clone( compositeShader.uniforms );
 
 	this.materialComposite = new THREE.ShaderMaterial(	{
 
-		uniforms: this.uniforms,
+		uniforms: this.compositeUniforms,
 		vertexShader: compositeShader.vertexShader,
 		fragmentShader: compositeShader.fragmentShader,
 		transparent: true,
 		blending: THREE.CustomBlending,
 		blendSrc: THREE.OneFactor,
 		blendDst: THREE.OneFactor,
+		blendSrcAlpha: THREE.OneFactor,
+		blendDstAlpha: THREE.OneFactor,
 		blendEquation: THREE.AddEquation,
 		depthTest: false,
 		depthWrite: false
@@ -49,6 +59,8 @@ THREE.ManualMSAARenderPass = function ( scene, camera, params ) {
 
 THREE.ManualMSAARenderPass.prototype = {
 
+	constructor: THREE.ManualMSAARenderPass,
+
 	dispose: function() {
 
 		if ( this.sampleRenderTarget ) {
@@ -70,7 +82,7 @@ THREE.ManualMSAARenderPass.prototype = {
 	render: function ( renderer, writeBuffer, readBuffer, delta ) {
 
 		var camera = ( this.camera || this.scene.camera );
-		var jitterOffsets = THREE.ManualMSAARenderPass.JitterVectors[ Math.max( 0, Math.min( this.sampleLevel, 4 ) ) ];
+		var jitterOffsets = THREE.ManualMSAARenderPass.JitterVectors[ Math.max( 0, Math.min( this.sampleLevel, 5 ) ) ];
 
 		if( jitterOffsets.length === 1 ) {
 
@@ -88,8 +100,8 @@ THREE.ManualMSAARenderPass.prototype = {
 		var autoClear = renderer.autoClear;
 		renderer.autoClear = false;
 
-		this.uniforms[ "scale" ].value = 1.0 / ( jitterOffsets.length );
-		this.uniforms[ "tForeground" ].value = this.sampleRenderTarget;
+		this.compositeUniforms[ "scale" ].value = 1.0 / ( jitterOffsets.length );
+		this.compositeUniforms[ "tForeground" ].value = this.sampleRenderTarget;
 
 		// render the scene multiple times, each slightly jitter offset from the last and accumulate the results.
 		for ( var i = 0; i < jitterOffsets.length; i ++ ) {
@@ -140,5 +152,15 @@ THREE.ManualMSAARenderPass.JitterVectors = [
 		[ - 5, - 2 ], [ 2, 5 ], [ 5, 3 ], [ 3, - 5 ],
 		[ - 2, 6 ], [ 0, - 7 ], [ - 4, - 6 ], [ - 6, 4 ],
 		[ - 8, 0 ], [ 7, - 4 ], [ 6, 7 ], [ - 7, - 8 ]
+	],
+	[
+		[ - 4, - 7 ], [ - 7, - 5 ], [ - 3, - 5 ], [ - 5, - 4 ],
+		[ - 1, - 4 ], [ - 2, - 2 ], [ - 6, - 1 ], [ - 4, 0 ],
+		[ - 7, 1 ], [ - 1, 2 ], [ - 6, 3 ], [ - 3, 3 ],
+		[ - 7, 6 ], [ - 3, 6 ], [ - 5, 7 ], [ - 1, 7 ],
+		[ 5, - 7 ], [ 1, - 6 ], [ 6, - 5 ], [ 4, - 4 ],
+		[ 2, - 3 ], [ 7, - 2 ], [ 1, - 1 ], [ 4, - 1 ],
+		[ 2, 1 ], [ 6, 2 ], [ 0, 4 ], [ 4, 4 ],
+		[ 2, 5 ], [ 7, 5 ], [ 5, 6 ], [ 3, 7 ]
 	]
 ];

+ 121 - 0
examples/js/postprocessing/TAARenderPass.js

@@ -0,0 +1,121 @@
+/**
+ *
+ * Temporal Anti-Aliasing Render Pass
+ *
+ * @author bhouston / http://clara.io/
+ *
+ * When there is no motion in the scene, the TAA render pass accumulates jittered camera samples across frames to create a high quality anti-aliased result.
+ *
+ * References:
+ *
+ * TODO: Add support for motion vector pas so that accumulation of samples across frames can occur on dynamics scenes.
+ *
+ */
+
+THREE.TAARenderPass = function ( scene, camera, params ) {
+
+	if ( THREE.ManualMSAARenderPass === undefined ) {
+
+		console.error( "THREE.TAARenderPass relies on THREE.ManualMSAARenderPass" );
+
+	}
+	THREE.ManualMSAARenderPass.call( this, scene, camera, params );
+
+	this.sampleLevel = 0;
+	this.accumulate = false;
+	
+};
+
+THREE.TAARenderPass.prototype = Object.create( THREE.ManualMSAARenderPass.prototype );
+THREE.TAARenderPass.prototype.constructor = THREE.TAARenderPass;
+THREE.TAARenderPass.JitterVectors = THREE.ManualMSAARenderPass.JitterVectors;
+
+THREE.TAARenderPass.prototype.render = function ( renderer, writeBuffer, readBuffer, delta ) {
+
+	if( ! this.accumulate ) {
+
+			THREE.ManualMSAARenderPass.prototype.render.call( this, renderer, writeBuffer, readBuffer, delta );
+
+			this.accumulateIndex = -1;
+			return;
+
+	}
+
+	var jitterOffsets = THREE.TAARenderPass.JitterVectors[ 5 ];
+
+	var camera = ( this.camera || this.scene.camera );
+
+	if ( ! this.sampleRenderTarget ) {
+
+		this.sampleRenderTarget = new THREE.WebGLRenderTarget( readBuffer.width, readBuffer.height, this.params );
+
+	}
+
+	if ( ! this.holdRenderTarget ) {
+
+		this.holdRenderTarget = new THREE.WebGLRenderTarget( readBuffer.width, readBuffer.height, this.params );
+
+	}
+
+	if( this.accumulate && this.accumulateIndex === -1 ) {
+
+			THREE.ManualMSAARenderPass.prototype.render.call( this, renderer, this.holdRenderTarget, readBuffer, delta );
+
+			this.accumulateIndex = 0;
+
+	}
+
+	var autoClear = renderer.autoClear;
+	renderer.autoClear = false;
+
+	var sampleWeight = 1.0 / ( jitterOffsets.length );
+
+	if( this.accumulateIndex >= 0 && this.accumulateIndex < jitterOffsets.length ) {
+
+		this.compositeUniforms[ "scale" ].value = sampleWeight;
+		this.compositeUniforms[ "tForeground" ].value = writeBuffer;
+
+		// render the scene multiple times, each slightly jitter offset from the last and accumulate the results.
+		var numSamplesPerFrame = Math.pow( 2, this.sampleLevel );
+		for ( var i = 0; i < numSamplesPerFrame; i ++ ) {
+
+			var j = this.accumulateIndex;
+			// only jitters perspective cameras.	TODO: add support for jittering orthogonal cameras
+			var jitterOffset = jitterOffsets[j];
+			if ( camera.setViewOffset ) {
+				camera.setViewOffset( readBuffer.width, readBuffer.height,
+					jitterOffset[ 0 ] * 0.0625, jitterOffset[ 1 ] * 0.0625,   // 0.0625 = 1 / 16
+					readBuffer.width, readBuffer.height );
+			}
+
+			renderer.render( this.scene, this.camera, writeBuffer, true );
+
+			renderer.render( this.scene2, this.camera2, this.sampleRenderTarget, ( this.accumulateIndex == 0 ) );
+
+			this.accumulateIndex ++;
+			if( this.accumulateIndex >= jitterOffsets.length ) break;
+		}
+
+		// reset jitter to nothing.	TODO: add support for orthogonal cameras
+		if ( camera.setViewOffset ) camera.setViewOffset( undefined, undefined, undefined, undefined, undefined, undefined );
+
+	}
+
+	var accumulationWeight = this.accumulateIndex * sampleWeight;
+
+	if( accumulationWeight > 0 ) {
+		this.compositeUniforms[ "scale" ].value = 1.0;
+		this.compositeUniforms[ "tForeground" ].value = this.sampleRenderTarget;
+		renderer.render( this.scene2, this.camera2, writeBuffer, true );
+	}
+
+	if( accumulationWeight < 1.0 ) {
+		this.compositeUniforms[ "scale" ].value = 1.0 - accumulationWeight;
+		this.compositeUniforms[ "tForeground" ].value = this.holdRenderTarget;
+		renderer.render( this.scene2, this.camera2, writeBuffer, ( accumulationWeight === 0 ) );
+	}
+
+	renderer.autoClear = autoClear;
+
+
+}

+ 10 - 8
examples/webgl_postprocessing_msaa.html

@@ -29,6 +29,7 @@
 	<body>
 		<div id="info">
 			<a href="http://threejs.org" target="_blank">three.js</a> - Manual Multi-Sample Anti-Aliasing (MSAA) pass by <a href="https://clara.io" target="_blank">Ben Houston</a><br/><br/>
+			This manual approach to MSAA re-renders the scene ones for each sample with camera jitter and accumulates the results.<br/><br/>
 			Texture interpolation, mipmapping and anistropic sampling is disabled to emphasize<br/> the effect MSAA levels have one the resulting render quality.
 		</div>
 
@@ -50,7 +51,7 @@
 
 		<script>
 
-			var camera, scene, renderer, composer, copyPass, manualMSAARenderPass;
+			var camera, scene, renderer, composer, copyPass, msaaRenderPass;
 			var gui, stats, texture;
 
 			var param = { MSAASampleLevel: 2 };
@@ -71,11 +72,12 @@
 					'Level 1: 2 Samples': 1,
 					'Level 2: 4 Samples': 2,
 					'Level 3: 8 Samples': 3,
-					'Level 4: 16 Samples': 4
+					'Level 4: 16 Samples': 4,
+					'Level 5: 32 Samples': 5
 				} ).onFinishChange( function() {
 
-					if( manualMSAARenderPass ) {
-						manualMSAARenderPass.sampleLevel = param.MSAASampleLevel;
+					if( msaaRenderPass ) {
+						msaaRenderPass.sampleLevel = param.MSAASampleLevel;
 					}
 
 				} );
@@ -128,9 +130,9 @@
 
 				composer = new THREE.EffectComposer( renderer );
 
-				manualMSAARenderPass = new THREE.ManualMSAARenderPass( scene, camera );
-				manualMSAARenderPass.sampleLevel = param.MSAASampleLevel;
-				composer.addPass( manualMSAARenderPass );
+				msaaRenderPass = new THREE.ManualMSAARenderPass( scene, camera );
+				msaaRenderPass.sampleLevel = param.MSAASampleLevel;
+				composer.addPass( msaaRenderPass );
 
 				copyPass = new THREE.ShaderPass( THREE.CopyShader );
 		    copyPass.renderToScreen = true;
@@ -154,7 +156,7 @@
 				var newWidth  = Math.floor( width / pixelRatio ) || 1;
 				var newHeight = Math.floor( height / pixelRatio ) || 1;
 				composer.setSize( newWidth, newHeight );
-				msaaPass.setSize( newWidth, newHeight );
+				msaaRenderPass.setSize( newWidth, newHeight );
 
 			}
 

+ 214 - 0
examples/webgl_postprocessing_taa.html

@@ -0,0 +1,214 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - postprocessing manual msaa</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 {
+				margin: 0px;
+				background-color: #000;
+				overflow: hidden;
+				font-family:Monospace;
+				font-size:13px;
+				margin: 0px;
+				text-align:center;
+				overflow: hidden;
+			}
+
+			#info {
+				color: #fff;
+				position: absolute;
+				top: 10px;
+				width: 100%;
+				text-align: center;
+				display:block;
+			}
+		</style>
+	</head>
+	<body>
+		<div id="info">
+			<a href="http://threejs.org" target="_blank">three.js</a> - Temporal Anti-Aliasing (TAA) pass by <a href="https://clara.io" target="_blank">Ben Houston</a><br/><br/>
+			When there is no motion in the scene, the TAA render pass accumulates jittered camera samples<br/>
+			across frames to create a high quality anti-aliased result.<br/><br/>
+			Texture interpolation, mipmapping and anistropic sampling is disabled to emphasize<br/> the effect MSAA levels have one the resulting render quality.
+		</div>
+
+		<div id="container"></div>
+
+		<script src="../build/three.min.js"></script>
+		<script src="js/libs/stats.min.js"></script>
+		<script src="js/libs/dat.gui.min.js"></script>
+
+		<script src="js/postprocessing/ManualMSAARenderPass.js"></script>
+		<script src="js/postprocessing/TAARenderPass.js"></script>
+		<script src="js/shaders/CopyShader.js"></script>
+		<script src="js/shaders/CompositeShader.js"></script>
+
+		<script src="js/postprocessing/EffectComposer.js"></script>
+		<script src="js/postprocessing/RenderPass.js"></script>
+		<script src="js/postprocessing/MaskPass.js"></script>
+		<script src="js/postprocessing/ShaderPass.js"></script>
+
+
+		<script>
+
+			var camera, scene, renderer, composer, copyPass, taaRenderPass, renderPass;
+			var gui, stats, texture;
+
+			var param = { TAAEnabled: "1", TAASampleLevel: 0 };
+
+			init();
+			animate();
+
+			clearGui();
+
+			function clearGui() {
+
+				if ( gui ) gui.destroy();
+
+				gui = new dat.GUI();
+
+				gui.add( param, 'TAAEnabled', {
+					'Disabled': '0',
+					'Enabled': '1'
+				} ).onFinishChange( function() {
+
+					if( taaRenderPass ) {
+
+						taaRenderPass.enabled = ( param.TAAEnabled === "1" );
+						renderPass.enabled = ( param.TAAEnabled !== "1" );
+
+					}
+
+				} );
+
+				gui.add( param, 'TAASampleLevel', {
+					'Level 0: 1 Sample': 0,
+					'Level 1: 2 Samples': 1,
+					'Level 2: 4 Samples': 2,
+					'Level 3: 8 Samples': 3,
+					'Level 4: 16 Samples': 4,
+					'Level 5: 32 Samples': 5
+				} ).onFinishChange( function() {
+
+					if( taaRenderPass ) {
+						taaRenderPass.sampleLevel = param.TAASampleLevel;
+					}
+
+				} );
+
+				gui.open();
+
+			}
+
+			function init() {
+
+				container = document.getElementById( "container" );
+
+				renderer = new THREE.WebGLRenderer( { antialias: false } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				document.body.appendChild( renderer.domElement );
+
+				stats = new Stats();
+				stats.domElement.style.position = 'absolute';
+				stats.domElement.style.top = '0px';
+				container.appendChild( stats.domElement );
+
+				//
+
+				camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 1000 );
+				camera.position.z = 300;
+
+				scene = new THREE.Scene();
+
+				var geometry = new THREE.BoxGeometry( 120, 120, 120 );
+				var material = new THREE.MeshBasicMaterial( { color: 0xffffff, wireframe: true } );
+
+				var mesh = new THREE.Mesh( geometry, material );
+				mesh.position.x = - 100;
+				scene.add( mesh );
+
+				var texture = new THREE.TextureLoader().load( "textures/brick_diffuse.jpg" );
+				texture.minFilter = THREE.NearestFilter;
+				texture.magFilter = THREE.NearestFilter;
+				texture.anisotropy = 1;
+				texture.generateMipmaps = false;
+
+				var material = new THREE.MeshBasicMaterial( { map: texture } );
+
+				var mesh = new THREE.Mesh( geometry, material );
+				mesh.position.x = 100;
+				scene.add( mesh );
+
+				// postprocessing
+
+				composer = new THREE.EffectComposer( renderer );
+
+				taaRenderPass = new THREE.TAARenderPass( scene, camera );
+				composer.addPass( taaRenderPass );
+
+				renderPass = new THREE.RenderPass( scene, camera );
+				renderPass.enabled = false;
+				composer.addPass( renderPass );
+
+				copyPass = new THREE.ShaderPass( THREE.CopyShader );
+		    copyPass.renderToScreen = true;
+				composer.addPass( copyPass );
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+			}
+
+			function onWindowResize() {
+
+				var width = window.innerWidth;
+				var height = window.innerHeight;
+
+				camera.aspect = width / height;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( width, height );
+
+				var pixelRatio = renderer.getPixelRatio();
+				var newWidth  = Math.floor( width / pixelRatio ) || 1;
+				var newHeight = Math.floor( height / pixelRatio ) || 1;
+				composer.setSize( newWidth, newHeight );
+				taaRenderPass.setSize( newWidth, newHeight );
+
+			}
+
+			function animate() {
+
+				this.index = this.index || 0;
+
+				requestAnimationFrame( animate );
+
+				this.index ++;
+
+				if( Math.round( this.index / 200 ) % 2 === 0 ) {
+					for ( var i = 0; i < scene.children.length; i ++ ) {
+
+						var child = scene.children[ i ];
+
+						child.rotation.x += 0.005;
+						child.rotation.y += 0.01;
+
+					}
+					if( taaRenderPass ) taaRenderPass.accumulate = false;
+				}
+				else {
+					if( taaRenderPass ) taaRenderPass.accumulate = true;
+				}
+
+				composer.render();
+
+				stats.update();
+
+			}
+
+		</script>
+		<div>
+	</body>
+</html>