Browse Source

MRT Support (#16390)

* MRT Support

* Add MRT example

* Rename AttachnemtsNum to NumAttachments

* Reflect the review comments to webgl2_mrt.html

* Define texture parameter via WebGLMultiRenderTarget constructor in MRT example

* Minor clean up

* Fix setupFrameBufferTextre(). Use texture.format/type instead of renderTarget.texture's

* Use module in MRT example

* Replace var with let or const

* Replace ShaderMaterial with RawShaderMaterial for MRT example

* Fix formatting

* Fix for lint

* Add MRT example screenshot

* Reflect the review comments for WebGLMultiRenderTarget

* Rename .textures to .texture in WebGLMultiRenderTarget

* WebGLMultiRenderTarget: Remove setCount()

* WebGLMultiRenderTarget: Method chain

* WebGLMultiRenderTarget: More proper setTexture() and copy()

* WebGLCapabilities: Use extensions.has() instead of .get()

* WebGLMultiRenderTarget: Remove setTexture()

* Rename WebGLMultiRenderTarget to WebGLMultipleRenderTargets
Takahiro 4 years ago
parent
commit
2510609955

+ 1 - 0
examples/files.json

@@ -309,6 +309,7 @@
 		"webgl2_materials_texture2darray",
 		"webgl2_materials_texture3d",
 		"webgl2_materials_texture3d_partialupdate",
+		"webgl2_mrt",
 		"webgl2_multisampled_renderbuffers",
 		"webgl2_rendertarget_texture2darray",
 		"webgl2_volume_cloud",

BIN
examples/screenshots/webgl2_mrt.jpg


+ 277 - 0
examples/webgl2_mrt.html

@@ -0,0 +1,277 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - Multiple Render Targets</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 {
+				font-family: Monospace;
+				background-color: #000;
+				color: #fff;
+				margin: 0px;
+				overflow: hidden;
+			}
+			#info {
+				color: #fff;
+				position: absolute;
+				top: 10px;
+				width: 100%;
+				text-align: center;
+				display:block;
+			}
+			#info a, .button { color: #f00; font-weight: bold; text-decoration: underline; cursor: pointer }
+		</style>
+
+		<!-- Write to G-Buffer -->
+		<script id="gbuffer-vert" type="x-shader/x-vertex">
+			in vec3 position;
+			in vec3 normal;
+			in vec2 uv;
+
+			out vec3 vNormal;
+			out vec2 vUv;
+
+			uniform mat4 modelViewMatrix;
+			uniform mat4 projectionMatrix;
+			uniform mat3 normalMatrix;
+
+			void main() {
+
+				vUv = uv;
+
+				// get smooth normals
+				vec4 mvPosition = modelViewMatrix * vec4( position, 1.0 );
+
+				vec3 transformedNormal = normalMatrix * normal;
+				vNormal = normalize( transformedNormal );
+
+				gl_Position = projectionMatrix * mvPosition;
+
+			}
+		</script>
+		<script id="gbuffer-frag" type="x-shader/x-fragment">
+			precision highp float;
+			precision highp int;
+
+			layout(location = 0) out vec4 gColor;
+			layout(location = 1) out vec4 gNormal;
+
+			uniform sampler2D tDiffuse;
+			uniform vec2 repeat;
+
+			in vec3 vNormal;
+			in vec2 vUv;
+
+			void main() {
+
+				// write color to G-Buffer
+				gColor = texture( tDiffuse, vUv * repeat );
+
+				// write normals to G-Buffer
+				gNormal = vec4( normalize( vNormal ), 0.0 );
+
+			}
+		</script>
+
+		<!-- Read G-Buffer and render to screen -->
+		<script id="render-vert" type="x-shader/x-vertex">
+			in vec3 position;
+			in vec2 uv;
+
+			out vec2 vUv;
+
+			uniform mat4 modelViewMatrix;
+			uniform mat4 projectionMatrix;
+
+			void main() {
+
+				vUv = uv;
+				gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
+
+			}
+		</script>
+		<script id="render-frag" type="x-shader/x-fragment">
+			precision highp float;
+			precision highp int;
+
+			layout(location = 0) out vec4 pc_FragColor;
+
+			in vec2 vUv;
+
+			uniform sampler2D tDiffuse;
+			uniform sampler2D tNormal;
+
+			void main() {
+
+				vec3 diffuse = texture( tDiffuse, vUv ).rgb;
+				vec3 normal = texture( tNormal, vUv ).rgb;
+
+				pc_FragColor.rgb = mix( diffuse, normal, step( 0.5, vUv.x ) );
+				pc_FragColor.a = 1.0;
+
+			}
+		</script>
+
+	</head>
+	<body>
+		<div id="info">
+			<a href="http://threejs.org" target="_blank">threejs</a> - WebGL - Multiple Render Targets<br/>
+			Renders geometry into a G-Buffer.<br/>
+			Visualized here is the color and normal data from the G-Buffer.<br/>
+			Created by <a href="http://twitter.com/mattdesl" target="_blank">@mattdesl</a>.
+		</div>
+
+		<script type="module">
+
+			import * as THREE from '../build/three.module.js';
+
+			import { WEBGL } from './jsm/WebGL.js';
+			import { OrbitControls } from './jsm/controls/OrbitControls.js';
+
+			let container;
+			let camera, scene, renderer, controls;
+			let renderTarget;
+			let postScene, postCamera;
+
+			init();
+
+			function init() {
+
+				if ( WEBGL.isWebGL2Available() === false ) {
+
+					document.body.appendChild( WEBGL.getWebGL2ErrorMessage() );
+					return;
+
+				}
+
+				container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				const canvas = document.createElement( 'canvas' );
+				const context = canvas.getContext( 'webgl2' );
+
+				renderer = new THREE.WebGLRenderer( {
+					canvas: canvas,
+					context: context
+				} );
+
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				container.appendChild( renderer.domElement );
+
+				// Create a multi render target with Float buffers
+
+				renderTarget = new THREE.WebGLMultipleRenderTargets(
+					window.innerWidth * window.devicePixelRatio,
+					window.innerHeight * window.devicePixelRatio,
+					2
+				);
+
+				for ( let i = 0, il = renderTarget.texture.length; i < il; i ++ ) {
+
+					renderTarget.texture[ i ].minFilter = THREE.NearestFilter;
+					renderTarget.texture[ i ].magFilter = THREE.NearestFilter;
+					renderTarget.texture[ i ].type = THREE.FloatType;
+
+				}
+
+				// Name our G-Buffer attachments for debugging
+
+				renderTarget.texture[ 0 ].name = 'diffuse';
+				renderTarget.texture[ 1 ].name = 'normal';
+
+				// Scene setup
+
+				scene = new THREE.Scene();
+
+				camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 10 );
+				camera.position.z = 4;
+
+				const diffuse = new THREE.TextureLoader().load(
+
+					'textures/brick_diffuse.jpg',
+
+					function () {
+
+						// ready to render
+						render();
+
+					}
+
+				);
+
+				diffuse.wrapS = diffuse.wrapT = THREE.RepeatWrapping;
+
+				scene.add( new THREE.Mesh(
+					new THREE.TorusKnotGeometry( 1, 0.3, 128, 64 ),
+					new THREE.RawShaderMaterial( {
+						vertexShader: document.querySelector( '#gbuffer-vert' ).textContent.trim(),
+						fragmentShader: document.querySelector( '#gbuffer-frag' ).textContent.trim(),
+						uniforms: {
+							tDiffuse: { value: diffuse },
+							repeat: { value: new THREE.Vector2( 5, 0.5 ) }
+						},
+						glslVersion: THREE.GLSL3
+					} )
+				) );
+
+				// PostProcessing setup
+
+				postScene = new THREE.Scene();
+				postCamera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
+
+				postScene.add( new THREE.Mesh(
+					new THREE.PlaneGeometry( 2, 2 ),
+					new THREE.RawShaderMaterial( {
+						vertexShader: document.querySelector( '#render-vert' ).textContent.trim(),
+						fragmentShader: document.querySelector( '#render-frag' ).textContent.trim(),
+						uniforms: {
+							tDiffuse: { value: renderTarget.texture[ 0 ] },
+							tNormal: { value: renderTarget.texture[ 1 ] },
+						},
+						glslVersion: THREE.GLSL3
+					} )
+				) );
+
+				// Controls
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.addEventListener( 'change', render );
+				controls.enableZoom = false;
+				controls.screenSpacePanning = true;
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				const dpr = renderer.getPixelRatio();
+				renderTarget.setSize( window.innerWidth * dpr, window.innerHeight * dpr );
+
+				render();
+
+			}
+
+			function render() {
+
+				// render scene into target
+				renderer.setRenderTarget( renderTarget );
+				renderer.render( scene, camera );
+
+				// render post FX
+				renderer.setRenderTarget( null );
+				renderer.render( postScene, postCamera );
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 1 - 0
src/Three.js

@@ -1,5 +1,6 @@
 import { REVISION } from './constants.js';
 
+export { WebGLMultipleRenderTargets } from './renderers/WebGLMultipleRenderTargets.js';
 export { WebGLMultisampleRenderTarget } from './renderers/WebGLMultisampleRenderTarget.js';
 export { WebGLCubeRenderTarget } from './renderers/WebGLCubeRenderTarget.js';
 export { WebGLRenderTarget } from './renderers/WebGLRenderTarget.js';

+ 79 - 0
src/renderers/WebGLMultipleRenderTargets.js

@@ -0,0 +1,79 @@
+import { WebGLRenderTarget } from './WebGLRenderTarget.js';
+
+class WebGLMultipleRenderTargets extends WebGLRenderTarget {
+
+	constructor( width, height, count ) {
+
+		super( width, height );
+
+		const texture = this.texture;
+
+		this.texture = [];
+
+		for ( let i = 0; i < count; i ++ ) {
+
+			this.texture[ i ] = texture.clone();
+
+		}
+
+	}
+
+	setSize( width, height, depth = 1 ) {
+
+		if ( this.width !== width || this.height !== height || this.depth !== depth ) {
+
+			this.width = width;
+			this.height = height;
+			this.depth = depth;
+
+			for ( let i = 0, il = this.texture.length; i < il; i ++ ) {
+
+				this.texture[ i ].image.width = width;
+				this.texture[ i ].image.height = height;
+				this.texture[ i ].image.depth = depth;
+
+			}
+
+			this.dispose();
+
+		}
+
+		this.viewport.set( 0, 0, width, height );
+		this.scissor.set( 0, 0, width, height );
+
+		return this;
+
+	}
+
+	copy( source ) {
+
+		this.dispose();
+
+		this.width = source.width;
+		this.height = source.height;
+		this.depth = source.depth;
+
+		this.viewport.set( 0, 0, this.width, this.height );
+		this.scissor.set( 0, 0, this.width, this.height );
+
+		this.depthBuffer = source.depthBuffer;
+		this.stencilBuffer = source.stencilBuffer;
+		this.depthTexture = source.depthTexture;
+
+		this.texture.length = 0;
+
+		for ( let i = 0, il = source.texture.length; i < il; i ++ ) {
+
+			this.texture[ i ] = source.texture[ i ].clone();
+
+		}
+
+		return this;
+
+	}
+
+}
+
+WebGLMultipleRenderTargets.prototype.isWebGLMultipleRenderTargets = true;
+
+export { WebGLMultipleRenderTargets };

+ 62 - 1
src/renderers/WebGLRenderer.js

@@ -149,6 +149,10 @@ function WebGLRenderer( parameters ) {
 	const _scissor = new Vector4( 0, 0, _width, _height );
 	let _scissorTest = false;
 
+	//
+
+	const _currentDrawBuffers = [];
+
 	// frustum
 
 	const _frustum = new Frustum();
@@ -275,6 +279,8 @@ function WebGLRenderer( parameters ) {
 
 		state = new WebGLState( _gl, extensions, capabilities );
 
+		_currentDrawBuffers[ 0 ] = _gl.BACK;
+
 		info = new WebGLInfo( _gl );
 		properties = new WebGLProperties();
 		textures = new WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info );
@@ -1846,7 +1852,62 @@ function WebGLRenderer( parameters ) {
 
 		}
 
-		state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer );
+		if ( state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ) && capabilities.multiRenderTarget ) {
+
+			let needsUpdate = false;
+
+			if ( renderTarget && renderTarget.isWebGLMultipleRenderTargets ) {
+
+				if ( _currentDrawBuffers.length !== renderTarget.texture.length || _currentDrawBuffers[ 0 ] !== _gl.COLOR_ATTACHMENT0 ) {
+
+					for ( let i = 0, il = renderTarget.texture.length; i < il; i ++ ) {
+
+						_currentDrawBuffers[ i ] = _gl.COLOR_ATTACHMENT0 + i;
+
+					}
+
+					_currentDrawBuffers.length = renderTarget.texture.length;
+					needsUpdate = true;
+
+				}
+
+			} else if ( renderTarget ) {
+
+				if ( _currentDrawBuffers.length !== 1 || _currentDrawBuffers[ 0 ] !== _gl.COLOR_ATTACHMENT0 ) {
+
+					_currentDrawBuffers[ 0 ] = _gl.COLOR_ATTACHMENT0;
+					_currentDrawBuffers.length = 1;
+					needsUpdate = true;
+
+				}
+
+			} else {
+
+				if ( _currentDrawBuffers.length !== 1 || _currentDrawBuffers[ 0 ] !== _gl.BACK ) {
+
+					_currentDrawBuffers[ 0 ] = _gl.BACK;
+					_currentDrawBuffers.length = 1;
+					needsUpdate = true;
+
+				}
+
+			}
+
+			if ( needsUpdate ) {
+
+				if ( capabilities.isWebGL2 ) {
+
+					_gl.drawBuffers( _currentDrawBuffers );
+
+				} else {
+
+					extensions.get( 'WEBGL_draw_buffers' ).drawBuffersWEBGL( _currentDrawBuffers );
+
+				}
+
+			}
+
+		}
 
 		state.viewport( _currentViewport );
 		state.scissor( _currentScissor );

