import { REVISION, BackSide, FrontSide, DoubleSide, HalfFloatType, UnsignedByteType, NoToneMapping, LinearMipmapLinearFilter, SRGBColorSpace, LinearSRGBColorSpace, RGBAIntegerFormat, RGIntegerFormat, RedIntegerFormat, UnsignedIntType, UnsignedShortType, UnsignedInt248Type, UnsignedShort4444Type, UnsignedShort5551Type, WebGLCoordinateSystem, DisplayP3ColorSpace, LinearDisplayP3ColorSpace } from '../constants.js'; import { Color } from '../math/Color.js'; import { Frustum } from '../math/Frustum.js'; import { Matrix4 } from '../math/Matrix4.js'; import { Vector3 } from '../math/Vector3.js'; import { Vector4 } from '../math/Vector4.js'; import { WebGLAnimation } from './webgl/WebGLAnimation.js'; import { WebGLAttributes } from './webgl/WebGLAttributes.js'; import { WebGLBackground } from './webgl/WebGLBackground.js'; import { WebGLBindingStates } from './webgl/WebGLBindingStates.js'; import { WebGLBufferRenderer } from './webgl/WebGLBufferRenderer.js'; import { WebGLCapabilities } from './webgl/WebGLCapabilities.js'; import { WebGLClipping } from './webgl/WebGLClipping.js'; import { WebGLCubeMaps } from './webgl/WebGLCubeMaps.js'; import { WebGLCubeUVMaps } from './webgl/WebGLCubeUVMaps.js'; import { WebGLExtensions } from './webgl/WebGLExtensions.js'; import { WebGLGeometries } from './webgl/WebGLGeometries.js'; import { WebGLIndexedBufferRenderer } from './webgl/WebGLIndexedBufferRenderer.js'; import { WebGLInfo } from './webgl/WebGLInfo.js'; import { WebGLMorphtargets } from './webgl/WebGLMorphtargets.js'; import { WebGLObjects } from './webgl/WebGLObjects.js'; import { WebGLPrograms } from './webgl/WebGLPrograms.js'; import { WebGLProperties } from './webgl/WebGLProperties.js'; import { WebGLRenderLists } from './webgl/WebGLRenderLists.js'; import { WebGLRenderStates } from './webgl/WebGLRenderStates.js'; import { WebGLRenderTarget } from './WebGLRenderTarget.js'; import { WebGLShadowMap } from './webgl/WebGLShadowMap.js'; import { WebGLState } from './webgl/WebGLState.js'; import { WebGLTextures } from './webgl/WebGLTextures.js'; import { WebGLUniforms } from './webgl/WebGLUniforms.js'; import { WebGLUtils } from './webgl/WebGLUtils.js'; import { WebXRManager } from './webxr/WebXRManager.js'; import { WebGLMaterials } from './webgl/WebGLMaterials.js'; import { WebGLUniformsGroups } from './webgl/WebGLUniformsGroups.js'; import { createCanvasElement, probeAsync } from '../utils.js'; import { ColorManagement } from '../math/ColorManagement.js'; class WebGLRenderer { constructor( parameters = {} ) { const { canvas = createCanvasElement(), context = null, depth = true, stencil = false, alpha = false, antialias = false, premultipliedAlpha = true, preserveDrawingBuffer = false, powerPreference = 'default', failIfMajorPerformanceCaveat = false, } = parameters; this.isWebGLRenderer = true; let _alpha; if ( context !== null ) { if ( typeof WebGLRenderingContext !== 'undefined' && context instanceof WebGLRenderingContext ) { throw new Error( 'THREE.WebGLRenderer: WebGL 1 is not supported since r163.' ); } _alpha = context.getContextAttributes().alpha; } else { _alpha = alpha; } const uintClearColor = new Uint32Array( 4 ); const intClearColor = new Int32Array( 4 ); let currentRenderList = null; let currentRenderState = null; // render() can be called from within a callback triggered by another render. // We track this so that the nested render call gets its list and state isolated from the parent render call. const renderListStack = []; const renderStateStack = []; // public properties this.domElement = canvas; // Debug configuration container this.debug = { /** * Enables error checking and reporting when shader programs are being compiled * @type {boolean} */ checkShaderErrors: true, /** * Callback for custom error reporting. * @type {?Function} */ onShaderError: null }; // clearing this.autoClear = true; this.autoClearColor = true; this.autoClearDepth = true; this.autoClearStencil = true; // scene graph this.sortObjects = true; // user-defined clipping this.clippingPlanes = []; this.localClippingEnabled = false; // physically based shading this._outputColorSpace = SRGBColorSpace; // tone mapping this.toneMapping = NoToneMapping; this.toneMappingExposure = 1.0; // internal properties const _this = this; let _isContextLost = false; // internal state cache let _currentActiveCubeFace = 0; let _currentActiveMipmapLevel = 0; let _currentRenderTarget = null; let _currentMaterialId = - 1; let _currentCamera = null; const _currentViewport = new Vector4(); const _currentScissor = new Vector4(); let _currentScissorTest = null; const _currentClearColor = new Color( 0x000000 ); let _currentClearAlpha = 0; // let _width = canvas.width; let _height = canvas.height; let _pixelRatio = 1; let _opaqueSort = null; let _transparentSort = null; const _viewport = new Vector4( 0, 0, _width, _height ); const _scissor = new Vector4( 0, 0, _width, _height ); let _scissorTest = false; // frustum const _frustum = new Frustum(); // clipping let _clippingEnabled = false; let _localClippingEnabled = false; // camera matrices cache const _projScreenMatrix = new Matrix4(); const _vector3 = new Vector3(); const _emptyScene = { background: null, fog: null, environment: null, overrideMaterial: null, isScene: true }; let _renderBackground = false; function getTargetPixelRatio() { return _currentRenderTarget === null ? _pixelRatio : 1; } // initialize let _gl = context; function getContext( contextName, contextAttributes ) { return canvas.getContext( contextName, contextAttributes ); } try { const contextAttributes = { alpha: true, depth, stencil, antialias, premultipliedAlpha, preserveDrawingBuffer, powerPreference, failIfMajorPerformanceCaveat, }; // OffscreenCanvas does not have setAttribute, see #22811 if ( 'setAttribute' in canvas ) canvas.setAttribute( 'data-engine', `three.js r${REVISION}` ); // event listeners must be registered before WebGL context is created, see #12753 canvas.addEventListener( 'webglcontextlost', onContextLost, false ); canvas.addEventListener( 'webglcontextrestored', onContextRestore, false ); canvas.addEventListener( 'webglcontextcreationerror', onContextCreationError, false ); if ( _gl === null ) { const contextName = 'webgl2'; _gl = getContext( contextName, contextAttributes ); if ( _gl === null ) { if ( getContext( contextName ) ) { throw new Error( 'Error creating WebGL context with your selected attributes.' ); } else { throw new Error( 'Error creating WebGL context.' ); } } } } catch ( error ) { console.error( 'THREE.WebGLRenderer: ' + error.message ); throw error; } let extensions, capabilities, state, info; let properties, textures, cubemaps, cubeuvmaps, attributes, geometries, objects; let programCache, materials, renderLists, renderStates, clipping, shadowMap; let background, morphtargets, bufferRenderer, indexedBufferRenderer; let utils, bindingStates, uniformsGroups; function initGLContext() { extensions = new WebGLExtensions( _gl ); extensions.init(); utils = new WebGLUtils( _gl, extensions ); capabilities = new WebGLCapabilities( _gl, extensions, parameters, utils ); state = new WebGLState( _gl ); info = new WebGLInfo( _gl ); properties = new WebGLProperties(); textures = new WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ); cubemaps = new WebGLCubeMaps( _this ); cubeuvmaps = new WebGLCubeUVMaps( _this ); attributes = new WebGLAttributes( _gl ); bindingStates = new WebGLBindingStates( _gl, attributes ); geometries = new WebGLGeometries( _gl, attributes, info, bindingStates ); objects = new WebGLObjects( _gl, geometries, attributes, info ); morphtargets = new WebGLMorphtargets( _gl, capabilities, textures ); clipping = new WebGLClipping( properties ); programCache = new WebGLPrograms( _this, cubemaps, cubeuvmaps, extensions, capabilities, bindingStates, clipping ); materials = new WebGLMaterials( _this, properties ); renderLists = new WebGLRenderLists(); renderStates = new WebGLRenderStates( extensions ); background = new WebGLBackground( _this, cubemaps, cubeuvmaps, state, objects, _alpha, premultipliedAlpha ); shadowMap = new WebGLShadowMap( _this, objects, capabilities ); uniformsGroups = new WebGLUniformsGroups( _gl, info, capabilities, state ); bufferRenderer = new WebGLBufferRenderer( _gl, extensions, info ); indexedBufferRenderer = new WebGLIndexedBufferRenderer( _gl, extensions, info ); info.programs = programCache.programs; _this.capabilities = capabilities; _this.extensions = extensions; _this.properties = properties; _this.renderLists = renderLists; _this.shadowMap = shadowMap; _this.state = state; _this.info = info; } initGLContext(); // xr const xr = new WebXRManager( _this, _gl ); this.xr = xr; // API this.getContext = function () { return _gl; }; this.getContextAttributes = function () { return _gl.getContextAttributes(); }; this.forceContextLoss = function () { const extension = extensions.get( 'WEBGL_lose_context' ); if ( extension ) extension.loseContext(); }; this.forceContextRestore = function () { const extension = extensions.get( 'WEBGL_lose_context' ); if ( extension ) extension.restoreContext(); }; this.getPixelRatio = function () { return _pixelRatio; }; this.setPixelRatio = function ( value ) { if ( value === undefined ) return; _pixelRatio = value; this.setSize( _width, _height, false ); }; this.getSize = function ( target ) { return target.set( _width, _height ); }; this.setSize = function ( width, height, updateStyle = true ) { if ( xr.isPresenting ) { console.warn( 'THREE.WebGLRenderer: Can\'t change size while VR device is presenting.' ); return; } _width = width; _height = height; canvas.width = Math.floor( width * _pixelRatio ); canvas.height = Math.floor( height * _pixelRatio ); if ( updateStyle === true ) { canvas.style.width = width + 'px'; canvas.style.height = height + 'px'; } this.setViewport( 0, 0, width, height ); }; this.getDrawingBufferSize = function ( target ) { return target.set( _width * _pixelRatio, _height * _pixelRatio ).floor(); }; this.setDrawingBufferSize = function ( width, height, pixelRatio ) { _width = width; _height = height; _pixelRatio = pixelRatio; canvas.width = Math.floor( width * pixelRatio ); canvas.height = Math.floor( height * pixelRatio ); this.setViewport( 0, 0, width, height ); }; this.getCurrentViewport = function ( target ) { return target.copy( _currentViewport ); }; this.getViewport = function ( target ) { return target.copy( _viewport ); }; this.setViewport = function ( x, y, width, height ) { if ( x.isVector4 ) { _viewport.set( x.x, x.y, x.z, x.w ); } else { _viewport.set( x, y, width, height ); } state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).round() ); }; this.getScissor = function ( target ) { return target.copy( _scissor ); }; this.setScissor = function ( x, y, width, height ) { if ( x.isVector4 ) { _scissor.set( x.x, x.y, x.z, x.w ); } else { _scissor.set( x, y, width, height ); } state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).round() ); }; this.getScissorTest = function () { return _scissorTest; }; this.setScissorTest = function ( boolean ) { state.setScissorTest( _scissorTest = boolean ); }; this.setOpaqueSort = function ( method ) { _opaqueSort = method; }; this.setTransparentSort = function ( method ) { _transparentSort = method; }; // Clearing this.getClearColor = function ( target ) { return target.copy( background.getClearColor() ); }; this.setClearColor = function () { background.setClearColor.apply( background, arguments ); }; this.getClearAlpha = function () { return background.getClearAlpha(); }; this.setClearAlpha = function () { background.setClearAlpha.apply( background, arguments ); }; this.clear = function ( color = true, depth = true, stencil = true ) { let bits = 0; if ( color ) { // check if we're trying to clear an integer target let isIntegerFormat = false; if ( _currentRenderTarget !== null ) { const targetFormat = _currentRenderTarget.texture.format; isIntegerFormat = targetFormat === RGBAIntegerFormat || targetFormat === RGIntegerFormat || targetFormat === RedIntegerFormat; } // use the appropriate clear functions to clear the target if it's a signed // or unsigned integer target if ( isIntegerFormat ) { const targetType = _currentRenderTarget.texture.type; const isUnsignedType = targetType === UnsignedByteType || targetType === UnsignedIntType || targetType === UnsignedShortType || targetType === UnsignedInt248Type || targetType === UnsignedShort4444Type || targetType === UnsignedShort5551Type; const clearColor = background.getClearColor(); const a = background.getClearAlpha(); const r = clearColor.r; const g = clearColor.g; const b = clearColor.b; if ( isUnsignedType ) { uintClearColor[ 0 ] = r; uintClearColor[ 1 ] = g; uintClearColor[ 2 ] = b; uintClearColor[ 3 ] = a; _gl.clearBufferuiv( _gl.COLOR, 0, uintClearColor ); } else { intClearColor[ 0 ] = r; intClearColor[ 1 ] = g; intClearColor[ 2 ] = b; intClearColor[ 3 ] = a; _gl.clearBufferiv( _gl.COLOR, 0, intClearColor ); } } else { bits |= _gl.COLOR_BUFFER_BIT; } } if ( depth ) bits |= _gl.DEPTH_BUFFER_BIT; if ( stencil ) { bits |= _gl.STENCIL_BUFFER_BIT; this.state.buffers.stencil.setMask( 0xffffffff ); } _gl.clear( bits ); }; this.clearColor = function () { this.clear( true, false, false ); }; this.clearDepth = function () { this.clear( false, true, false ); }; this.clearStencil = function () { this.clear( false, false, true ); }; // this.dispose = function () { canvas.removeEventListener( 'webglcontextlost', onContextLost, false ); canvas.removeEventListener( 'webglcontextrestored', onContextRestore, false ); canvas.removeEventListener( 'webglcontextcreationerror', onContextCreationError, false ); renderLists.dispose(); renderStates.dispose(); properties.dispose(); cubemaps.dispose(); cubeuvmaps.dispose(); objects.dispose(); bindingStates.dispose(); uniformsGroups.dispose(); programCache.dispose(); xr.dispose(); xr.removeEventListener( 'sessionstart', onXRSessionStart ); xr.removeEventListener( 'sessionend', onXRSessionEnd ); animation.stop(); }; // Events function onContextLost( event ) { event.preventDefault(); console.log( 'THREE.WebGLRenderer: Context Lost.' ); _isContextLost = true; } function onContextRestore( /* event */ ) { console.log( 'THREE.WebGLRenderer: Context Restored.' ); _isContextLost = false; const infoAutoReset = info.autoReset; const shadowMapEnabled = shadowMap.enabled; const shadowMapAutoUpdate = shadowMap.autoUpdate; const shadowMapNeedsUpdate = shadowMap.needsUpdate; const shadowMapType = shadowMap.type; initGLContext(); info.autoReset = infoAutoReset; shadowMap.enabled = shadowMapEnabled; shadowMap.autoUpdate = shadowMapAutoUpdate; shadowMap.needsUpdate = shadowMapNeedsUpdate; shadowMap.type = shadowMapType; } function onContextCreationError( event ) { console.error( 'THREE.WebGLRenderer: A WebGL context could not be created. Reason: ', event.statusMessage ); } function onMaterialDispose( event ) { const material = event.target; material.removeEventListener( 'dispose', onMaterialDispose ); deallocateMaterial( material ); } // Buffer deallocation function deallocateMaterial( material ) { releaseMaterialProgramReferences( material ); properties.remove( material ); } function releaseMaterialProgramReferences( material ) { const programs = properties.get( material ).programs; if ( programs !== undefined ) { programs.forEach( function ( program ) { programCache.releaseProgram( program ); } ); if ( material.isShaderMaterial ) { programCache.releaseShaderCache( material ); } } } // Buffer rendering this.renderBufferDirect = function ( camera, scene, geometry, material, object, group ) { if ( scene === null ) scene = _emptyScene; // renderBufferDirect second parameter used to be fog (could be null) const frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 ); const program = setProgram( camera, scene, geometry, material, object ); state.setMaterial( material, frontFaceCW ); // let index = geometry.index; let rangeFactor = 1; if ( material.wireframe === true ) { index = geometries.getWireframeAttribute( geometry ); if ( index === undefined ) return; rangeFactor = 2; } // const drawRange = geometry.drawRange; const position = geometry.attributes.position; let drawStart = drawRange.start * rangeFactor; let drawEnd = ( drawRange.start + drawRange.count ) * rangeFactor; if ( group !== null ) { drawStart = Math.max( drawStart, group.start * rangeFactor ); drawEnd = Math.min( drawEnd, ( group.start + group.count ) * rangeFactor ); } if ( index !== null ) { drawStart = Math.max( drawStart, 0 ); drawEnd = Math.min( drawEnd, index.count ); } else if ( position !== undefined && position !== null ) { drawStart = Math.max( drawStart, 0 ); drawEnd = Math.min( drawEnd, position.count ); } const drawCount = drawEnd - drawStart; if ( drawCount < 0 || drawCount === Infinity ) return; // bindingStates.setup( object, material, program, geometry, index ); let attribute; let renderer = bufferRenderer; if ( index !== null ) { attribute = attributes.get( index ); renderer = indexedBufferRenderer; renderer.setIndex( attribute ); } // if ( object.isMesh ) { if ( material.wireframe === true ) { state.setLineWidth( material.wireframeLinewidth * getTargetPixelRatio() ); renderer.setMode( _gl.LINES ); } else { renderer.setMode( _gl.TRIANGLES ); } } else if ( object.isLine ) { let lineWidth = material.linewidth; if ( lineWidth === undefined ) lineWidth = 1; // Not using Line*Material state.setLineWidth( lineWidth * getTargetPixelRatio() ); if ( object.isLineSegments ) { renderer.setMode( _gl.LINES ); } else if ( object.isLineLoop ) { renderer.setMode( _gl.LINE_LOOP ); } else { renderer.setMode( _gl.LINE_STRIP ); } } else if ( object.isPoints ) { renderer.setMode( _gl.POINTS ); } else if ( object.isSprite ) { renderer.setMode( _gl.TRIANGLES ); } if ( object.isBatchedMesh ) { if ( object._multiDrawInstances !== null ) { renderer.renderMultiDrawInstances( object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount, object._multiDrawInstances ); } else { renderer.renderMultiDraw( object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount ); } } else if ( object.isInstancedMesh ) { renderer.renderInstances( drawStart, drawCount, object.count ); } else if ( geometry.isInstancedBufferGeometry ) { const maxInstanceCount = geometry._maxInstanceCount !== undefined ? geometry._maxInstanceCount : Infinity; const instanceCount = Math.min( geometry.instanceCount, maxInstanceCount ); renderer.renderInstances( drawStart, drawCount, instanceCount ); } else { renderer.render( drawStart, drawCount ); } }; // Compile function prepareMaterial( material, scene, object ) { if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) { material.side = BackSide; material.needsUpdate = true; getProgram( material, scene, object ); material.side = FrontSide; material.needsUpdate = true; getProgram( material, scene, object ); material.side = DoubleSide; } else { getProgram( material, scene, object ); } } this.compile = function ( scene, camera, targetScene = null ) { if ( targetScene === null ) targetScene = scene; currentRenderState = renderStates.get( targetScene ); currentRenderState.init( camera ); renderStateStack.push( currentRenderState ); // gather lights from both the target scene and the new object that will be added to the scene. targetScene.traverseVisible( function ( object ) { if ( object.isLight && object.layers.test( camera.layers ) ) { currentRenderState.pushLight( object ); if ( object.castShadow ) { currentRenderState.pushShadow( object ); } } } ); if ( scene !== targetScene ) { scene.traverseVisible( function ( object ) { if ( object.isLight && object.layers.test( camera.layers ) ) { currentRenderState.pushLight( object ); if ( object.castShadow ) { currentRenderState.pushShadow( object ); } } } ); } currentRenderState.setupLights(); // Only initialize materials in the new scene, not the targetScene. const materials = new Set(); scene.traverse( function ( object ) { const material = object.material; if ( material ) { if ( Array.isArray( material ) ) { for ( let i = 0; i < material.length; i ++ ) { const material2 = material[ i ]; prepareMaterial( material2, targetScene, object ); materials.add( material2 ); } } else { prepareMaterial( material, targetScene, object ); materials.add( material ); } } } ); renderStateStack.pop(); currentRenderState = null; return materials; }; // compileAsync this.compileAsync = function ( scene, camera, targetScene = null ) { const materials = this.compile( scene, camera, targetScene ); // Wait for all the materials in the new object to indicate that they're // ready to be used before resolving the promise. return new Promise( ( resolve ) => { function checkMaterialsReady() { materials.forEach( function ( material ) { const materialProperties = properties.get( material ); const program = materialProperties.currentProgram; if ( program.isReady() ) { // remove any programs that report they're ready to use from the list materials.delete( material ); } } ); // once the list of compiling materials is empty, call the callback if ( materials.size === 0 ) { resolve( scene ); return; } // if some materials are still not ready, wait a bit and check again setTimeout( checkMaterialsReady, 10 ); } if ( extensions.get( 'KHR_parallel_shader_compile' ) !== null ) { // If we can check the compilation status of the materials without // blocking then do so right away. checkMaterialsReady(); } else { // Otherwise start by waiting a bit to give the materials we just // initialized a chance to finish. setTimeout( checkMaterialsReady, 10 ); } } ); }; // Animation Loop let onAnimationFrameCallback = null; function onAnimationFrame( time ) { if ( onAnimationFrameCallback ) onAnimationFrameCallback( time ); } function onXRSessionStart() { animation.stop(); } function onXRSessionEnd() { animation.start(); } const animation = new WebGLAnimation(); animation.setAnimationLoop( onAnimationFrame ); if ( typeof self !== 'undefined' ) animation.setContext( self ); this.setAnimationLoop = function ( callback ) { onAnimationFrameCallback = callback; xr.setAnimationLoop( callback ); ( callback === null ) ? animation.stop() : animation.start(); }; xr.addEventListener( 'sessionstart', onXRSessionStart ); xr.addEventListener( 'sessionend', onXRSessionEnd ); // Rendering this.render = function ( scene, camera ) { if ( camera !== undefined && camera.isCamera !== true ) { console.error( 'THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.' ); return; } if ( _isContextLost === true ) return; // update scene graph if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld(); // update camera matrices and frustum if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld(); if ( xr.enabled === true && xr.isPresenting === true ) { if ( xr.cameraAutoUpdate === true ) xr.updateCamera( camera ); camera = xr.getCamera(); // use XR camera for rendering } // if ( scene.isScene === true ) scene.onBeforeRender( _this, scene, camera, _currentRenderTarget ); currentRenderState = renderStates.get( scene, renderStateStack.length ); currentRenderState.init( camera ); renderStateStack.push( currentRenderState ); _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); _frustum.setFromProjectionMatrix( _projScreenMatrix ); _localClippingEnabled = this.localClippingEnabled; _clippingEnabled = clipping.init( this.clippingPlanes, _localClippingEnabled ); currentRenderList = renderLists.get( scene, renderListStack.length ); currentRenderList.init(); renderListStack.push( currentRenderList ); if ( xr.enabled === true && xr.isPresenting === true ) { const depthSensingMesh = _this.xr.getDepthSensingMesh(); if ( depthSensingMesh !== null ) { projectObject( depthSensingMesh, camera, - Infinity, _this.sortObjects ); } } projectObject( scene, camera, 0, _this.sortObjects ); currentRenderList.finish(); if ( _this.sortObjects === true ) { currentRenderList.sort( _opaqueSort, _transparentSort ); } _renderBackground = xr.enabled === false || xr.isPresenting === false || xr.hasDepthSensing() === false; if ( _renderBackground ) { background.addToRenderList( currentRenderList, scene ); } // this.info.render.frame ++; if ( _clippingEnabled === true ) clipping.beginShadows(); const shadowsArray = currentRenderState.state.shadowsArray; shadowMap.render( shadowsArray, scene, camera ); if ( _clippingEnabled === true ) clipping.endShadows(); // if ( this.info.autoReset === true ) this.info.reset(); // render scene const opaqueObjects = currentRenderList.opaque; const transmissiveObjects = currentRenderList.transmissive; currentRenderState.setupLights(); if ( camera.isArrayCamera ) { const cameras = camera.cameras; if ( transmissiveObjects.length > 0 ) { for ( let i = 0, l = cameras.length; i < l; i ++ ) { const camera2 = cameras[ i ]; renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera2 ); } } if ( _renderBackground ) background.render( scene ); for ( let i = 0, l = cameras.length; i < l; i ++ ) { const camera2 = cameras[ i ]; renderScene( currentRenderList, scene, camera2, camera2.viewport ); } } else { if ( transmissiveObjects.length > 0 ) renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera ); if ( _renderBackground ) background.render( scene ); renderScene( currentRenderList, scene, camera ); } // if ( _currentRenderTarget !== null ) { // resolve multisample renderbuffers to a single-sample texture if necessary textures.updateMultisampleRenderTarget( _currentRenderTarget ); // Generate mipmap if we're using any kind of mipmap filtering textures.updateRenderTargetMipmap( _currentRenderTarget ); } // if ( scene.isScene === true ) scene.onAfterRender( _this, scene, camera ); // _gl.finish(); bindingStates.resetDefaultState(); _currentMaterialId = - 1; _currentCamera = null; renderStateStack.pop(); if ( renderStateStack.length > 0 ) { currentRenderState = renderStateStack[ renderStateStack.length - 1 ]; if ( _clippingEnabled === true ) clipping.setGlobalState( _this.clippingPlanes, currentRenderState.state.camera ); } else { currentRenderState = null; } renderListStack.pop(); if ( renderListStack.length > 0 ) { currentRenderList = renderListStack[ renderListStack.length - 1 ]; } else { currentRenderList = null; } }; function projectObject( object, camera, groupOrder, sortObjects ) { if ( object.visible === false ) return; const visible = object.layers.test( camera.layers ); if ( visible ) { if ( object.isGroup ) { groupOrder = object.renderOrder; } else if ( object.isLOD ) { if ( object.autoUpdate === true ) object.update( camera ); } else if ( object.isLight ) { currentRenderState.pushLight( object ); if ( object.castShadow ) { currentRenderState.pushShadow( object ); } } else if ( object.isSprite ) { if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) { if ( sortObjects ) { _vector3.setFromMatrixPosition( object.matrixWorld ) .applyMatrix4( _projScreenMatrix ); } const geometry = objects.update( object ); const material = object.material; if ( material.visible ) { currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null ); } } } else if ( object.isMesh || object.isLine || object.isPoints ) { if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) { const geometry = objects.update( object ); const material = object.material; if ( sortObjects ) { if ( object.boundingSphere !== undefined ) { if ( object.boundingSphere === null ) object.computeBoundingSphere(); _vector3.copy( object.boundingSphere.center ); } else { if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere(); _vector3.copy( geometry.boundingSphere.center ); } _vector3 .applyMatrix4( object.matrixWorld ) .applyMatrix4( _projScreenMatrix ); } if ( Array.isArray( material ) ) { const groups = geometry.groups; for ( let i = 0, l = groups.length; i < l; i ++ ) { const group = groups[ i ]; const groupMaterial = material[ group.materialIndex ]; if ( groupMaterial && groupMaterial.visible ) { currentRenderList.push( object, geometry, groupMaterial, groupOrder, _vector3.z, group ); } } } else if ( material.visible ) { currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null ); } } } } const children = object.children; for ( let i = 0, l = children.length; i < l; i ++ ) { projectObject( children[ i ], camera, groupOrder, sortObjects ); } } function renderScene( currentRenderList, scene, camera, viewport ) { const opaqueObjects = currentRenderList.opaque; const transmissiveObjects = currentRenderList.transmissive; const transparentObjects = currentRenderList.transparent; currentRenderState.setupLightsView( camera ); if ( _clippingEnabled === true ) clipping.setGlobalState( _this.clippingPlanes, camera ); if ( viewport ) state.viewport( _currentViewport.copy( viewport ) ); if ( opaqueObjects.length > 0 ) renderObjects( opaqueObjects, scene, camera ); if ( transmissiveObjects.length > 0 ) renderObjects( transmissiveObjects, scene, camera ); if ( transparentObjects.length > 0 ) renderObjects( transparentObjects, scene, camera ); // Ensure depth buffer writing is enabled so it can be cleared on next render state.buffers.depth.setTest( true ); state.buffers.depth.setMask( true ); state.buffers.color.setMask( true ); state.setPolygonOffset( false ); } function renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, camera ) { const overrideMaterial = scene.isScene === true ? scene.overrideMaterial : null; if ( overrideMaterial !== null ) { return; } if ( currentRenderState.state.transmissionRenderTarget[ camera.id ] === undefined ) { currentRenderState.state.transmissionRenderTarget[ camera.id ] = new WebGLRenderTarget( 1, 1, { generateMipmaps: true, type: ( extensions.has( 'EXT_color_buffer_half_float' ) || extensions.has( 'EXT_color_buffer_float' ) ) ? HalfFloatType : UnsignedByteType, minFilter: LinearMipmapLinearFilter, samples: 4, stencilBuffer: stencil, resolveDepthBuffer: false, resolveStencilBuffer: false, colorSpace: ColorManagement.workingColorSpace, } ); // debug /* const geometry = new PlaneGeometry(); const material = new MeshBasicMaterial( { map: _transmissionRenderTarget.texture } ); const mesh = new Mesh( geometry, material ); scene.add( mesh ); */ } const transmissionRenderTarget = currentRenderState.state.transmissionRenderTarget[ camera.id ]; const activeViewport = camera.viewport || _currentViewport; transmissionRenderTarget.setSize( activeViewport.z, activeViewport.w ); // const currentRenderTarget = _this.getRenderTarget(); _this.setRenderTarget( transmissionRenderTarget ); _this.getClearColor( _currentClearColor ); _currentClearAlpha = _this.getClearAlpha(); if ( _currentClearAlpha < 1 ) _this.setClearColor( 0xffffff, 0.5 ); if ( _renderBackground ) { background.render( scene ); } else { _this.clear(); } // Turn off the features which can affect the frag color for opaque objects pass. // Otherwise they are applied twice in opaque objects pass and transmission objects pass. const currentToneMapping = _this.toneMapping; _this.toneMapping = NoToneMapping; // Remove viewport from camera to avoid nested render calls resetting viewport to it (e.g Reflector). // Transmission render pass requires viewport to match the transmissionRenderTarget. const currentCameraViewport = camera.viewport; if ( camera.viewport !== undefined ) camera.viewport = undefined; currentRenderState.setupLightsView( camera ); if ( _clippingEnabled === true ) clipping.setGlobalState( _this.clippingPlanes, camera ); renderObjects( opaqueObjects, scene, camera ); textures.updateMultisampleRenderTarget( transmissionRenderTarget ); textures.updateRenderTargetMipmap( transmissionRenderTarget ); if ( extensions.has( 'WEBGL_multisampled_render_to_texture' ) === false ) { // see #28131 let renderTargetNeedsUpdate = false; for ( let i = 0, l = transmissiveObjects.length; i < l; i ++ ) { const renderItem = transmissiveObjects[ i ]; const object = renderItem.object; const geometry = renderItem.geometry; const material = renderItem.material; const group = renderItem.group; if ( material.side === DoubleSide && object.layers.test( camera.layers ) ) { const currentSide = material.side; material.side = BackSide; material.needsUpdate = true; renderObject( object, scene, camera, geometry, material, group ); material.side = currentSide; material.needsUpdate = true; renderTargetNeedsUpdate = true; } } if ( renderTargetNeedsUpdate === true ) { textures.updateMultisampleRenderTarget( transmissionRenderTarget ); textures.updateRenderTargetMipmap( transmissionRenderTarget ); } } _this.setRenderTarget( currentRenderTarget ); _this.setClearColor( _currentClearColor, _currentClearAlpha ); if ( currentCameraViewport !== undefined ) camera.viewport = currentCameraViewport; _this.toneMapping = currentToneMapping; } function renderObjects( renderList, scene, camera ) { const overrideMaterial = scene.isScene === true ? scene.overrideMaterial : null; for ( let i = 0, l = renderList.length; i < l; i ++ ) { const renderItem = renderList[ i ]; const object = renderItem.object; const geometry = renderItem.geometry; const material = overrideMaterial === null ? renderItem.material : overrideMaterial; const group = renderItem.group; if ( object.layers.test( camera.layers ) ) { renderObject( object, scene, camera, geometry, material, group ); } } } function renderObject( object, scene, camera, geometry, material, group ) { object.onBeforeRender( _this, scene, camera, geometry, material, group ); object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); object.normalMatrix.getNormalMatrix( object.modelViewMatrix ); material.onBeforeRender( _this, scene, camera, geometry, object, group ); if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) { material.side = BackSide; material.needsUpdate = true; _this.renderBufferDirect( camera, scene, geometry, material, object, group ); material.side = FrontSide; material.needsUpdate = true; _this.renderBufferDirect( camera, scene, geometry, material, object, group ); material.side = DoubleSide; } else { _this.renderBufferDirect( camera, scene, geometry, material, object, group ); } object.onAfterRender( _this, scene, camera, geometry, material, group ); } function getProgram( material, scene, object ) { if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ... const materialProperties = properties.get( material ); const lights = currentRenderState.state.lights; const shadowsArray = currentRenderState.state.shadowsArray; const lightsStateVersion = lights.state.version; const parameters = programCache.getParameters( material, lights.state, shadowsArray, scene, object ); const programCacheKey = programCache.getProgramCacheKey( parameters ); let programs = materialProperties.programs; // always update environment and fog - changing these trigger an getProgram call, but it's possible that the program doesn't change materialProperties.environment = material.isMeshStandardMaterial ? scene.environment : null; materialProperties.fog = scene.fog; materialProperties.envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || materialProperties.environment ); materialProperties.envMapRotation = ( materialProperties.environment !== null && material.envMap === null ) ? scene.environmentRotation : material.envMapRotation; if ( programs === undefined ) { // new material material.addEventListener( 'dispose', onMaterialDispose ); programs = new Map(); materialProperties.programs = programs; } let program = programs.get( programCacheKey ); if ( program !== undefined ) { // early out if program and light state is identical if ( materialProperties.currentProgram === program && materialProperties.lightsStateVersion === lightsStateVersion ) { updateCommonMaterialProperties( material, parameters ); return program; } } else { parameters.uniforms = programCache.getUniforms( material ); material.onBuild( object, parameters, _this ); material.onBeforeCompile( parameters, _this ); program = programCache.acquireProgram( parameters, programCacheKey ); programs.set( programCacheKey, program ); materialProperties.uniforms = parameters.uniforms; } const uniforms = materialProperties.uniforms; if ( ( ! material.isShaderMaterial && ! material.isRawShaderMaterial ) || material.clipping === true ) { uniforms.clippingPlanes = clipping.uniform; } updateCommonMaterialProperties( material, parameters ); // store the light setup it was created for materialProperties.needsLights = materialNeedsLights( material ); materialProperties.lightsStateVersion = lightsStateVersion; if ( materialProperties.needsLights ) { // wire up the material to this renderer's lighting state uniforms.ambientLightColor.value = lights.state.ambient; uniforms.lightProbe.value = lights.state.probe; uniforms.directionalLights.value = lights.state.directional; uniforms.directionalLightShadows.value = lights.state.directionalShadow; uniforms.spotLights.value = lights.state.spot; uniforms.spotLightShadows.value = lights.state.spotShadow; uniforms.rectAreaLights.value = lights.state.rectArea; uniforms.ltc_1.value = lights.state.rectAreaLTC1; uniforms.ltc_2.value = lights.state.rectAreaLTC2; uniforms.pointLights.value = lights.state.point; uniforms.pointLightShadows.value = lights.state.pointShadow; uniforms.hemisphereLights.value = lights.state.hemi; uniforms.directionalShadowMap.value = lights.state.directionalShadowMap; uniforms.directionalShadowMatrix.value = lights.state.directionalShadowMatrix; uniforms.spotShadowMap.value = lights.state.spotShadowMap; uniforms.spotLightMatrix.value = lights.state.spotLightMatrix; uniforms.spotLightMap.value = lights.state.spotLightMap; uniforms.pointShadowMap.value = lights.state.pointShadowMap; uniforms.pointShadowMatrix.value = lights.state.pointShadowMatrix; // TODO (abelnation): add area lights shadow info to uniforms } materialProperties.currentProgram = program; materialProperties.uniformsList = null; return program; } function getUniformList( materialProperties ) { if ( materialProperties.uniformsList === null ) { const progUniforms = materialProperties.currentProgram.getUniforms(); materialProperties.uniformsList = WebGLUniforms.seqWithValue( progUniforms.seq, materialProperties.uniforms ); } return materialProperties.uniformsList; } function updateCommonMaterialProperties( material, parameters ) { const materialProperties = properties.get( material ); materialProperties.outputColorSpace = parameters.outputColorSpace; materialProperties.batching = parameters.batching; materialProperties.batchingColor = parameters.batchingColor; materialProperties.instancing = parameters.instancing; materialProperties.instancingColor = parameters.instancingColor; materialProperties.instancingMorph = parameters.instancingMorph; materialProperties.skinning = parameters.skinning; materialProperties.morphTargets = parameters.morphTargets; materialProperties.morphNormals = parameters.morphNormals; materialProperties.morphColors = parameters.morphColors; materialProperties.morphTargetsCount = parameters.morphTargetsCount; materialProperties.numClippingPlanes = parameters.numClippingPlanes; materialProperties.numIntersection = parameters.numClipIntersection; materialProperties.vertexAlphas = parameters.vertexAlphas; materialProperties.vertexTangents = parameters.vertexTangents; materialProperties.toneMapping = parameters.toneMapping; } function setProgram( camera, scene, geometry, material, object ) { if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ... textures.resetTextureUnits(); const fog = scene.fog; const environment = material.isMeshStandardMaterial ? scene.environment : null; const colorSpace = ( _currentRenderTarget === null ) ? _this.outputColorSpace : ( _currentRenderTarget.isXRRenderTarget === true ? _currentRenderTarget.texture.colorSpace : LinearSRGBColorSpace ); const envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || environment ); const vertexAlphas = material.vertexColors === true && !! geometry.attributes.color && geometry.attributes.color.itemSize === 4; const vertexTangents = !! geometry.attributes.tangent && ( !! material.normalMap || material.anisotropy > 0 ); const morphTargets = !! geometry.morphAttributes.position; const morphNormals = !! geometry.morphAttributes.normal; const morphColors = !! geometry.morphAttributes.color; let toneMapping = NoToneMapping; if ( material.toneMapped ) { if ( _currentRenderTarget === null || _currentRenderTarget.isXRRenderTarget === true ) { toneMapping = _this.toneMapping; } } const morphAttribute = geometry.morphAttributes.position || geometry.morphAttributes.normal || geometry.morphAttributes.color; const morphTargetsCount = ( morphAttribute !== undefined ) ? morphAttribute.length : 0; const materialProperties = properties.get( material ); const lights = currentRenderState.state.lights; if ( _clippingEnabled === true ) { if ( _localClippingEnabled === true || camera !== _currentCamera ) { const useCache = camera === _currentCamera && material.id === _currentMaterialId; // we might want to call this function with some ClippingGroup // object instead of the material, once it becomes feasible // (#8465, #8379) clipping.setState( material, camera, useCache ); } } // let needsProgramChange = false; if ( material.version === materialProperties.__version ) { if ( materialProperties.needsLights && ( materialProperties.lightsStateVersion !== lights.state.version ) ) { needsProgramChange = true; } else if ( materialProperties.outputColorSpace !== colorSpace ) { needsProgramChange = true; } else if ( object.isBatchedMesh && materialProperties.batching === false ) { needsProgramChange = true; } else if ( ! object.isBatchedMesh && materialProperties.batching === true ) { needsProgramChange = true; } else if ( object.isBatchedMesh && materialProperties.batchingColor === true && object.colorTexture === null ) { needsProgramChange = true; } else if ( object.isBatchedMesh && materialProperties.batchingColor === false && object.colorTexture !== null ) { needsProgramChange = true; } else if ( object.isInstancedMesh && materialProperties.instancing === false ) { needsProgramChange = true; } else if ( ! object.isInstancedMesh && materialProperties.instancing === true ) { needsProgramChange = true; } else if ( object.isSkinnedMesh && materialProperties.skinning === false ) { needsProgramChange = true; } else if ( ! object.isSkinnedMesh && materialProperties.skinning === true ) { needsProgramChange = true; } else if ( object.isInstancedMesh && materialProperties.instancingColor === true && object.instanceColor === null ) { needsProgramChange = true; } else if ( object.isInstancedMesh && materialProperties.instancingColor === false && object.instanceColor !== null ) { needsProgramChange = true; } else if ( object.isInstancedMesh && materialProperties.instancingMorph === true && object.morphTexture === null ) { needsProgramChange = true; } else if ( object.isInstancedMesh && materialProperties.instancingMorph === false && object.morphTexture !== null ) { needsProgramChange = true; } else if ( materialProperties.envMap !== envMap ) { needsProgramChange = true; } else if ( material.fog === true && materialProperties.fog !== fog ) { needsProgramChange = true; } else if ( materialProperties.numClippingPlanes !== undefined && ( materialProperties.numClippingPlanes !== clipping.numPlanes || materialProperties.numIntersection !== clipping.numIntersection ) ) { needsProgramChange = true; } else if ( materialProperties.vertexAlphas !== vertexAlphas ) { needsProgramChange = true; } else if ( materialProperties.vertexTangents !== vertexTangents ) { needsProgramChange = true; } else if ( materialProperties.morphTargets !== morphTargets ) { needsProgramChange = true; } else if ( materialProperties.morphNormals !== morphNormals ) { needsProgramChange = true; } else if ( materialProperties.morphColors !== morphColors ) { needsProgramChange = true; } else if ( materialProperties.toneMapping !== toneMapping ) { needsProgramChange = true; } else if ( materialProperties.morphTargetsCount !== morphTargetsCount ) { needsProgramChange = true; } } else { needsProgramChange = true; materialProperties.__version = material.version; } // let program = materialProperties.currentProgram; if ( needsProgramChange === true ) { program = getProgram( material, scene, object ); } let refreshProgram = false; let refreshMaterial = false; let refreshLights = false; const p_uniforms = program.getUniforms(), m_uniforms = materialProperties.uniforms; if ( state.useProgram( program.program ) ) { refreshProgram = true; refreshMaterial = true; refreshLights = true; } if ( material.id !== _currentMaterialId ) { _currentMaterialId = material.id; refreshMaterial = true; } if ( refreshProgram || _currentCamera !== camera ) { // common camera uniforms p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix ); p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse ); const uCamPos = p_uniforms.map.cameraPosition; if ( uCamPos !== undefined ) { uCamPos.setValue( _gl, _vector3.setFromMatrixPosition( camera.matrixWorld ) ); } if ( capabilities.logarithmicDepthBuffer ) { p_uniforms.setValue( _gl, 'logDepthBufFC', 2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) ); } // consider moving isOrthographic to UniformLib and WebGLMaterials, see https://github.com/mrdoob/three.js/pull/26467#issuecomment-1645185067 if ( material.isMeshPhongMaterial || material.isMeshToonMaterial || material.isMeshLambertMaterial || material.isMeshBasicMaterial || material.isMeshStandardMaterial || material.isShaderMaterial ) { p_uniforms.setValue( _gl, 'isOrthographic', camera.isOrthographicCamera === true ); } if ( _currentCamera !== camera ) { _currentCamera = camera; // lighting uniforms depend on the camera so enforce an update // now, in case this material supports lights - or later, when // the next material that does gets activated: refreshMaterial = true; // set to true on material change refreshLights = true; // remains set until update done } } // skinning and morph target uniforms must be set even if material didn't change // auto-setting of texture unit for bone and morph texture must go before other textures // otherwise textures used for skinning and morphing can take over texture units reserved for other material textures if ( object.isSkinnedMesh ) { p_uniforms.setOptional( _gl, object, 'bindMatrix' ); p_uniforms.setOptional( _gl, object, 'bindMatrixInverse' ); const skeleton = object.skeleton; if ( skeleton ) { if ( skeleton.boneTexture === null ) skeleton.computeBoneTexture(); p_uniforms.setValue( _gl, 'boneTexture', skeleton.boneTexture, textures ); } } if ( object.isBatchedMesh ) { p_uniforms.setOptional( _gl, object, 'batchingTexture' ); p_uniforms.setValue( _gl, 'batchingTexture', object._matricesTexture, textures ); p_uniforms.setOptional( _gl, object, 'batchingColorTexture' ); if ( object._colorsTexture !== null ) { p_uniforms.setValue( _gl, 'batchingColorTexture', object._colorsTexture, textures ); } } const morphAttributes = geometry.morphAttributes; if ( morphAttributes.position !== undefined || morphAttributes.normal !== undefined || ( morphAttributes.color !== undefined ) ) { morphtargets.update( object, geometry, program ); } if ( refreshMaterial || materialProperties.receiveShadow !== object.receiveShadow ) { materialProperties.receiveShadow = object.receiveShadow; p_uniforms.setValue( _gl, 'receiveShadow', object.receiveShadow ); } // https://github.com/mrdoob/three.js/pull/24467#issuecomment-1209031512 if ( material.isMeshGouraudMaterial && material.envMap !== null ) { m_uniforms.envMap.value = envMap; m_uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) ? - 1 : 1; } if ( material.isMeshStandardMaterial && material.envMap === null && scene.environment !== null ) { m_uniforms.envMapIntensity.value = scene.environmentIntensity; } if ( refreshMaterial ) { p_uniforms.setValue( _gl, 'toneMappingExposure', _this.toneMappingExposure ); if ( materialProperties.needsLights ) { // the current material requires lighting info // note: all lighting uniforms are always set correctly // they simply reference the renderer's state for their // values // // use the current material's .needsUpdate flags to set // the GL state when required markUniformsLightsNeedsUpdate( m_uniforms, refreshLights ); } // refresh uniforms common to several materials if ( fog && material.fog === true ) { materials.refreshFogUniforms( m_uniforms, fog ); } materials.refreshMaterialUniforms( m_uniforms, material, _pixelRatio, _height, currentRenderState.state.transmissionRenderTarget[ camera.id ] ); WebGLUniforms.upload( _gl, getUniformList( materialProperties ), m_uniforms, textures ); } if ( material.isShaderMaterial && material.uniformsNeedUpdate === true ) { WebGLUniforms.upload( _gl, getUniformList( materialProperties ), m_uniforms, textures ); material.uniformsNeedUpdate = false; } if ( material.isSpriteMaterial ) { p_uniforms.setValue( _gl, 'center', object.center ); } // common matrices p_uniforms.setValue( _gl, 'modelViewMatrix', object.modelViewMatrix ); p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix ); p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld ); // UBOs if ( material.isShaderMaterial || material.isRawShaderMaterial ) { const groups = material.uniformsGroups; for ( let i = 0, l = groups.length; i < l; i ++ ) { const group = groups[ i ]; uniformsGroups.update( group, program ); uniformsGroups.bind( group, program ); } } return program; } // If uniforms are marked as clean, they don't need to be loaded to the GPU. function markUniformsLightsNeedsUpdate( uniforms, value ) { uniforms.ambientLightColor.needsUpdate = value; uniforms.lightProbe.needsUpdate = value; uniforms.directionalLights.needsUpdate = value; uniforms.directionalLightShadows.needsUpdate = value; uniforms.pointLights.needsUpdate = value; uniforms.pointLightShadows.needsUpdate = value; uniforms.spotLights.needsUpdate = value; uniforms.spotLightShadows.needsUpdate = value; uniforms.rectAreaLights.needsUpdate = value; uniforms.hemisphereLights.needsUpdate = value; } function materialNeedsLights( material ) { return material.isMeshLambertMaterial || material.isMeshToonMaterial || material.isMeshPhongMaterial || material.isMeshStandardMaterial || material.isShadowMaterial || ( material.isShaderMaterial && material.lights === true ); } this.getActiveCubeFace = function () { return _currentActiveCubeFace; }; this.getActiveMipmapLevel = function () { return _currentActiveMipmapLevel; }; this.getRenderTarget = function () { return _currentRenderTarget; }; this.setRenderTargetTextures = function ( renderTarget, colorTexture, depthTexture ) { properties.get( renderTarget.texture ).__webglTexture = colorTexture; properties.get( renderTarget.depthTexture ).__webglTexture = depthTexture; const renderTargetProperties = properties.get( renderTarget ); renderTargetProperties.__hasExternalTextures = true; renderTargetProperties.__autoAllocateDepthBuffer = depthTexture === undefined; if ( ! renderTargetProperties.__autoAllocateDepthBuffer ) { // The multisample_render_to_texture extension doesn't work properly if there // are midframe flushes and an external depth buffer. Disable use of the extension. if ( extensions.has( 'WEBGL_multisampled_render_to_texture' ) === true ) { console.warn( 'THREE.WebGLRenderer: Render-to-texture extension was disabled because an external texture was provided' ); renderTargetProperties.__useRenderToTexture = false; } } }; this.setRenderTargetFramebuffer = function ( renderTarget, defaultFramebuffer ) { const renderTargetProperties = properties.get( renderTarget ); renderTargetProperties.__webglFramebuffer = defaultFramebuffer; renderTargetProperties.__useDefaultFramebuffer = defaultFramebuffer === undefined; }; this.setRenderTarget = function ( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0 ) { _currentRenderTarget = renderTarget; _currentActiveCubeFace = activeCubeFace; _currentActiveMipmapLevel = activeMipmapLevel; let useDefaultFramebuffer = true; let framebuffer = null; let isCube = false; let isRenderTarget3D = false; if ( renderTarget ) { const renderTargetProperties = properties.get( renderTarget ); if ( renderTargetProperties.__useDefaultFramebuffer !== undefined ) { // We need to make sure to rebind the framebuffer. state.bindFramebuffer( _gl.FRAMEBUFFER, null ); useDefaultFramebuffer = false; } else if ( renderTargetProperties.__webglFramebuffer === undefined ) { textures.setupRenderTarget( renderTarget ); } else if ( renderTargetProperties.__hasExternalTextures ) { // Color and depth texture must be rebound in order for the swapchain to update. textures.rebindTextures( renderTarget, properties.get( renderTarget.texture ).__webglTexture, properties.get( renderTarget.depthTexture ).__webglTexture ); } const texture = renderTarget.texture; if ( texture.isData3DTexture || texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { isRenderTarget3D = true; } const __webglFramebuffer = properties.get( renderTarget ).__webglFramebuffer; if ( renderTarget.isWebGLCubeRenderTarget ) { if ( Array.isArray( __webglFramebuffer[ activeCubeFace ] ) ) { framebuffer = __webglFramebuffer[ activeCubeFace ][ activeMipmapLevel ]; } else { framebuffer = __webglFramebuffer[ activeCubeFace ]; } isCube = true; } else if ( ( renderTarget.samples > 0 ) && textures.useMultisampledRTT( renderTarget ) === false ) { framebuffer = properties.get( renderTarget ).__webglMultisampledFramebuffer; } else { if ( Array.isArray( __webglFramebuffer ) ) { framebuffer = __webglFramebuffer[ activeMipmapLevel ]; } else { framebuffer = __webglFramebuffer; } } _currentViewport.copy( renderTarget.viewport ); _currentScissor.copy( renderTarget.scissor ); _currentScissorTest = renderTarget.scissorTest; } else { _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor(); _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor(); _currentScissorTest = _scissorTest; } const framebufferBound = state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); if ( framebufferBound && useDefaultFramebuffer ) { state.drawBuffers( renderTarget, framebuffer ); } state.viewport( _currentViewport ); state.scissor( _currentScissor ); state.setScissorTest( _currentScissorTest ); if ( isCube ) { const textureProperties = properties.get( renderTarget.texture ); _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + activeCubeFace, textureProperties.__webglTexture, activeMipmapLevel ); } else if ( isRenderTarget3D ) { const textureProperties = properties.get( renderTarget.texture ); const layer = activeCubeFace || 0; _gl.framebufferTextureLayer( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, textureProperties.__webglTexture, activeMipmapLevel || 0, layer ); } _currentMaterialId = - 1; // reset current material to ensure correct uniform bindings }; this.readRenderTargetPixels = function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex ) { if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) { console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' ); return; } let framebuffer = properties.get( renderTarget ).__webglFramebuffer; if ( renderTarget.isWebGLCubeRenderTarget && activeCubeFaceIndex !== undefined ) { framebuffer = framebuffer[ activeCubeFaceIndex ]; } if ( framebuffer ) { state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); try { const texture = renderTarget.texture; const textureFormat = texture.format; const textureType = texture.type; if ( ! capabilities.textureFormatReadable( textureFormat ) ) { console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.' ); return; } if ( ! capabilities.textureTypeReadable( textureType ) ) { console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.' ); return; } // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604) if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) { _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), buffer ); } } finally { // restore framebuffer of current render target if necessary const framebuffer = ( _currentRenderTarget !== null ) ? properties.get( _currentRenderTarget ).__webglFramebuffer : null; state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); } } }; this.readRenderTargetPixelsAsync = async function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex ) { if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) { throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' ); } let framebuffer = properties.get( renderTarget ).__webglFramebuffer; if ( renderTarget.isWebGLCubeRenderTarget && activeCubeFaceIndex !== undefined ) { framebuffer = framebuffer[ activeCubeFaceIndex ]; } if ( framebuffer ) { state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); try { const texture = renderTarget.texture; const textureFormat = texture.format; const textureType = texture.type; if ( ! capabilities.textureFormatReadable( textureFormat ) ) { throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixelsAsync: renderTarget is not in RGBA or implementation defined format.' ); } if ( ! capabilities.textureTypeReadable( textureType ) ) { throw new Error( 'THREE.WebGLRenderer.readRenderTargetPixelsAsync: renderTarget is not in UnsignedByteType or implementation defined type.' ); } // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604) if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) { const glBuffer = _gl.createBuffer(); _gl.bindBuffer( _gl.PIXEL_PACK_BUFFER, glBuffer ); _gl.bufferData( _gl.PIXEL_PACK_BUFFER, buffer.byteLength, _gl.STREAM_READ ); _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), 0 ); _gl.flush(); // check if the commands have finished every 8 ms const sync = _gl.fenceSync( _gl.SYNC_GPU_COMMANDS_COMPLETE, 0 ); await probeAsync( _gl, sync, 4 ); try { _gl.bindBuffer( _gl.PIXEL_PACK_BUFFER, glBuffer ); _gl.getBufferSubData( _gl.PIXEL_PACK_BUFFER, 0, buffer ); } finally { _gl.deleteBuffer( glBuffer ); _gl.deleteSync( sync ); } return buffer; } } finally { // restore framebuffer of current render target if necessary const framebuffer = ( _currentRenderTarget !== null ) ? properties.get( _currentRenderTarget ).__webglFramebuffer : null; state.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); } } }; this.copyFramebufferToTexture = function ( texture, position = null, level = 0 ) { // support previous signature with position first if ( texture.isTexture !== true ) { // @deprecated, r165 console.warn( 'WebGLRenderer: copyFramebufferToTexture function signature has changed.' ); position = arguments[ 0 ] || null; texture = arguments[ 1 ]; } const levelScale = Math.pow( 2, - level ); const width = Math.floor( texture.image.width * levelScale ); const height = Math.floor( texture.image.height * levelScale ); const x = position !== null ? position.x : 0; const y = position !== null ? position.y : 0; textures.setTexture2D( texture, 0 ); _gl.copyTexSubImage2D( _gl.TEXTURE_2D, level, 0, 0, x, y, width, height ); state.unbindTexture(); }; this.copyTextureToTexture = function ( srcTexture, dstTexture, srcRegion = null, dstPosition = null, level = 0 ) { // support previous signature with dstPosition first if ( srcTexture.isTexture !== true ) { // @deprecated, r165 console.warn( 'WebGLRenderer: copyTextureToTexture function signature has changed.' ); dstPosition = arguments[ 0 ] || null; srcTexture = arguments[ 1 ]; dstTexture = arguments[ 2 ]; level = arguments[ 3 ] || 0; srcRegion = null; } let width, height, minX, minY; let dstX, dstY; if ( srcRegion !== null ) { width = srcRegion.max.x - srcRegion.min.x; height = srcRegion.max.y - srcRegion.min.y; minX = srcRegion.min.x; minY = srcRegion.min.y; } else { width = srcTexture.image.width; height = srcTexture.image.height; minX = 0; minY = 0; } if ( dstPosition !== null ) { dstX = dstPosition.x; dstY = dstPosition.y; } else { dstX = 0; dstY = 0; } const glFormat = utils.convert( dstTexture.format ); const glType = utils.convert( dstTexture.type ); textures.setTexture2D( dstTexture, 0 ); // As another texture upload may have changed pixelStorei // parameters, make sure they are correct for the dstTexture _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, dstTexture.flipY ); _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha ); _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment ); const currentUnpackRowLen = _gl.getParameter( _gl.UNPACK_ROW_LENGTH ); const currentUnpackImageHeight = _gl.getParameter( _gl.UNPACK_IMAGE_HEIGHT ); const currentUnpackSkipPixels = _gl.getParameter( _gl.UNPACK_SKIP_PIXELS ); const currentUnpackSkipRows = _gl.getParameter( _gl.UNPACK_SKIP_ROWS ); const currentUnpackSkipImages = _gl.getParameter( _gl.UNPACK_SKIP_IMAGES ); const image = srcTexture.isCompressedTexture ? srcTexture.mipmaps[ level ] : srcTexture.image; _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, image.width ); _gl.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, image.height ); _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, minX ); _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, minY ); if ( srcTexture.isDataTexture ) { _gl.texSubImage2D( _gl.TEXTURE_2D, level, dstX, dstY, width, height, glFormat, glType, image.data ); } else { if ( srcTexture.isCompressedTexture ) { _gl.compressedTexSubImage2D( _gl.TEXTURE_2D, level, dstX, dstY, image.width, image.height, glFormat, image.data ); } else { _gl.texSubImage2D( _gl.TEXTURE_2D, level, dstX, dstY, glFormat, glType, image ); } } _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, currentUnpackRowLen ); _gl.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, currentUnpackImageHeight ); _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, currentUnpackSkipPixels ); _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, currentUnpackSkipRows ); _gl.pixelStorei( _gl.UNPACK_SKIP_IMAGES, currentUnpackSkipImages ); // Generate mipmaps only when copying level 0 if ( level === 0 && dstTexture.generateMipmaps ) _gl.generateMipmap( _gl.TEXTURE_2D ); state.unbindTexture(); }; this.copyTextureToTexture3D = function ( srcTexture, dstTexture, srcRegion = null, dstPosition = null, level = 0 ) { // support previous signature with source box first if ( srcTexture.isTexture !== true ) { // @deprecated, r165 console.warn( 'WebGLRenderer: copyTextureToTexture3D function signature has changed.' ); srcRegion = arguments[ 0 ] || null; dstPosition = arguments[ 1 ] || null; srcTexture = arguments[ 2 ]; dstTexture = arguments[ 3 ]; level = arguments[ 4 ] || 0; } let width, height, depth, minX, minY, minZ; let dstX, dstY, dstZ; const image = srcTexture.isCompressedTexture ? srcTexture.mipmaps[ level ] : srcTexture.image; if ( srcRegion !== null ) { width = srcRegion.max.x - srcRegion.min.x; height = srcRegion.max.y - srcRegion.min.y; depth = srcRegion.max.z - srcRegion.min.z; minX = srcRegion.min.x; minY = srcRegion.min.y; minZ = srcRegion.min.z; } else { width = image.width; height = image.height; depth = image.depth; minX = 0; minY = 0; minZ = 0; } if ( dstPosition !== null ) { dstX = dstPosition.x; dstY = dstPosition.y; dstZ = dstPosition.z; } else { dstX = 0; dstY = 0; dstZ = 0; } const glFormat = utils.convert( dstTexture.format ); const glType = utils.convert( dstTexture.type ); let glTarget; if ( dstTexture.isData3DTexture ) { textures.setTexture3D( dstTexture, 0 ); glTarget = _gl.TEXTURE_3D; } else if ( dstTexture.isDataArrayTexture || dstTexture.isCompressedArrayTexture ) { textures.setTexture2DArray( dstTexture, 0 ); glTarget = _gl.TEXTURE_2D_ARRAY; } else { console.warn( 'THREE.WebGLRenderer.copyTextureToTexture3D: only supports THREE.DataTexture3D and THREE.DataTexture2DArray.' ); return; } _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, dstTexture.flipY ); _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, dstTexture.premultiplyAlpha ); _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, dstTexture.unpackAlignment ); const currentUnpackRowLen = _gl.getParameter( _gl.UNPACK_ROW_LENGTH ); const currentUnpackImageHeight = _gl.getParameter( _gl.UNPACK_IMAGE_HEIGHT ); const currentUnpackSkipPixels = _gl.getParameter( _gl.UNPACK_SKIP_PIXELS ); const currentUnpackSkipRows = _gl.getParameter( _gl.UNPACK_SKIP_ROWS ); const currentUnpackSkipImages = _gl.getParameter( _gl.UNPACK_SKIP_IMAGES ); _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, image.width ); _gl.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, image.height ); _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, minX ); _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, minY ); _gl.pixelStorei( _gl.UNPACK_SKIP_IMAGES, minZ ); if ( srcTexture.isDataTexture || srcTexture.isData3DTexture ) { _gl.texSubImage3D( glTarget, level, dstX, dstY, dstZ, width, height, depth, glFormat, glType, image.data ); } else { if ( dstTexture.isCompressedArrayTexture ) { _gl.compressedTexSubImage3D( glTarget, level, dstX, dstY, dstZ, width, height, depth, glFormat, image.data ); } else { _gl.texSubImage3D( glTarget, level, dstX, dstY, dstZ, width, height, depth, glFormat, glType, image ); } } _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, currentUnpackRowLen ); _gl.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, currentUnpackImageHeight ); _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, currentUnpackSkipPixels ); _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, currentUnpackSkipRows ); _gl.pixelStorei( _gl.UNPACK_SKIP_IMAGES, currentUnpackSkipImages ); // Generate mipmaps only when copying level 0 if ( level === 0 && dstTexture.generateMipmaps ) _gl.generateMipmap( glTarget ); state.unbindTexture(); }; this.initRenderTarget = function ( target ) { if ( properties.get( target ).__webglFramebuffer === undefined ) { textures.setupRenderTarget( target ); } }; this.initTexture = function ( texture ) { if ( texture.isCubeTexture ) { textures.setTextureCube( texture, 0 ); } else if ( texture.isData3DTexture ) { textures.setTexture3D( texture, 0 ); } else if ( texture.isDataArrayTexture || texture.isCompressedArrayTexture ) { textures.setTexture2DArray( texture, 0 ); } else { textures.setTexture2D( texture, 0 ); } state.unbindTexture(); }; this.resetState = function () { _currentActiveCubeFace = 0; _currentActiveMipmapLevel = 0; _currentRenderTarget = null; state.reset(); bindingStates.reset(); }; if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) { __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); } } get coordinateSystem() { return WebGLCoordinateSystem; } get outputColorSpace() { return this._outputColorSpace; } set outputColorSpace( colorSpace ) { this._outputColorSpace = colorSpace; const gl = this.getContext(); gl.drawingBufferColorSpace = colorSpace === DisplayP3ColorSpace ? 'display-p3' : 'srgb'; gl.unpackColorSpace = ColorManagement.workingColorSpace === LinearDisplayP3ColorSpace ? 'display-p3' : 'srgb'; } } export { WebGLRenderer };