Browse Source

WebGLMultipleRenderTargets: Add multisampling support. (#24001)

* setUsage does not update attributes after the buffer is created

* check is not necessary in the buffer creation

* clean code

* prevent unnecessary double buffer update

* fix lint

* [WebGLMultipleRenderTarget] Support Multisampling

[WebGLMultipleRenderTarget] Support Multisampling

clean bufferattributes code

clean bufferattributes code

* removed unused code

* added build for tests

* pupeeter screenshot

* better example

* resets the active FBO back to the multisampled FBO after blitting.

* lint

* use mask instead of COLOR_BUFFER_BIT

* Update webgl2_multiple_rendertargets_multisampled.html

* wip fix safari

* removed use of temporary buffers and fix safari and ios example

* fix lint

* fix pupeeter example?

* prevent unecessary fbo creation for non-mrt

* fix

* fix puppeeter rendering black screenshot

* remove build

* Update webgl2_multiple_rendertargets_multisampled.html

Create `DepthTexture` without parameters.

* remove unecessary framebuffer render and texture instructions

* example: usage of stencil buffer instead of depth texture

Co-authored-by: Michael Herzog <[email protected]>
Renaud Rohlinger 3 năm trước cách đây
mục cha
commit
714a21be0b

+ 1 - 0
examples/files.json

@@ -300,6 +300,7 @@
 		"webgl2_materials_texture3d",
 		"webgl2_materials_texture3d_partialupdate",
 		"webgl2_multiple_rendertargets",
+		"webgl2_multiple_rendertargets_multisampled",
 		"webgl2_multisampled_renderbuffers",
 		"webgl2_rendertarget_texture2darray",
 		"webgl2_volume_cloud",

BIN
examples/screenshots/webgl2_multiple_rendertargets_multisampled.jpg


+ 1 - 0
examples/tags.json

@@ -93,6 +93,7 @@
 	"webgl_simple_gi": [ "global illumination" ],
 	"webgl_tiled_forward": [ "derivatives" ],
 	"webgl2_multiple_rendertargets": [ "mrt" ],
+	"webgl2_multiple_rendertargets_multisampled": [ "mrt" ],
 	"webgl2_multisampled_renderbuffers": [ "msaa" ],
 	"physics_ammo_cloth": [ "integration" ],
 	"misc_controls_arcball": [ "rotation" ],

+ 337 - 0
examples/webgl2_multiple_rendertargets_multisampled.html

@@ -0,0 +1,337 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - Multiple Render Targets MultiSampled</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link type="text/css" rel="stylesheet" href="main.css">
+
+		<!-- 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 = vec4(vec3(0.), 1.);
+				pc_FragColor.rgb += mix( diffuse, normal, step( 0.5, vUv.x ) );
+
+			}
+		</script>
+
+	</head>
+	<body>
+		<div id="info">
+			<a href="http://threejs.org" target="_blank">threejs</a> - WebGL - Multiple Render Targets Multisampled<br/>
+			Created by <a href="http://twitter.com/onirenaud" target="_blank">@onirenaud</a> with the help of <a href="http://twitter.com/Cody_J_Bennett" target="_blank">@Cody_J_Bennett</a>.
+		</div>
+		<div id="container">
+		</div>
+		<!-- Import maps polyfill -->
+		<!-- Remove this when import maps will be widely supported -->
+		<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			import WebGL from './jsm/capabilities/WebGL.js';
+			import { OrbitControls } from './jsm/controls/OrbitControls.js';
+			import { GUI } from './jsm/libs/lil-gui.module.min.js';
+			import Stats from './jsm/libs/stats.module.js';
+
+			let camera, scene, renderer, controls, container, group;
+			let renderTarget;
+			let postScene, postCamera;
+			let stats;
+
+			const clock = new THREE.Clock();
+			// Create a multi render target with Float buffers
+			const gui = new GUI();
+
+			const effectController = {
+				msaa: true
+			};
+
+			init();
+			gui.add( effectController, 'msaa', true ).onChange( () => {
+
+				renderTarget.samples = effectController.msaa ? 4 : 0;
+				render();
+
+			} );
+
+			function init() {
+
+				container = document.getElementById( 'container' );
+
+				if ( WebGL.isWebGL2Available() === false ) {
+
+					document.body.appendChild( WebGL.getWebGL2ErrorMessage() );
+					return;
+
+				}
+
+				renderer = new THREE.WebGLRenderer();
+				renderer.setPixelRatio( 1 );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				container.appendChild( renderer.domElement );
+
+				stats = new Stats();
+				stats.domElement.style.position = 'absolute';
+				stats.domElement.style.top = '0px';
+				container.appendChild( stats.domElement );
+
+
+				renderTarget = new THREE.WebGLMultipleRenderTargets(
+					window.innerWidth,
+					window.innerHeight,
+					2,
+					{
+						samples: effectController.msaa ? 4 : 0,
+						depthBuffer: true,
+						stencilBuffer: true,
+					}
+				);
+
+				for ( let i = 0, il = renderTarget.texture.length; i < il; i ++ ) {
+
+					renderTarget.texture[ i ].minFilter = THREE.NearestFilter;
+					renderTarget.texture[ i ].magFilter = THREE.NearestFilter;
+
+				}
+				// Name our G-Buffer attachments for debugging
+
+				renderTarget.texture[ 0 ].name = 'diffuse';
+				renderTarget.texture[ 1 ].name = 'normal';
+
+				// Scene setup
+
+				scene = new THREE.Scene();
+				scene.fog = new THREE.Fog( 0xa0a0a0, 500, 2000 );
+
+				const groundMat = new THREE.MeshBasicMaterial( { color: 0xffffff, transparent: true } );
+				groundMat.onBeforeCompile = function ( shader ) {
+
+					shader.fragmentShader = `
+						layout(location = 1) out vec4 gOther;
+						${shader.fragmentShader}
+					`;
+					shader.fragmentShader = shader.fragmentShader.replace(
+						'#include <fog_fragment>',
+						`
+						#include <fog_fragment>
+						gOther = gl_FragColor;
+						`
+					);
+
+				};
+
+				const ground = new THREE.Mesh( new THREE.PlaneGeometry( 10000, 10000 ), groundMat );
+				ground.position.y = - 100;
+				ground.rotation.x = - Math.PI / 2;
+				scene.add( ground );
+				scene.background = new THREE.Color( 0xa0a0a0 );
+
+				container = document.getElementById( 'container' );
+
+				camera = new THREE.PerspectiveCamera( 45, container.offsetWidth / container.offsetHeight, 1, 2000 );
+				camera.position.z = 500;
+
+				const diffuse = new THREE.TextureLoader().load(
+
+					'textures/brick_diffuse.jpg'
+
+				);
+
+				diffuse.wrapS = diffuse.wrapT = THREE.RepeatWrapping;
+
+				group = new THREE.Group();
+
+				const geometry = new THREE.TorusKnotGeometry( 10, 3.3, 12, 32 );
+			
+				for ( let i = 0; i < 10; i ++ ) {
+
+					const material = 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 ) }
+						},
+						wireframe: i % 2 === 0,
+						glslVersion: THREE.GLSL3
+					} );
+
+					const mesh = new THREE.Mesh( geometry, material );
+					mesh.position.x = ( i / 10 ) * 600 - 300;
+					mesh.position.y = ( i / 10 ) * 200 - 100;
+					mesh.position.z = Math.random() * 600 - 300;
+					mesh.rotation.x = Math.random();
+					mesh.rotation.z = Math.random();
+					mesh.scale.setScalar( Math.random() * 5 + 5 );
+					group.add( mesh );
+
+
+				}
+
+				scene.add( group );
+				// 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 );
+
+				animate();
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				renderTarget.setSize( window.innerWidth, window.innerHeight );
+
+				render();
+
+			}
+
+			function render() {
+
+				// render scene into target
+				renderer.setRenderTarget( renderTarget );
+				renderer.render( scene, camera );
+
+				// render post FX
+				renderer.setRenderTarget( null );
+				renderer.render( postScene, postCamera );
+
+			}
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				controls.update();
+
+				group.rotation.y += clock.getDelta() * 0.1;
+
+				stats.update();
+				render();
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 124 - 40
src/renderers/webgl/WebGLTextures.js