+ 3 - 1
src/renderers/webgl/WebGLCapabilities.js

@@ -84,6 +84,7 @@ function WebGLCapabilities( gl, extensions, parameters ) {
 	const floatVertexTextures = vertexTextures && floatFragmentTextures;
 
 	const maxSamples = isWebGL2 ? gl.getParameter( gl.MAX_SAMPLES ) : 0;
+	const multiRenderTarget = isWebGL2 || extensions.has( 'WEBGL_draw_buffers' );
 
 	return {
 
@@ -109,7 +110,8 @@ function WebGLCapabilities( gl, extensions, parameters ) {
 		floatFragmentTextures: floatFragmentTextures,
 		floatVertexTextures: floatVertexTextures,
 
-		maxSamples: maxSamples
+		maxSamples: maxSamples,
+		multiRenderTarget: multiRenderTarget
 
 	};
 

+ 4 - 0
src/renderers/webgl/WebGLState.js

@@ -465,8 +465,12 @@ function WebGLState( gl, extensions, capabilities ) {
 
 			}
 
+			return true;
+
 		}
 
+		return false;
+
 	}
 
 	function useProgram( program ) {

+ 96 - 20
src/renderers/webgl/WebGLTextures.js

@@ -223,8 +223,6 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 		deallocateRenderTarget( renderTarget );
 
-		info.memory.textures --;
-
 	}
 
 	//
