diff --git a/examples/files.json b/examples/files.json
index 4fca40924db47..efff05afe5379 100644
--- a/examples/files.json
+++ b/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",
diff --git a/examples/screenshots/webgl2_mrt.jpg b/examples/screenshots/webgl2_mrt.jpg
new file mode 100644
index 0000000000000..099d0ed451b10
Binary files /dev/null and b/examples/screenshots/webgl2_mrt.jpg differ
diff --git a/examples/webgl2_mrt.html b/examples/webgl2_mrt.html
new file mode 100644
index 0000000000000..2bc2d8e3d23df
--- /dev/null
+++ b/examples/webgl2_mrt.html
@@ -0,0 +1,277 @@
+
+
+
+ three.js webgl - Multiple Render Targets
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
threejs - WebGL - Multiple Render Targets
+ Renders geometry into a G-Buffer.
+ Visualized here is the color and normal data from the G-Buffer.
+ Created by
@mattdesl.
+
+
+
+
+
+
diff --git a/src/Three.js b/src/Three.js
index 7e3bb3e8b29ba..06a29a4c8fc9d 100644
--- a/src/Three.js
+++ b/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';
diff --git a/src/renderers/WebGLMultipleRenderTargets.js b/src/renderers/WebGLMultipleRenderTargets.js
new file mode 100644
index 0000000000000..73bd6d6738ec6
--- /dev/null
+++ b/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 };
diff --git a/src/renderers/WebGLRenderer.js b/src/renderers/WebGLRenderer.js
index 8444fa67b1df6..fb79ca660c8b8 100644
--- a/src/renderers/WebGLRenderer.js
+++ b/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 );
@@ -1857,7 +1863,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 );
diff --git a/src/renderers/webgl/WebGLCapabilities.js b/src/renderers/webgl/WebGLCapabilities.js
index f94b044f331ff..85d446fb7dc82 100644
--- a/src/renderers/webgl/WebGLCapabilities.js
+++ b/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
};
diff --git a/src/renderers/webgl/WebGLState.js b/src/renderers/webgl/WebGLState.js
index 597cecdcd4fc9..82e61a58eb6de 100644
--- a/src/renderers/webgl/WebGLState.js
+++ b/src/renderers/webgl/WebGLState.js
@@ -465,8 +465,12 @@ function WebGLState( gl, extensions, capabilities ) {
}
+ return true;
+
}
+ return false;
+
}
function useProgram( program ) {
diff --git a/src/renderers/webgl/WebGLTextures.js b/src/renderers/webgl/WebGLTextures.js
index 16e4d217e2cf5..30ea998c0de3f 100644
--- a/src/renderers/webgl/WebGLTextures.js
+++ b/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 );
+
+ }
}