@@ -343,7 +343,17 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 			_gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer );
 			if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer );
 			if ( renderTargetProperties.__webglMultisampledFramebuffer ) _gl.deleteFramebuffer( renderTargetProperties.__webglMultisampledFramebuffer );
-			if ( renderTargetProperties.__webglColorRenderbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglColorRenderbuffer );
+
+			if ( renderTargetProperties.__webglColorRenderbuffer ) {
+
+				for ( let i = 0; i < renderTargetProperties.__webglColorRenderbuffer.length; i ++ ) {
+
+					if ( renderTargetProperties.__webglColorRenderbuffer[ i ] ) _gl.deleteRenderbuffer( renderTargetProperties.__webglColorRenderbuffer[ i ] );
+
+				}
+
+			}
+
 			if ( renderTargetProperties.__webglDepthRenderbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthRenderbuffer );
 
 		}
@@ -1338,25 +1348,30 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 		} else {
 
-			// Use the first texture for MRT so far
-			const texture = renderTarget.isWebGLMultipleRenderTargets === true ? renderTarget.texture[ 0 ] : renderTarget.texture;
+			const textures = renderTarget.isWebGLMultipleRenderTargets === true ? renderTarget.texture : [ renderTarget.texture ];
 
-			const glFormat = utils.convert( texture.format, texture.encoding );
-			const glType = utils.convert( texture.type );
-			const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.encoding );
-			const samples = getRenderTargetSamples( renderTarget );
+			for ( let i = 0; i < textures.length; i ++ ) {
 
-			if ( isMultisample && useMultisampledRTT( renderTarget ) === false ) {
+				const texture = textures[ i ];
 
-				_gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height );
+				const glFormat = utils.convert( texture.format, texture.encoding );
+				const glType = utils.convert( texture.type );
+				const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.encoding );
+				const samples = getRenderTargetSamples( renderTarget );
 