@@ -254,6 +252,8 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 			_gl.deleteTexture( textureProperties.__webglTexture );
 
+			info.memory.textures --;
+
 		}
 
 		if ( renderTarget.depthTexture ) {
@@ -281,6 +281,26 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 		}
 
+		if ( renderTarget.isWebGLMultipleRenderTargets ) {
+
+			for ( let i = 0, il = texture.length; i < il; i ++ ) {
+
+				const attachmentProperties = properties.get( texture[ i ] );
+
+				if ( attachmentProperties.__webglTexture ) {
+
+					_gl.deleteTexture( attachmentProperties.__webglTexture );
+
+					info.memory.textures --;
+
+				}
+
+				properties.remove( texture[ i ] );
+
+			}
+
+		}
+
 		properties.remove( texture );
 		properties.remove( renderTarget );
 
@@ -833,9 +853,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 	// Render targets
 
 	// Setup storage for target texture and bind it to correct framebuffer
-	function setupFrameBufferTexture( framebuffer, renderTarget, attachment, textureTarget ) {
-
-		const texture = renderTarget.texture;
+	function setupFrameBufferTexture( framebuffer, renderTarget, texture, attachment, textureTarget ) {
 
 		const glFormat = utils.convert( texture.format );
 		const glType = utils.convert( texture.type );
@@ -915,7 +933,8 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 		} else {
 
-			const texture = renderTarget.texture;
+			// Use the first texture for MRT so far
+			const texture = renderTarget.isWebGLMultipleRenderTargets === true ? renderTarget.texture[ 0 ] : renderTarget.texture;
 
 			const glFormat = utils.convert( texture.format );
 			const glType = utils.convert( texture.type );
@@ -1035,12 +1054,16 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 		renderTarget.addEventListener( 'dispose', onRenderTargetDispose );
 
-		textureProperties.__webglTexture = _gl.createTexture();
-		textureProperties.__version = texture.version;
+		if ( renderTarget.isWebGLMultipleRenderTargets !== true ) {
 
-		info.memory.textures ++;
+			textureProperties.__webglTexture = _gl.createTexture();
+			textureProperties.__version = texture.version;
+			info.memory.textures ++;
+
+		}
 
 		const isCube = ( renderTarget.isWebGLCubeRenderTarget === true );
+		const isMultiRenderTarget = ( renderTarget.isWebGLMultipleRenderTargets === true );
 		const isMultisample = ( renderTarget.isWebGLMultisampleRenderTarget === true );
 		const isRenderTarget3D = texture.isDataTexture3D || texture.isDataTexture2DArray;
 		const supportsMips = isPowerOfTwo( renderTarget ) || isWebGL2;
@@ -1071,7 +1094,31 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 			renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer();
 
-			if ( isMultisample ) {
+			if ( isMultiRenderTarget ) {
+
+				if ( capabilities.multiRenderTarget ) {
+
+					for ( let i = 0, il = renderTarget.texture.length; i < il; i ++ ) {
+
+						const attachmentProperties = properties.get( renderTarget.texture[ i ] );
+
+						if ( attachmentProperties.__webglTexture === undefined ) {
+
+							attachmentProperties.__webglTexture = _gl.createTexture();
+
+							info.memory.textures ++;
+
+						}
+
+					}
+
+				} else {
+
+					console.warn( 'THREE.WebGLRenderer: WebGLMultipleRenderTargets can only be used with WebGL2 or WEBGL_draw_buffers extension.' );
+
+				}
+
+			} else if ( isMultisample ) {
 
 				if ( isWebGL2 ) {
 
@@ -1119,7 +1166,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 			for ( let i = 0; i < 6; i ++ ) {
 
-				setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i );
+				setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, texture, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i );
 
 			}
 
@@ -1131,6 +1178,29 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 			state.bindTexture( _gl.TEXTURE_CUBE_MAP, null );
 
+		} else if ( isMultiRenderTarget ) {
+
+			const texture = renderTarget.texture;
+
+			for ( let i = 0, il = texture.length; i < il; i ++ ) {
+
+				const attachment = texture[ i ];
+				const attachmentProperties = properties.get( attachment );
+
+				state.bindTexture( _gl.TEXTURE_2D, attachmentProperties.__webglTexture );
+				setTextureParameters( _gl.TEXTURE_2D, attachment, supportsMips );
+				setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, attachment, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D );
+
+				if ( textureNeedsGenerateMipmaps( attachment, supportsMips ) ) {
+
+					generateMipmap( _gl.TEXTURE_2D, attachment, renderTarget.width, renderTarget.height );
+
+				}
+
+			}
+
+			state.bindTexture( _gl.TEXTURE_2D, null );
+
 		} else {
 
 			let glTextureType = _gl.TEXTURE_2D;
@@ -1154,7 +1224,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 			state.bindTexture( glTextureType, textureProperties.__webglTexture );
 			setTextureParameters( glTextureType, texture, supportsMips );
-			setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, _gl.COLOR_ATTACHMENT0, glTextureType );
+			setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, texture, _gl.COLOR_ATTACHMENT0, glTextureType );
 
 			if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {
 
@@ -1178,18 +1248,24 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 	function updateRenderTargetMipmap( renderTarget ) {
 
-		const texture = renderTarget.texture;
-
 		const supportsMips = isPowerOfTwo( renderTarget ) || isWebGL2;
 
-		if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {
+		const textures = renderTarget.isWebGLMultipleRenderTargets === true ? renderTarget.texture : [ renderTarget.texture ];
+
+		for ( let i = 0, il = textures.length; i < il; i ++ ) {
+
+			const texture = textures[ i ];
 
-			const target = renderTarget.isWebGLCubeRenderTarget ? _gl.TEXTURE_CUBE_MAP : _gl.TEXTURE_2D;
-			const webglTexture = properties.get( texture ).__webglTexture;
+			if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {
+
+				const target = renderTarget.isWebGLCubeRenderTarget ? _gl.TEXTURE_CUBE_MAP : _gl.TEXTURE_2D;
+				const webglTexture = properties.get( texture ).__webglTexture;
 
-			state.bindTexture( target, webglTexture );
-			generateMipmap( target, texture, renderTarget.width, renderTarget.height );
-			state.bindTexture( target, null );
+				state.bindTexture( target, webglTexture );
+				generateMipmap( target, texture, renderTarget.width, renderTarget.height );
+				state.bindTexture( target, null );
+
+			}
 
 		}