Permalink
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
three.js/src/renderers/WebGLRenderer.js /
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* WebGLRenderer: convert to ES6 class * Switch back to prototype.isWebGL1Renderer = true * Trigger ci * Trigger ci
2312 lines (1358 sloc)
59.8 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { | |
| REVISION, | |
| BackSide, | |
| FrontSide, | |
| DoubleSide, | |
| RGBAFormat, | |
| HalfFloatType, | |
| FloatType, | |
| UnsignedByteType, | |
| LinearEncoding, | |
| NoToneMapping, | |
| LinearMipmapLinearFilter | |
| } from '../constants.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 { createElementNS } from '../utils.js'; | |
| function createCanvasElement() { | |
| const canvas = createElementNS( 'canvas' ); | |
| canvas.style.display = 'block'; | |
| return canvas; | |
| } | |
| class WebGLRenderer { | |
| constructor( parameters = {} ) { | |
| const { | |
| canvas = createCanvasElement(), | |
| context = null, | |
| depth = true, | |
| stencil = true, | |
| alpha = false, | |
| antialias = false, | |
| premultipliedAlpha = true, | |
| preserveDrawingBuffer = false, | |
| powerPreference = 'default', | |
| failIfMajorPerformanceCaveat = false, | |
| } = parameters; | |
| this.isWebGLRenderer = true; | |
| let _alpha; | |
| if ( context !== null ) { | |
| _alpha = context.getContextAttributes().alpha; | |
| } else { | |
| _alpha = alpha; | |
| } | |
| 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 | |
| }; | |
| // 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.outputEncoding = LinearEncoding; | |
| // physical lights | |
| this.useLegacyLights = true; | |
| // 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; | |
| // | |
| 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; | |
| // transmission | |
| let _transmissionRenderTarget = null; | |
| // camera matrices cache | |
| const _projScreenMatrix = new Matrix4(); | |
| const _vector3 = new Vector3(); | |
| const _emptyScene = { background: null, fog: null, environment: null, overrideMaterial: null, isScene: true }; | |
| function getTargetPixelRatio() { | |
| return _currentRenderTarget === null ? _pixelRatio : 1; | |
| } | |
| // initialize | |
| let _gl = context; | |
| function getContext( contextNames, contextAttributes ) { | |
| for ( let i = 0; i < contextNames.length; i ++ ) { | |
| const contextName = contextNames[ i ]; | |
| const context = canvas.getContext( contextName, contextAttributes ); | |
| if ( context !== null ) return context; | |
| } | |
| return null; | |
| } | |
| 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 contextNames = [ 'webgl2', 'webgl', 'experimental-webgl' ]; | |
| if ( _this.isWebGL1Renderer === true ) { | |
| contextNames.shift(); | |
| } | |
| _gl = getContext( contextNames, contextAttributes ); | |
| if ( _gl === null ) { | |
| if ( getContext( contextNames ) ) { | |
| throw new Error( 'Error creating WebGL context with your selected attributes.' ); | |
| } else { | |
| throw new Error( 'Error creating WebGL context.' ); | |
| } | |
| } | |
| } | |
| // Some experimental-webgl implementations do not have getShaderPrecisionFormat | |
| if ( _gl.getShaderPrecisionFormat === undefined ) { | |
| _gl.getShaderPrecisionFormat = function () { | |
| return { 'rangeMin': 1, 'rangeMax': 1, 'precision': 1 }; | |
| }; | |
| } | |
| } 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 ); | |
| capabilities = new WebGLCapabilities( _gl, extensions, parameters ); | |
| extensions.init( capabilities ); | |
| utils = new WebGLUtils( _gl, extensions, capabilities ); | |
| state = new WebGLState( _gl, extensions, capabilities ); | |
| 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, capabilities ); | |
| bindingStates = new WebGLBindingStates( _gl, extensions, attributes, capabilities ); | |
| 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, capabilities ); | |
| 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, capabilities ); | |
| indexedBufferRenderer = new WebGLIndexedBufferRenderer( _gl, extensions, info, capabilities ); | |
| 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 ).floor() ); | |
| }; | |
| 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 ).floor() ); | |
| }; | |
| 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 ) bits |= _gl.COLOR_BUFFER_BIT; | |
| if ( depth ) bits |= _gl.DEPTH_BUFFER_BIT; | |
| if ( stencil ) bits |= _gl.STENCIL_BUFFER_BIT; | |
| _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 ); | |
| if ( _transmissionRenderTarget ) { | |
| _transmissionRenderTarget.dispose(); | |
| _transmissionRenderTarget = null; | |
| } | |
| 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 ); | |
| 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.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 | |
| this.compile = function ( scene, camera ) { | |
| function prepare( 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 ); | |
| } | |
| } | |
| currentRenderState = renderStates.get( scene ); | |
| currentRenderState.init(); | |
| renderStateStack.push( currentRenderState ); | |
| scene.traverseVisible( function ( object ) { | |
| if ( object.isLight && object.layers.test( camera.layers ) ) { | |
| currentRenderState.pushLight( object ); | |
| if ( object.castShadow ) { | |
| currentRenderState.pushShadow( object ); | |
| } | |
| } | |
| } ); | |
| currentRenderState.setupLights( _this.useLegacyLights ); | |
| 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 ]; | |
| prepare( material2, scene, object ); | |
| } | |
| } else { | |
| prepare( material, scene, object ); | |
| } | |
| } | |
| } ); | |
| renderStateStack.pop(); | |
| currentRenderState = null; | |
| }; | |
| // 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(); | |
| 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 ); | |
| projectObject( scene, camera, 0, _this.sortObjects ); | |
| currentRenderList.finish(); | |
| if ( _this.sortObjects === true ) { | |
| currentRenderList.sort( _opaqueSort, _transparentSort ); | |
| } | |
| // | |
| 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(); | |
| // | |
| background.render( currentRenderList, scene ); | |
| // render scene | |
| currentRenderState.setupLights( _this.useLegacyLights ); | |
| if ( camera.isArrayCamera ) { | |
| const cameras = camera.cameras; | |
| for ( let i = 0, l = cameras.length; i < l; i ++ ) { | |
| const camera2 = cameras[ i ]; | |
| renderScene( currentRenderList, scene, camera2, camera2.viewport ); | |
| } | |
| } else { | |
| 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 ]; | |
| } 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.isSkinnedMesh ) { | |
| // update skeleton only once in a frame | |
| if ( object.skeleton.frame !== info.render.frame ) { | |
| object.skeleton.update(); | |
| object.skeleton.frame = info.render.frame; | |
| } | |
| } | |
| if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) { | |
| if ( sortObjects ) { | |
| _vector3.setFromMatrixPosition( object.matrixWorld ) | |
| .applyMatrix4( _projScreenMatrix ); | |
| } | |
| const geometry = objects.update( object ); | |
| const material = object.material; | |
| 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 ( transmissiveObjects.length > 0 ) renderTransmissionPass( opaqueObjects, transmissiveObjects, scene, 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 ) { | |
| if ( _transmissionRenderTarget === null ) { | |
| const isWebGL2 = capabilities.isWebGL2; | |
| _transmissionRenderTarget = new WebGLRenderTarget( 1024, 1024, { | |
| generateMipmaps: true, | |
| type: extensions.has( 'EXT_color_buffer_half_float' ) ? HalfFloatType : UnsignedByteType, | |
| minFilter: LinearMipmapLinearFilter, | |
| samples: ( isWebGL2 && antialias === true ) ? 4 : 0 | |
| } ); | |
| // debug | |
| /* | |
| const geometry = new PlaneGeometry(); | |
| const material = new MeshBasicMaterial( { map: _transmissionRenderTarget.texture } ); | |
| const mesh = new Mesh( geometry, material ); | |
| scene.add( mesh ); | |
| */ | |
| } | |
| // | |
| const currentRenderTarget = _this.getRenderTarget(); | |
| _this.setRenderTarget( _transmissionRenderTarget ); | |
| _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; | |
| renderObjects( opaqueObjects, scene, camera ); | |
| textures.updateMultisampleRenderTarget( _transmissionRenderTarget ); | |
| textures.updateRenderTargetMipmap( _transmissionRenderTarget ); | |
| 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.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 ); | |
| 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 | |
| } | |
| const progUniforms = program.getUniforms(); | |
| const uniformsList = WebGLUniforms.seqWithValue( progUniforms.seq, uniforms ); | |
| materialProperties.currentProgram = program; | |
| materialProperties.uniformsList = uniformsList; | |
| return program; | |
| } | |
| function updateCommonMaterialProperties( material, parameters ) { | |
| const materialProperties = properties.get( material ); | |
| materialProperties.outputEncoding = parameters.outputEncoding; | |
| materialProperties.instancing = parameters.instancing; | |
| 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 encoding = ( _currentRenderTarget === null ) ? _this.outputEncoding : ( _currentRenderTarget.isXRRenderTarget === true ? _currentRenderTarget.texture.encoding : LinearEncoding ); | |
| 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 = !! material.normalMap && !! geometry.attributes.tangent; | |
| const morphTargets = !! geometry.morphAttributes.position; | |
| const morphNormals = !! geometry.morphAttributes.normal; | |
| const morphColors = !! geometry.morphAttributes.color; | |
| const toneMapping = material.toneMapped ? _this.toneMapping : NoToneMapping; | |
| 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.outputEncoding !== encoding ) { | |
| 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 ( 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 ( capabilities.isWebGL2 === true && 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 ) { | |
| p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix ); | |
| if ( capabilities.logarithmicDepthBuffer ) { | |
| p_uniforms.setValue( _gl, 'logDepthBufFC', | |
| 2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) ); | |
| } | |
| 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 | |
| } | |
| // load material specific uniforms | |
| // (shader material also gets them for the sake of genericity) | |
| if ( material.isShaderMaterial || | |
| material.isMeshPhongMaterial || | |
| material.isMeshToonMaterial || | |
| material.isMeshStandardMaterial || | |
| material.envMap ) { | |
| const uCamPos = p_uniforms.map.cameraPosition; | |
| if ( uCamPos !== undefined ) { | |
| uCamPos.setValue( _gl, | |
| _vector3.setFromMatrixPosition( camera.matrixWorld ) ); | |
| } | |
| } | |
| if ( material.isMeshPhongMaterial || | |
| material.isMeshToonMaterial || | |
| material.isMeshLambertMaterial || | |
| material.isMeshBasicMaterial || | |
| material.isMeshStandardMaterial || | |
| material.isShaderMaterial ) { | |
| p_uniforms.setValue( _gl, 'isOrthographic', camera.isOrthographicCamera === true ); | |
| } | |
| if ( material.isMeshPhongMaterial || | |
| material.isMeshToonMaterial || | |
| material.isMeshLambertMaterial || | |
| material.isMeshBasicMaterial || | |
| material.isMeshStandardMaterial || | |
| material.isShaderMaterial || | |
| material.isShadowMaterial || | |
| object.isSkinnedMesh ) { | |
| p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse ); | |
| } | |
| } | |
| // 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 ( capabilities.floatVertexTextures ) { | |
| if ( skeleton.boneTexture === null ) skeleton.computeBoneTexture(); | |
| p_uniforms.setValue( _gl, 'boneTexture', skeleton.boneTexture, textures ); | |
| p_uniforms.setValue( _gl, 'boneTextureSize', skeleton.boneTextureSize ); | |
| } else { | |
| console.warn( 'THREE.WebGLRenderer: SkinnedMesh can only be used with WebGL 2. With WebGL 1 OES_texture_float and vertex textures support is required.' ); | |
| } | |
| } | |
| } | |
| const morphAttributes = geometry.morphAttributes; | |
| if ( morphAttributes.position !== undefined || morphAttributes.normal !== undefined || ( morphAttributes.color !== undefined && capabilities.isWebGL2 === true ) ) { | |
| 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 ( 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, _transmissionRenderTarget ); | |
| WebGLUniforms.upload( _gl, materialProperties.uniformsList, m_uniforms, textures ); | |
| } | |
| if ( material.isShaderMaterial && material.uniformsNeedUpdate === true ) { | |
| WebGLUniforms.upload( _gl, materialProperties.uniformsList, 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 ++ ) { | |
| if ( capabilities.isWebGL2 ) { | |
| const group = groups[ i ]; | |
| uniformsGroups.update( group, program ); | |
| uniformsGroups.bind( group, program ); | |
| } else { | |
| console.warn( 'THREE.WebGLRenderer: Uniform Buffer Objects can only be used with WebGL 2.' ); | |
| } | |
| } | |
| } | |
| 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; | |
| if ( renderTargetProperties.__hasExternalTextures ) { | |
| 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 ) { | |
| framebuffer = __webglFramebuffer[ activeCubeFace ]; | |
| isCube = true; | |
| } else if ( ( capabilities.isWebGL2 && renderTarget.samples > 0 ) && textures.useMultisampledRTT( renderTarget ) === false ) { | |
| framebuffer = properties.get( renderTarget ).__webglMultisampledFramebuffer; | |
| } 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 && capabilities.drawBuffers && 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 ( textureFormat !== RGBAFormat && utils.convert( textureFormat ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_FORMAT ) ) { | |
| console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.' ); | |
| return; | |
| } | |
| const halfFloatSupportedByExt = ( textureType === HalfFloatType ) && ( extensions.has( 'EXT_color_buffer_half_float' ) || ( capabilities.isWebGL2 && extensions.has( 'EXT_color_buffer_float' ) ) ); | |
| if ( textureType !== UnsignedByteType && utils.convert( textureType ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_TYPE ) && // Edge and Chrome Mac < 52 (#9513) | |
| ! ( textureType === FloatType && ( capabilities.isWebGL2 || extensions.has( 'OES_texture_float' ) || extensions.has( 'WEBGL_color_buffer_float' ) ) ) && // Chrome Mac >= 52 and Firefox | |
| ! halfFloatSupportedByExt ) { | |
| 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.copyFramebufferToTexture = function ( position, texture, level = 0 ) { | |
| const levelScale = Math.pow( 2, - level ); | |
| const width = Math.floor( texture.image.width * levelScale ); | |
| const height = Math.floor( texture.image.height * levelScale ); | |
| textures.setTexture2D( texture, 0 ); | |
| _gl.copyTexSubImage2D( _gl.TEXTURE_2D, level, 0, 0, position.x, position.y, width, height ); | |
| state.unbindTexture(); | |
| }; | |
| this.copyTextureToTexture = function ( position, srcTexture, dstTexture, level = 0 ) { | |
| const width = srcTexture.image.width; | |
| const height = srcTexture.image.height; | |
| 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 ); | |
| if ( srcTexture.isDataTexture ) { | |
| _gl.texSubImage2D( _gl.TEXTURE_2D, level, position.x, position.y, width, height, glFormat, glType, srcTexture.image.data ); | |
| } else { | |
| if ( srcTexture.isCompressedTexture ) { | |
| _gl.compressedTexSubImage2D( _gl.TEXTURE_2D, level, position.x, position.y, srcTexture.mipmaps[ 0 ].width, srcTexture.mipmaps[ 0 ].height, glFormat, srcTexture.mipmaps[ 0 ].data ); | |
| } else { | |
| _gl.texSubImage2D( _gl.TEXTURE_2D, level, position.x, position.y, glFormat, glType, srcTexture.image ); | |
| } | |
| } | |
| // Generate mipmaps only when copying level 0 | |
| if ( level === 0 && dstTexture.generateMipmaps ) _gl.generateMipmap( _gl.TEXTURE_2D ); | |
| state.unbindTexture(); | |
| }; | |
| this.copyTextureToTexture3D = function ( sourceBox, position, srcTexture, dstTexture, level = 0 ) { | |
| if ( _this.isWebGL1Renderer ) { | |
| console.warn( 'THREE.WebGLRenderer.copyTextureToTexture3D: can only be used with WebGL2.' ); | |
| return; | |
| } | |
| const width = sourceBox.max.x - sourceBox.min.x + 1; | |
| const height = sourceBox.max.y - sourceBox.min.y + 1; | |
| const depth = sourceBox.max.z - sourceBox.min.z + 1; | |
| 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 ) { | |
| 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 unpackRowLen = _gl.getParameter( _gl.UNPACK_ROW_LENGTH ); | |
| const unpackImageHeight = _gl.getParameter( _gl.UNPACK_IMAGE_HEIGHT ); | |
| const unpackSkipPixels = _gl.getParameter( _gl.UNPACK_SKIP_PIXELS ); | |
| const unpackSkipRows = _gl.getParameter( _gl.UNPACK_SKIP_ROWS ); | |
| const unpackSkipImages = _gl.getParameter( _gl.UNPACK_SKIP_IMAGES ); | |
| const image = srcTexture.isCompressedTexture ? srcTexture.mipmaps[ 0 ] : srcTexture.image; | |
| _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, image.width ); | |
| _gl.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, image.height ); | |
| _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, sourceBox.min.x ); | |
| _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, sourceBox.min.y ); | |
| _gl.pixelStorei( _gl.UNPACK_SKIP_IMAGES, sourceBox.min.z ); | |
| if ( srcTexture.isDataTexture || srcTexture.isData3DTexture ) { | |
| _gl.texSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, glType, image.data ); | |
| } else { | |
| if ( srcTexture.isCompressedArrayTexture ) { | |
| console.warn( 'THREE.WebGLRenderer.copyTextureToTexture3D: untested support for compressed srcTexture.' ); | |
| _gl.compressedTexSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, image.data ); | |
| } else { | |
| _gl.texSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, glType, image ); | |
| } | |
| } | |
| _gl.pixelStorei( _gl.UNPACK_ROW_LENGTH, unpackRowLen ); | |
| _gl.pixelStorei( _gl.UNPACK_IMAGE_HEIGHT, unpackImageHeight ); | |
| _gl.pixelStorei( _gl.UNPACK_SKIP_PIXELS, unpackSkipPixels ); | |
| _gl.pixelStorei( _gl.UNPACK_SKIP_ROWS, unpackSkipRows ); | |
| _gl.pixelStorei( _gl.UNPACK_SKIP_IMAGES, unpackSkipImages ); | |
| // Generate mipmaps only when copying level 0 | |
| if ( level === 0 && dstTexture.generateMipmaps ) _gl.generateMipmap( glTarget ); | |
| state.unbindTexture(); | |
| }; | |
| 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 } ) ); | |
| } | |
| } | |
| // @deprecated since r150 | |
| get physicallyCorrectLights() { | |
| console.warn( 'THREE.WebGLRenderer: the property .physicallyCorrectLights has been removed. Set renderer.useLegacyLights instead.' ); | |
| return ! this.useLegacyLights; | |
| } | |
| // @deprecated since r150 | |
| set physicallyCorrectLights( value ) { | |
| console.warn( 'THREE.WebGLRenderer: the property .physicallyCorrectLights has been removed. Set renderer.useLegacyLights instead.' ); | |
| this.useLegacyLights = ! value; | |
| } | |
| } | |
| export { WebGLRenderer }; |