-			} else if ( useMultisampledRTT( renderTarget ) ) {
+				if ( isMultisample && useMultisampledRTT( renderTarget ) === false ) {
+
+					_gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height );
 
-				multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height );
+				} else if ( useMultisampledRTT( renderTarget ) ) {
 
-			} else {
+					multisampledRTTExt.renderbufferStorageMultisampleEXT( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height );
 
-				_gl.renderbufferStorage( _gl.RENDERBUFFER, glInternalFormat, renderTarget.width, renderTarget.height );
+				} else {
+
+					_gl.renderbufferStorage( _gl.RENDERBUFFER, glInternalFormat, renderTarget.width, renderTarget.height );
+
+				}
 
 			}
 
@@ -1556,21 +1571,34 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 				}
 
-			} else if ( ( isWebGL2 && renderTarget.samples > 0 ) && useMultisampledRTT( renderTarget ) === false ) {
+			}
 
-				renderTargetProperties.__webglMultisampledFramebuffer = _gl.createFramebuffer();
-				renderTargetProperties.__webglColorRenderbuffer = _gl.createRenderbuffer();
+			if ( ( isWebGL2 && renderTarget.samples > 0 ) && useMultisampledRTT( renderTarget ) === false ) {
 
-				_gl.bindRenderbuffer( _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer );
+				const textures = isMultipleRenderTargets ? texture : [ texture ];
 
-				const glFormat = utils.convert( texture.format, texture.encoding );
-				const glType = utils.convert( texture.type );
-				const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.encoding );
-				const samples = getRenderTargetSamples( renderTarget );
-				_gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height );
+				renderTargetProperties.__webglMultisampledFramebuffer = _gl.createFramebuffer();
+				renderTargetProperties.__webglColorRenderbuffer = [];
 
 				state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer );
-				_gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer );
+
+				for ( let i = 0; i < textures.length; i ++ ) {
+
+					const texture = textures[ i ];
+					renderTargetProperties.__webglColorRenderbuffer[ i ] = _gl.createRenderbuffer();
+
+					_gl.bindRenderbuffer( _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] );
+
+					const glFormat = utils.convert( texture.format, texture.encoding );
+					const glType = utils.convert( texture.type );
+					const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.encoding );
+					const samples = getRenderTargetSamples( renderTarget );
+					_gl.renderbufferStorageMultisample( _gl.RENDERBUFFER, samples, glInternalFormat, renderTarget.width, renderTarget.height );
+
+					_gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] );
+
+				}
+
 				_gl.bindRenderbuffer( _gl.RENDERBUFFER, null );
 
 				if ( renderTarget.depthBuffer ) {
@@ -1701,47 +1729,103 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils,
 
 		if ( ( isWebGL2 && renderTarget.samples > 0 ) && useMultisampledRTT( renderTarget ) === false ) {
 
+			const textures = renderTarget.isWebGLMultipleRenderTargets ? renderTarget.texture : [ renderTarget.texture ];
 			const width = renderTarget.width;
 			const height = renderTarget.height;
 			let mask = _gl.COLOR_BUFFER_BIT;
-			const invalidationArray = [ _gl.COLOR_ATTACHMENT0 ];
+			const invalidationArray = [];
 			const depthStyle = renderTarget.stencilBuffer ? _gl.DEPTH_STENCIL_ATTACHMENT : _gl.DEPTH_ATTACHMENT;
+			const renderTargetProperties = properties.get( renderTarget );
+			const isMultipleRenderTargets = ( renderTarget.isWebGLMultipleRenderTargets === true );
 
-			if ( renderTarget.depthBuffer ) {
-
-				invalidationArray.push( depthStyle );
+			// If MRT we need to remove FBO attachments
+			if ( isMultipleRenderTargets ) {
 
-			}
+				for ( let i = 0; i < textures.length; i ++ ) {
 
-			const renderTargetProperties = properties.get( renderTarget );
-			const ignoreDepthValues = ( renderTargetProperties.__ignoreDepthValues !== undefined ) ? renderTargetProperties.__ignoreDepthValues : false;
+					state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer );
+					_gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, null );
 
-			if ( ignoreDepthValues === false ) {
+					state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer );
+					_gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, null, 0 );
 
-				if ( renderTarget.depthBuffer ) mask |= _gl.DEPTH_BUFFER_BIT;
-				if ( renderTarget.stencilBuffer ) mask |= _gl.STENCIL_BUFFER_BIT;
+				}
 
 			}
 
 			state.bindFramebuffer( _gl.READ_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer );
 			state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglFramebuffer );
 
