From c9b978996a045681ef18ca83c71546efa8e6c4ff Mon Sep 17 00:00:00 2001 From: mbredif Date: Mon, 5 Jul 2021 16:44:34 +0200 Subject: [PATCH] WebGLCubeRenderTarget.depthTexture --- examples/webgl_materials_cubemap_dynamic.html | 32 ++- src/cameras/CubeCamera.js | 14 ++ src/renderers/WebGLCubeRenderTarget.js | 4 - src/renderers/WebGLRenderer.js | 46 ++-- src/renderers/webgl/WebGLTextures.js | 199 ++++++++---------- src/textures/CubeTexture.js | 10 +- 6 files changed, 160 insertions(+), 145 deletions(-) diff --git a/examples/webgl_materials_cubemap_dynamic.html b/examples/webgl_materials_cubemap_dynamic.html index bc60a704806482..e92e9ef18e3367 100644 --- a/examples/webgl_materials_cubemap_dynamic.html +++ b/examples/webgl_materials_cubemap_dynamic.html @@ -62,8 +62,17 @@ minFilter: THREE.LinearMipmapLinearFilter, encoding: THREE.sRGBEncoding // temporary -- to prevent the material's shader from recompiling every frame } ); - - cubeCamera1 = new THREE.CubeCamera( 1, 1000, cubeRenderTarget1 ); + cubeRenderTarget1.stencilBuffer = false; + cubeRenderTarget1.depthBuffer = true; + cubeRenderTarget1.depthTexture = new THREE.CubeTexture(); + cubeRenderTarget1.depthTexture.format = THREE.DepthFormat; + cubeRenderTarget1.depthTexture.type = THREE.UnsignedShortType; + cubeRenderTarget1.depthTexture.minFilter = THREE.NearestFilter; + cubeRenderTarget1.depthTexture.magFilter = THREE.NearestFilter; + cubeRenderTarget1.depthTexture.generateMipmaps = false; + cubeRenderTarget1.depthTexture._needsFlipEnvMap = false; + + cubeCamera1 = new THREE.CubeCamera( 10, 100, cubeRenderTarget1 ); cubeRenderTarget2 = new THREE.WebGLCubeRenderTarget( 256, { format: THREE.RGBFormat, @@ -71,10 +80,17 @@ minFilter: THREE.LinearMipmapLinearFilter, encoding: THREE.sRGBEncoding } ); - - cubeCamera2 = new THREE.CubeCamera( 1, 1000, cubeRenderTarget2 ); - - // + cubeRenderTarget2.stencilBuffer = false; + cubeRenderTarget2.depthBuffer = true; + cubeRenderTarget2.depthTexture = new THREE.CubeTexture(); + cubeRenderTarget2.depthTexture.format = THREE.DepthFormat; + cubeRenderTarget2.depthTexture.type = THREE.FloatType; + cubeRenderTarget2.depthTexture.minFilter = THREE.NearestFilter; + cubeRenderTarget2.depthTexture.magFilter = THREE.NearestFilter; + cubeRenderTarget2.depthTexture.generateMipmaps = false; + cubeRenderTarget2.depthTexture._needsFlipEnvMap = false; + + cubeCamera2 = new THREE.CubeCamera( 10, 100, cubeRenderTarget2 ); material = new THREE.MeshBasicMaterial( { envMap: cubeRenderTarget2.texture, @@ -190,12 +206,12 @@ if ( count % 2 === 0 ) { cubeCamera1.update( renderer, scene ); - material.envMap = cubeRenderTarget1.texture; + material.envMap = cubeRenderTarget1.depthTexture; } else { cubeCamera2.update( renderer, scene ); - material.envMap = cubeRenderTarget2.texture; + material.envMap = cubeRenderTarget2.depthTexture; } diff --git a/src/cameras/CubeCamera.js b/src/cameras/CubeCamera.js index b19ff7dbd05761..2489b9649bb63a 100644 --- a/src/cameras/CubeCamera.js +++ b/src/cameras/CubeCamera.js @@ -80,6 +80,14 @@ class CubeCamera extends Object3D { } + let generateDepthMipmaps = false; + if ( renderTarget.depthTexture ) { + + generateDepthMipmaps = renderTarget.depthTexture.generateMipmaps; + renderTarget.depthTexture.generateMipmaps = false; + + } + renderer.setRenderTarget( renderTarget, 0 ); renderer.render( scene, cameraPX ); @@ -102,6 +110,12 @@ class CubeCamera extends Object3D { } + if ( renderTarget.depthTexture ) { + + renderTarget.depthTexture.generateMipmaps = generateDepthMipmaps; + + } + renderer.setRenderTarget( renderTarget, 5 ); renderer.render( scene, cameraNZ ); diff --git a/src/renderers/WebGLCubeRenderTarget.js b/src/renderers/WebGLCubeRenderTarget.js index 847d4dbf13a358..893e87c42d43a2 100644 --- a/src/renderers/WebGLCubeRenderTarget.js +++ b/src/renderers/WebGLCubeRenderTarget.js @@ -25,10 +25,6 @@ class WebGLCubeRenderTarget extends WebGLRenderTarget { options.texture = new CubeTexture( undefined, options.mapping, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.encoding ); - } - - if ( options.texture._needsFlipEnvMap === undefined ) { - options.texture._needsFlipEnvMap = false; } diff --git a/src/renderers/WebGLRenderer.js b/src/renderers/WebGLRenderer.js index 199b5d5c30defe..9e927d4fd67fa7 100644 --- a/src/renderers/WebGLRenderer.js +++ b/src/renderers/WebGLRenderer.js @@ -1918,22 +1918,30 @@ function WebGLRenderer( parameters = {} ) { state.scissor( _currentScissor ); state.setScissorTest( _currentScissorTest ); + // select active layer and level for 2D array textures and 3D textures + // 2D and textures need no update + // * framebufferTexture2D's level is fixed to 0 + // * the active cube face is already selected by the active face framebuffer if ( renderTarget ) { + const level = activeMipmapLevel || 0; + const layer = activeCubeFace || 0; const textures = renderTarget.textures; for ( let i = 0, il = textures.length; i < il; i ++ ) { + const texture = textures[ i ]; - const __webglTexture = properties.get( texture ).__webglTexture; - const attachment = _gl.COLOR_ATTACHMENT0 + i; if ( texture.isDataTexture3D || texture.isDataTexture2DArray ) { - _gl.framebufferTextureLayer( _gl.FRAMEBUFFER, attachment, __webglTexture, activeMipmapLevel, activeCubeFace ); + const __webglTexture = properties.get( texture ).__webglTexture; + const attachment = _gl.COLOR_ATTACHMENT0 + i; + _gl.framebufferTextureLayer( _gl.FRAMEBUFFER, attachment, __webglTexture, level, layer ); } + } - const texture = renderTarget.depthTexture; - const __webglTexture = properties.get( texture ).__webglTexture; + const texture = renderTarget.depthTexture; + if ( texture && ( texture.isDataTexture3D || texture.isDataTexture2DArray ) ) { let attachment = undefined; if ( texture.format === DepthFormat ) { @@ -1946,33 +1954,25 @@ function WebGLRenderer( parameters = {} ) { } else { + throw new Error( 'Unsupported depthTexture format' ); - if ( renderTarget.depthTexture ) { - - const texture = renderTarget.depthTexture; - if ( texture.isDataTexture3D || texture.isDataTexture2DArray ) { - - let attachment = undefined; - if ( texture.format === DepthFormat ) { - - attachment = _gl.DEPTH_ATTACHMENT; + } - } else if ( texture.format === DepthStencilFormat ) { + const __webglTexture = properties.get( texture ).__webglTexture; + _gl.framebufferTextureLayer( _gl.FRAMEBUFFER, attachment, __webglTexture, level, layer ); - attachment = _gl.DEPTH_STENCIL_ATTACHMENT; + } - } else { + } - throw new Error( 'Unknown depthTexture format' ); - } + const framebufferStatus = _gl.checkFramebufferStatus( _gl.FRAMEBUFFER ); + if ( framebufferStatus !== _gl.FRAMEBUFFER_COMPLETE ) { - const __webglTexture = properties.get( texture ).__webglTexture; - _gl.framebufferTextureLayer( _gl.FRAMEBUFFER, attachment, __webglTexture, activeMipmapLevel, activeCubeFace ); + console.log( 'incomplete framebuffer', framebufferStatus, framebuffer ); - } - } } + }; this.readRenderTargetPixels = function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex ) { diff --git a/src/renderers/webgl/WebGLTextures.js b/src/renderers/webgl/WebGLTextures.js index 336bdc9ae0bdb5..b3647cc02adf36 100644 --- a/src/renderers/webgl/WebGLTextures.js +++ b/src/renderers/webgl/WebGLTextures.js @@ -177,6 +177,20 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } + if ( glFormat === _gl.DEPTH_COMPONENT ) { + + if ( glType === _gl.UNSIGNED_SHORT ) internalFormat = _gl.DEPTH_COMPONENT16; + if ( glType === _gl.UNSIGNED_INT ) internalFormat = _gl.DEPTH_COMPONENT24; + if ( glType === _gl.FLOAT ) internalFormat = _gl.DEPTH_COMPONENT32F; + + } + + if ( glFormat === _gl.DEPTH_STENCIL ) { + + if ( glType === _gl.UNSIGNED_INT_24_8 ) internalFormat = _gl.DEPTH24_STENCIL8; + + } + return internalFormat; } @@ -252,10 +266,10 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const renderTargetProperties = properties.get( renderTarget ); if ( renderTarget.isWebGLCubeRenderTarget ) { - for ( let f = 0; f < 6; f ++ ) { + for ( let i = 0; i < 6; i ++ ) { - _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ f ] ); - if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer[ f ] ); + _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ] ); + if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer[ i ] ); } @@ -494,7 +508,6 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, function uploadTexture( textureProperties, texture, slot ) { let textureType = _gl.TEXTURE_2D; - if ( texture.isDataTexture2DArray ) textureType = _gl.TEXTURE_2D_ARRAY; if ( texture.isDataTexture3D ) textureType = _gl.TEXTURE_3D; @@ -522,77 +535,29 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, let mipmap; const mipmaps = texture.mipmaps; - if ( texture.format === DepthFormat || texture.format === DepthStencilFormat ) { - - // populate depth texture with dummy data - - glInternalFormat = _gl.DEPTH_COMPONENT; - - if ( isWebGL2 ) { - - if ( texture.type === FloatType ) { - - glInternalFormat = _gl.DEPTH_COMPONENT32F; - - } else if ( texture.type === UnsignedIntType ) { + // https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/ + if ( ! isWebGL2 ) { - glInternalFormat = _gl.DEPTH_COMPONENT24; + if ( ( texture.format === DepthFormat || texture.format === DepthStencilFormat ) + && texture.type === FloatType ) { - } else if ( texture.type === UnsignedInt248Type ) { - - glInternalFormat = _gl.DEPTH24_STENCIL8; - - } else { - - glInternalFormat = _gl.DEPTH_COMPONENT16; // WebGL2 requires sized internalformat for glTexImage2D - - } - - } else { - - if ( texture.type === FloatType ) { - - console.error( 'WebGLRenderer: Floating point depth texture requires WebGL2.' ); - - } + console.error( 'WebGLRenderer: Floating point depth texture requires WebGL2.' ); } - // validation checks for WebGL 1 + if ( texture.format === DepthFormat && texture.type !== UnsignedShortType && texture.type !== UnsignedIntType ) { - if ( texture.format === DepthFormat && glInternalFormat === _gl.DEPTH_COMPONENT ) { - - // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are - // DEPTH_COMPONENT and type is not UNSIGNED_SHORT or UNSIGNED_INT - // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) - if ( texture.type !== UnsignedShortType && texture.type !== UnsignedIntType ) { - - console.warn( 'THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture.' ); - - texture.type = UnsignedShortType; - glType = utils.convert( texture.type ); - - } + console.warn( 'THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture.' ); + texture.type = UnsignedShortType; + glType = utils.convert( texture.type ); } - if ( texture.format === DepthStencilFormat && glInternalFormat === _gl.DEPTH_COMPONENT ) { - - // Depth stencil textures need the DEPTH_STENCIL internal format - // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) - glInternalFormat = _gl.DEPTH_STENCIL; - - // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are - // DEPTH_STENCIL and type is not UNSIGNED_INT_24_8_WEBGL. - // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/) - if ( texture.type !== UnsignedInt248Type ) { - - console.warn( 'THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture.' ); + if ( texture.format === DepthStencilFormat && texture.type !== UnsignedInt248Type ) { - texture.type = UnsignedInt248Type; - glType = utils.convert( texture.type ); - - } + console.warn( 'THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture.' ); + texture.type = UnsignedInt248Type; + glType = utils.convert( texture.type ); } @@ -721,15 +686,15 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, const cubeImage = []; - for ( let f = 0; f < 6; f ++ ) { + for ( let i = 0; i < 6; i ++ ) { if ( ! isCompressed && ! isDataTexture ) { - cubeImage[ f ] = resizeImage( texture.image[ f ], false, true, maxCubemapSize ); + cubeImage[ i ] = resizeImage( texture.image[ i ], false, true, maxCubemapSize ); } else { - cubeImage[ f ] = isDataTexture ? texture.image[ f ].image : texture.image[ f ]; + cubeImage[ i ] = isDataTexture ? texture.image[ i ].image : texture.image[ i ]; } @@ -747,10 +712,9 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, if ( isCompressed ) { - for ( let f = 0; f < 6; f ++ ) { + for ( let i = 0; i < 6; i ++ ) { - mipmaps = cubeImage[ f ].mipmaps; - const target = _gl.TEXTURE_CUBE_MAP_POSITIVE_X + f; + mipmaps = cubeImage[ i ].mipmaps; for ( let j = 0; j < mipmaps.length; j ++ ) { @@ -760,7 +724,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, if ( glFormat !== null ) { - state.compressedTexImage2D( target, j, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data ); + state.compressedTexImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data ); } else { @@ -770,7 +734,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } else { - state.texImage2D( target, j, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); } @@ -784,31 +748,30 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, mipmaps = texture.mipmaps; - for ( let f = 0; f < 6; f ++ ) { + for ( let i = 0; i < 6; i ++ ) { - const target = _gl.TEXTURE_CUBE_MAP_POSITIVE_X + f; - if ( isDataTexture ) { + if ( cubeImage[ i ].data !== undefined ) { - state.texImage2D( target, 0, glInternalFormat, cubeImage[ f ].width, cubeImage[ f ].height, 0, glFormat, glType, cubeImage[ f ].data ); + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, cubeImage[ i ].width, cubeImage[ i ].height, 0, glFormat, glType, cubeImage[ i ].data ); for ( let j = 0; j < mipmaps.length; j ++ ) { const mipmap = mipmaps[ j ]; - const mipmapImage = mipmap.image[ f ].image; + const mipmapImage = mipmap.image[ i ].image; - state.texImage2D( target, j + 1, glInternalFormat, mipmapImage.width, mipmapImage.height, 0, glFormat, glType, mipmapImage.data ); + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, glInternalFormat, mipmapImage.width, mipmapImage.height, 0, glFormat, glType, mipmapImage.data ); } } else { - state.texImage2D( target, 0, glInternalFormat, glFormat, glType, cubeImage[ f ] ); + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glInternalFormat, glFormat, glType, cubeImage[ i ] ); for ( let j = 0; j < mipmaps.length; j ++ ) { const mipmap = mipmaps[ j ]; - state.texImage2D( target, j + 1, glInternalFormat, glFormat, glType, mipmap.image[ f ] ); + state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j + 1, glInternalFormat, glFormat, glType, mipmap.image[ i ] ); } @@ -868,15 +831,15 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, if ( isMultisample ) { - const texture = renderTarget.depthTexture; + const depthTexture = renderTarget.depthTexture; - if ( texture && ( texture.format === DepthFormat || texture.format === DepthStencilFormat ) ) { + if ( depthTexture && ( depthTexture.format === DepthFormat || depthTexture.format === DepthStencilFormat ) ) { - if ( texture.type === FloatType ) { + if ( depthTexture.type === FloatType ) { glInternalFormat = _gl.DEPTH_COMPONENT32F; - } else if ( texture.type === UnsignedIntType ) { + } else if ( depthTexture.type === UnsignedIntType ) { glInternalFormat = _gl.DEPTH_COMPONENT24; @@ -970,17 +933,22 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } - // upload an empty depth texture with framebuffer size - if ( texture.image.width !== renderTarget.width || - texture.image.height !== renderTarget.height ) { + if ( texture.isCubeTexture ) { + + // upload an empty depth texture with framebuffer size + for ( let f = 0; f < 6; f ++ ) { - texture.image.width = renderTarget.width; - texture.image.height = renderTarget.height; - texture.needsUpdate = true; + texture.image[ f ] = texture.image[ f ] || { data: null }; + if ( texture.image[ f ].width !== renderTarget.width || + texture.image[ f ].height !== renderTarget.height ) { - } + texture.image[ f ].width = renderTarget.width; + texture.image[ f ].height = renderTarget.height; + texture.needsUpdate = true; - if ( texture.isCubeTexture ) { + } + + } setTextureCube( texture, 0 ); for ( let f = 0; f < 6; f ++ ) { @@ -991,21 +959,34 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } - } else if ( texture.isDataTexture3D ) { + } else { - throw new Error( 'renderTarget.depthTexture may not be a 3D texture.' ); + // upload an empty depth texture with framebuffer size + if ( texture.image.width !== renderTarget.width || + texture.image.height !== renderTarget.height ) { - } else if ( texture.isDataTexture2DArray ) { + texture.image.width = renderTarget.width; + texture.image.height = renderTarget.height; + texture.needsUpdate = true; + + } - setTexture2DArray( texture, 0 ); state.bindFramebuffer( _gl.FRAMEBUFFER, __webglFramebuffer ); - _gl.framebufferTextureLayer( _gl.FRAMEBUFFER, attachment, textureProperties.__webglTexture, 0, 0 ); + if ( texture.isDataTexture3D ) { - } else { + throw new Error( 'renderTarget.depthTexture may not be a 3D texture.' ); - setTexture2D( texture, 0 ); - state.bindFramebuffer( _gl.FRAMEBUFFER, __webglFramebuffer ); - _gl.framebufferTexture2D( _gl.FRAMEBUFFER, attachment, _gl.TEXTURE_2D, textureProperties.__webglTexture, 0 ); + } else if ( texture.isDataTexture2DArray ) { + + setTexture2DArray( texture, 0 ); + _gl.framebufferTextureLayer( _gl.FRAMEBUFFER, attachment, textureProperties.__webglTexture, 0, 0 ); + + } else { + + setTexture2D( texture, 0 ); + _gl.framebufferTexture2D( _gl.FRAMEBUFFER, attachment, _gl.TEXTURE_2D, textureProperties.__webglTexture, 0 ); + + } } @@ -1125,7 +1106,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } - const target = + const glTextureType = texture.isCubeTexture ? _gl.TEXTURE_CUBE_MAP : texture.isDataTexture3D ? _gl.TEXTURE_3D : texture.isDataTexture2DArray ? _gl.TEXTURE_2D_ARRAY : @@ -1149,8 +1130,8 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, // Setup color buffer - state.bindTexture( target, textureProperties.__webglTexture ); - setTextureParameters( target, texture, supportsMips ); + state.bindTexture( glTextureType, textureProperties.__webglTexture ); + setTextureParameters( glTextureType, texture, supportsMips ); if ( texture.isCubeTexture ) { for ( let f = 0; f < 6; f ++ ) { @@ -1161,17 +1142,17 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, } else { - setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, texture, _gl.COLOR_ATTACHMENT0 + i, target ); + setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, texture, _gl.COLOR_ATTACHMENT0 + i, glTextureType ); } if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) { - generateMipmap( target, texture, renderTarget.width, renderTarget.height, renderTarget.depth ); + generateMipmap( glTextureType, texture, renderTarget.width, renderTarget.height, renderTarget.depth ); } - state.bindTexture( target, null ); + state.bindTexture( glTextureType, null ); } @@ -1287,7 +1268,7 @@ function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, if ( warnedTexture2D === false ) { - console.warn( 'THREE.WebGLTextures.safeSetTexture2D: don\'t use render targets as textures. Use their .texture property instead.' ); + console.warn( 'THREE.WebGLTextures.safeSetTexture2D: don\'t use render targets as textures. Use their .textures[0] property instead.' ); warnedTexture2D = true; } diff --git a/src/textures/CubeTexture.js b/src/textures/CubeTexture.js index e839eeae16e0ea..cb182ba79d628a 100644 --- a/src/textures/CubeTexture.js +++ b/src/textures/CubeTexture.js @@ -19,7 +19,7 @@ class CubeTexture extends Texture { // three.js uses a right-handed coordinate system. So environment maps used in three.js appear to have px and nx swapped // and the flag _needsFlipEnvMap controls this conversion. The flip is not required (and thus _needsFlipEnvMap is set to false) - // when using a cube texture in WebGLCubeRenderTarget.textures. + // when using WebGLCubeRenderTarget.texture as a cube texture. this._needsFlipEnvMap = true; @@ -39,6 +39,14 @@ class CubeTexture extends Texture { } + copy( source ) { + + super.copy( source ); + this._needsFlipEnvMap = source._needsFlipEnvMap; + return this; + + } + } CubeTexture.prototype.isCubeTexture = true;