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 ); + + } }