-			if ( ignoreDepthValues === true ) {
+			for ( let i = 0; i < textures.length; i ++ ) {
 
-				_gl.invalidateFramebuffer( _gl.READ_FRAMEBUFFER, [ depthStyle ] );
-				_gl.invalidateFramebuffer( _gl.DRAW_FRAMEBUFFER, [ depthStyle ] );
+				invalidationArray.push( _gl.COLOR_ATTACHMENT0 + i );
 
-			}
+				if ( renderTarget.depthBuffer ) {
+
+					invalidationArray.push( depthStyle );
+
+				}
+
+				const ignoreDepthValues = ( renderTargetProperties.__ignoreDepthValues !== undefined ) ? renderTargetProperties.__ignoreDepthValues : false;
 
-			_gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, mask, _gl.NEAREST );
+				if ( ignoreDepthValues === false ) {
 
-			if ( supportsInvalidateFramebuffer ) {
+					if ( renderTarget.depthBuffer ) mask |= _gl.DEPTH_BUFFER_BIT;
+					if ( renderTarget.stencilBuffer ) mask |= _gl.STENCIL_BUFFER_BIT;
+
+				}
+
+				if ( isMultipleRenderTargets ) {
+
+					_gl.framebufferRenderbuffer( _gl.READ_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] );
+
+				}
+
+				if ( ignoreDepthValues === true ) {
+
+					_gl.invalidateFramebuffer( _gl.READ_FRAMEBUFFER, [ depthStyle ] );
+					_gl.invalidateFramebuffer( _gl.DRAW_FRAMEBUFFER, [ depthStyle ] );
+
+				}
+
+				if ( isMultipleRenderTargets ) {
+
+					const webglTexture = properties.get( textures[ i ] ).__webglTexture;
+					_gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D, webglTexture, 0 );
+
+				}
+
+				_gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, mask, _gl.NEAREST );
+
+				if ( supportsInvalidateFramebuffer ) {
+
+					_gl.invalidateFramebuffer( _gl.READ_FRAMEBUFFER, invalidationArray );
+
+				}
 
-				_gl.invalidateFramebuffer( _gl.READ_FRAMEBUFFER, invalidationArray );
 
 			}
 
 			state.bindFramebuffer( _gl.READ_FRAMEBUFFER, null );
+			state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, null );
+
+			// If MRT since pre-blit we removed the FBO we need to reconstruct the attachments
+			if ( isMultipleRenderTargets ) {
+
+				for ( let i = 0; i < textures.length; i ++ ) {
+
+					state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer );
+					_gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.RENDERBUFFER, renderTargetProperties.__webglColorRenderbuffer[ i ] );
+
+					const webglTexture = properties.get( textures[ i ] ).__webglTexture;
+
+					state.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer );
+					_gl.framebufferTexture2D( _gl.DRAW_FRAMEBUFFER, _gl.COLOR_ATTACHMENT0 + i, _gl.TEXTURE_2D, webglTexture, 0 );
+
+				}
+
+			}
+
 			state.bindFramebuffer( _gl.DRAW_FRAMEBUFFER, renderTargetProperties.__webglMultisampledFramebuffer );
 
 		}