/** * @author supereggbert / http://www.paulbrunt.co.uk/ * @author mrdoob / http://mrdoob.com/ * @author alteredq / http://alteredqualia.com/ * @author szimek / https://github.com/szimek/ */ THREE.WebGLRenderer = function ( parameters ) { console.log( 'THREE.WebGLRenderer', THREE.REVISION ); parameters = parameters || {}; var _canvas = parameters.canvas !== undefined ? parameters.canvas : document.createElement( 'canvas' ), _context = parameters.context !== undefined ? parameters.context : null, _alpha = parameters.alpha !== undefined ? parameters.alpha : false, _depth = parameters.depth !== undefined ? parameters.depth : true, _stencil = parameters.stencil !== undefined ? parameters.stencil : true, _antialias = parameters.antialias !== undefined ? parameters.antialias : false, _premultipliedAlpha = parameters.premultipliedAlpha !== undefined ? parameters.premultipliedAlpha : true, _preserveDrawingBuffer = parameters.preserveDrawingBuffer !== undefined ? parameters.preserveDrawingBuffer : false; var lights = []; var opaqueObjects = []; var opaqueObjectsLastIndex = - 1; var transparentObjects = []; var transparentObjectsLastIndex = - 1; var morphInfluences = new Float32Array( 8 ); var sprites = []; var lensFlares = []; // public properties this.domElement = _canvas; this.context = null; // clearing this.autoClear = true; this.autoClearColor = true; this.autoClearDepth = true; this.autoClearStencil = true; // scene graph this.sortObjects = true; // physically based shading this.gammaFactor = 2.0; // for backwards compatibility this.gammaInput = false; this.gammaOutput = false; // morphs this.maxMorphTargets = 8; this.maxMorphNormals = 4; // flags this.autoScaleCubemaps = true; // internal properties var _this = this, // internal state cache _currentProgram = null, _currentRenderTarget = null, _currentFramebuffer = null, _currentMaterialId = - 1, _currentGeometryProgram = '', _currentCamera = null, _currentScissor = new THREE.Vector4(), _currentScissorTest = null, _currentViewport = new THREE.Vector4(), // _usedTextureUnits = 0, // _clearColor = new THREE.Color( 0x000000 ), _clearAlpha = 0, _width = _canvas.width, _height = _canvas.height, _pixelRatio = 1, _scissor = new THREE.Vector4( 0, 0, _width, _height ), _scissorTest = false, _viewport = new THREE.Vector4( 0, 0, _width, _height ), // frustum _frustum = new THREE.Frustum(), // camera matrices cache _projScreenMatrix = new THREE.Matrix4(), _vector3 = new THREE.Vector3(), // light arrays cache _lights = { hash: '', ambient: [ 0, 0, 0 ], directional: [], directionalShadowMap: [], directionalShadowMatrix: [], spot: [], spotShadowMap: [], spotShadowMatrix: [], point: [], pointShadowMap: [], pointShadowMatrix: [], hemi: [], shadows: [], shadowsPointLight: 0 }, // info _infoMemory = { geometries: 0, textures: 0 }, _infoRender = { calls: 0, vertices: 0, faces: 0, points: 0 }; this.info = { render: _infoRender, memory: _infoMemory, programs: null }; // initialize var _gl; try { var attributes = { alpha: _alpha, depth: _depth, stencil: _stencil, antialias: _antialias, premultipliedAlpha: _premultipliedAlpha, preserveDrawingBuffer: _preserveDrawingBuffer }; _gl = _context || _canvas.getContext( 'webgl', attributes ) || _canvas.getContext( 'experimental-webgl', attributes ); if ( _gl === null ) { if ( _canvas.getContext( 'webgl' ) !== null ) { throw 'Error creating WebGL context with your selected attributes.'; } else { throw 'Error creating WebGL context.'; } } _canvas.addEventListener( 'webglcontextlost', onContextLost, false ); } catch ( error ) { console.error( 'THREE.WebGLRenderer: ' + error ); } var extensions = new THREE.WebGLExtensions( _gl ); extensions.get( 'OES_texture_float' ); extensions.get( 'OES_texture_float_linear' ); extensions.get( 'OES_texture_half_float' ); extensions.get( 'OES_texture_half_float_linear' ); extensions.get( 'OES_standard_derivatives' ); extensions.get( 'ANGLE_instanced_arrays' ); if ( extensions.get( 'OES_element_index_uint' ) ) { THREE.BufferGeometry.MaxIndex = 4294967296; } var capabilities = new THREE.WebGLCapabilities( _gl, extensions, parameters ); var state = new THREE.WebGLState( _gl, extensions, paramThreeToGL ); var properties = new THREE.WebGLProperties(); var objects = new THREE.WebGLObjects( _gl, properties, this.info ); var programCache = new THREE.WebGLPrograms( this, capabilities ); var lightCache = new THREE.WebGLLights(); this.info.programs = programCache.programs; var bufferRenderer = new THREE.WebGLBufferRenderer( _gl, extensions, _infoRender ); var indexedBufferRenderer = new THREE.WebGLIndexedBufferRenderer( _gl, extensions, _infoRender ); // function getTargetPixelRatio() { return _currentRenderTarget === null ? _pixelRatio : 1; } function glClearColor( r, g, b, a ) { if ( _premultipliedAlpha === true ) { r *= a; g *= a; b *= a; } state.clearColor( r, g, b, a ); } function setDefaultGLState() { state.init(); state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ) ); state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ) ); glClearColor( _clearColor.r, _clearColor.g, _clearColor.b, _clearAlpha ); } function resetGLState() { _currentProgram = null; _currentCamera = null; _currentGeometryProgram = ''; _currentMaterialId = - 1; state.reset(); } setDefaultGLState(); this.context = _gl; this.capabilities = capabilities; this.extensions = extensions; this.properties = properties; this.state = state; // shadow map var shadowMap = new THREE.WebGLShadowMap( this, _lights, objects ); this.shadowMap = shadowMap; // Plugins var spritePlugin = new THREE.SpritePlugin( this, sprites ); var lensFlarePlugin = new THREE.LensFlarePlugin( this, lensFlares ); // API this.getContext = function () { return _gl; }; this.getContextAttributes = function () { return _gl.getContextAttributes(); }; this.forceContextLoss = function () { extensions.get( 'WEBGL_lose_context' ).loseContext(); }; this.getMaxAnisotropy = ( function () { var value; return function getMaxAnisotropy() { if ( value !== undefined ) return value; var extension = extensions.get( 'EXT_texture_filter_anisotropic' ); if ( extension !== null ) { value = _gl.getParameter( extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT ); } else { value = 0; } return value; }; } )(); this.getPrecision = function () { return capabilities.precision; }; this.getPixelRatio = function () { return _pixelRatio; }; this.setPixelRatio = function ( value ) { if ( value === undefined ) return; _pixelRatio = value; this.setSize( _viewport.z, _viewport.w, false ); }; this.getSize = function () { return { width: _width, height: _height }; }; this.setSize = function ( width, height, updateStyle ) { _width = width; _height = height; _canvas.width = width * _pixelRatio; _canvas.height = height * _pixelRatio; if ( updateStyle !== false ) { _canvas.style.width = width + 'px'; _canvas.style.height = height + 'px'; } this.setViewport( 0, 0, width, height ); }; this.setViewport = function ( x, y, width, height ) { state.viewport( _viewport.set( x, y, width, height ) ); }; this.setScissor = function ( x, y, width, height ) { state.scissor( _scissor.set( x, y, width, height ) ); }; this.setScissorTest = function ( boolean ) { state.setScissorTest( _scissorTest = boolean ); }; // Clearing this.getClearColor = function () { return _clearColor; }; this.setClearColor = function ( color, alpha ) { _clearColor.set( color ); _clearAlpha = alpha !== undefined ? alpha : 1; glClearColor( _clearColor.r, _clearColor.g, _clearColor.b, _clearAlpha ); }; this.getClearAlpha = function () { return _clearAlpha; }; this.setClearAlpha = function ( alpha ) { _clearAlpha = alpha; glClearColor( _clearColor.r, _clearColor.g, _clearColor.b, _clearAlpha ); }; this.clear = function ( color, depth, stencil ) { var bits = 0; if ( color === undefined || color ) bits |= _gl.COLOR_BUFFER_BIT; if ( depth === undefined || depth ) bits |= _gl.DEPTH_BUFFER_BIT; if ( stencil === undefined || 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.clearTarget = function ( renderTarget, color, depth, stencil ) { this.setRenderTarget( renderTarget ); this.clear( color, depth, stencil ); }; // Reset this.resetGLState = resetGLState; this.dispose = function() { _canvas.removeEventListener( 'webglcontextlost', onContextLost, false ); }; // Events function onContextLost( event ) { event.preventDefault(); resetGLState(); setDefaultGLState(); properties.clear(); } function onTextureDispose( event ) { var texture = event.target; texture.removeEventListener( 'dispose', onTextureDispose ); deallocateTexture( texture ); _infoMemory.textures --; } function onRenderTargetDispose( event ) { var renderTarget = event.target; renderTarget.removeEventListener( 'dispose', onRenderTargetDispose ); deallocateRenderTarget( renderTarget ); _infoMemory.textures --; } function onMaterialDispose( event ) { var material = event.target; material.removeEventListener( 'dispose', onMaterialDispose ); deallocateMaterial( material ); } // Buffer deallocation function deallocateTexture( texture ) { var textureProperties = properties.get( texture ); if ( texture.image && textureProperties.__image__webglTextureCube ) { // cube texture _gl.deleteTexture( textureProperties.__image__webglTextureCube ); } else { // 2D texture if ( textureProperties.__webglInit === undefined ) return; _gl.deleteTexture( textureProperties.__webglTexture ); } // remove all webgl properties properties.delete( texture ); } function deallocateRenderTarget( renderTarget ) { var renderTargetProperties = properties.get( renderTarget ); var textureProperties = properties.get( renderTarget.texture ); if ( ! renderTarget || textureProperties.__webglTexture === undefined ) return; _gl.deleteTexture( textureProperties.__webglTexture ); if ( renderTarget instanceof THREE.WebGLRenderTargetCube ) { for ( var i = 0; i < 6; i ++ ) { _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ] ); _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer[ i ] ); } } else { _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer ); _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer ); } properties.delete( renderTarget.texture ); properties.delete( renderTarget ); } function deallocateMaterial( material ) { releaseMaterialProgramReference( material ); properties.delete( material ); } function releaseMaterialProgramReference( material ) { var programInfo = properties.get( material ).program; material.program = undefined; if ( programInfo !== undefined ) { programCache.releaseProgram( programInfo ); } } // Buffer rendering this.renderBufferImmediate = function ( object, program, material ) { state.initAttributes(); var buffers = properties.get( object ); if ( object.hasPositions && ! buffers.position ) buffers.position = _gl.createBuffer(); if ( object.hasNormals && ! buffers.normal ) buffers.normal = _gl.createBuffer(); if ( object.hasUvs && ! buffers.uv ) buffers.uv = _gl.createBuffer(); if ( object.hasColors && ! buffers.color ) buffers.color = _gl.createBuffer(); var attributes = program.getAttributes(); if ( object.hasPositions ) { _gl.bindBuffer( _gl.ARRAY_BUFFER, buffers.position ); _gl.bufferData( _gl.ARRAY_BUFFER, object.positionArray, _gl.DYNAMIC_DRAW ); state.enableAttribute( attributes.position ); _gl.vertexAttribPointer( attributes.position, 3, _gl.FLOAT, false, 0, 0 ); } if ( object.hasNormals ) { _gl.bindBuffer( _gl.ARRAY_BUFFER, buffers.normal ); if ( material.type !== 'MeshPhongMaterial' && material.type !== 'MeshStandardMaterial' && material.shading === THREE.FlatShading ) { for ( var i = 0, l = object.count * 3; i < l; i += 9 ) { var array = object.normalArray; var nx = ( array[ i + 0 ] + array[ i + 3 ] + array[ i + 6 ] ) / 3; var ny = ( array[ i + 1 ] + array[ i + 4 ] + array[ i + 7 ] ) / 3; var nz = ( array[ i + 2 ] + array[ i + 5 ] + array[ i + 8 ] ) / 3; array[ i + 0 ] = nx; array[ i + 1 ] = ny; array[ i + 2 ] = nz; array[ i + 3 ] = nx; array[ i + 4 ] = ny; array[ i + 5 ] = nz; array[ i + 6 ] = nx; array[ i + 7 ] = ny; array[ i + 8 ] = nz; } } _gl.bufferData( _gl.ARRAY_BUFFER, object.normalArray, _gl.DYNAMIC_DRAW ); state.enableAttribute( attributes.normal ); _gl.vertexAttribPointer( attributes.normal, 3, _gl.FLOAT, false, 0, 0 ); } if ( object.hasUvs && material.map ) { _gl.bindBuffer( _gl.ARRAY_BUFFER, buffers.uv ); _gl.bufferData( _gl.ARRAY_BUFFER, object.uvArray, _gl.DYNAMIC_DRAW ); state.enableAttribute( attributes.uv ); _gl.vertexAttribPointer( attributes.uv, 2, _gl.FLOAT, false, 0, 0 ); } if ( object.hasColors && material.vertexColors !== THREE.NoColors ) { _gl.bindBuffer( _gl.ARRAY_BUFFER, buffers.color ); _gl.bufferData( _gl.ARRAY_BUFFER, object.colorArray, _gl.DYNAMIC_DRAW ); state.enableAttribute( attributes.color ); _gl.vertexAttribPointer( attributes.color, 3, _gl.FLOAT, false, 0, 0 ); } state.disableUnusedAttributes(); _gl.drawArrays( _gl.TRIANGLES, 0, object.count ); object.count = 0; }; this.renderBufferDirect = function ( camera, fog, geometry, material, object, group ) { setMaterial( material ); var program = setProgram( camera, fog, material, object ); var updateBuffers = false; var geometryProgram = geometry.id + '_' + program.id + '_' + material.wireframe; if ( geometryProgram !== _currentGeometryProgram ) { _currentGeometryProgram = geometryProgram; updateBuffers = true; } // morph targets var morphTargetInfluences = object.morphTargetInfluences; if ( morphTargetInfluences !== undefined ) { var activeInfluences = []; for ( var i = 0, l = morphTargetInfluences.length; i < l; i ++ ) { var influence = morphTargetInfluences[ i ]; activeInfluences.push( [ influence, i ] ); } activeInfluences.sort( absNumericalSort ); if ( activeInfluences.length > 8 ) { activeInfluences.length = 8; } var morphAttributes = geometry.morphAttributes; for ( var i = 0, l = activeInfluences.length; i < l; i ++ ) { var influence = activeInfluences[ i ]; morphInfluences[ i ] = influence[ 0 ]; if ( influence[ 0 ] !== 0 ) { var index = influence[ 1 ]; if ( material.morphTargets === true && morphAttributes.position ) geometry.addAttribute( 'morphTarget' + i, morphAttributes.position[ index ] ); if ( material.morphNormals === true && morphAttributes.normal ) geometry.addAttribute( 'morphNormal' + i, morphAttributes.normal[ index ] ); } else { if ( material.morphTargets === true ) geometry.removeAttribute( 'morphTarget' + i ); if ( material.morphNormals === true ) geometry.removeAttribute( 'morphNormal' + i ); } } var uniforms = program.getUniforms(); if ( uniforms.morphTargetInfluences !== null ) { _gl.uniform1fv( uniforms.morphTargetInfluences, morphInfluences ); } updateBuffers = true; } // var index = geometry.index; var position = geometry.attributes.position; if ( material.wireframe === true ) { index = objects.getWireframeAttribute( geometry ); } var renderer; if ( index !== null ) { renderer = indexedBufferRenderer; renderer.setIndex( index ); } else { renderer = bufferRenderer; } if ( updateBuffers ) { setupVertexAttributes( material, program, geometry ); if ( index !== null ) { _gl.bindBuffer( _gl.ELEMENT_ARRAY_BUFFER, objects.getAttributeBuffer( index ) ); } } // var dataStart = 0; var dataCount = Infinity; if ( index !== null ) { dataCount = index.count; } else if ( position !== undefined ) { dataCount = position.count; } var rangeStart = geometry.drawRange.start; var rangeCount = geometry.drawRange.count; var groupStart = group !== null ? group.start : 0; var groupCount = group !== null ? group.count : Infinity; var drawStart = Math.max( dataStart, rangeStart, groupStart ); var drawEnd = Math.min( dataStart + dataCount, rangeStart + rangeCount, groupStart + groupCount ) - 1; var drawCount = Math.max( 0, drawEnd - drawStart + 1 ); // if ( object instanceof THREE.Mesh ) { if ( material.wireframe === true ) { state.setLineWidth( material.wireframeLinewidth * getTargetPixelRatio() ); renderer.setMode( _gl.LINES ); } else { switch ( object.drawMode ) { case THREE.TrianglesDrawMode: renderer.setMode( _gl.TRIANGLES ); break; case THREE.TriangleStripDrawMode: renderer.setMode( _gl.TRIANGLE_STRIP ); break; case THREE.TriangleFanDrawMode: renderer.setMode( _gl.TRIANGLE_FAN ); break; } } } else if ( object instanceof THREE.Line ) { var lineWidth = material.linewidth; if ( lineWidth === undefined ) lineWidth = 1; // Not using Line*Material state.setLineWidth( lineWidth * getTargetPixelRatio() ); if ( object instanceof THREE.LineSegments ) { renderer.setMode( _gl.LINES ); } else { renderer.setMode( _gl.LINE_STRIP ); } } else if ( object instanceof THREE.Points ) { renderer.setMode( _gl.POINTS ); } if ( geometry instanceof THREE.InstancedBufferGeometry && geometry.maxInstancedCount > 0 ) { renderer.renderInstances( geometry, drawStart, drawCount ); } else { renderer.render( drawStart, drawCount ); } }; function setupVertexAttributes( material, program, geometry, startIndex ) { var extension; if ( geometry instanceof THREE.InstancedBufferGeometry ) { extension = extensions.get( 'ANGLE_instanced_arrays' ); if ( extension === null ) { console.error( 'THREE.WebGLRenderer.setupVertexAttributes: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' ); return; } } if ( startIndex === undefined ) startIndex = 0; state.initAttributes(); var geometryAttributes = geometry.attributes; var programAttributes = program.getAttributes(); var materialDefaultAttributeValues = material.defaultAttributeValues; for ( var name in programAttributes ) { var programAttribute = programAttributes[ name ]; if ( programAttribute >= 0 ) { var geometryAttribute = geometryAttributes[ name ]; if ( geometryAttribute !== undefined ) { var size = geometryAttribute.itemSize; var buffer = objects.getAttributeBuffer( geometryAttribute ); if ( geometryAttribute instanceof THREE.InterleavedBufferAttribute ) { var data = geometryAttribute.data; var stride = data.stride; var offset = geometryAttribute.offset; if ( data instanceof THREE.InstancedInterleavedBuffer ) { state.enableAttributeAndDivisor( programAttribute, data.meshPerAttribute, extension ); if ( geometry.maxInstancedCount === undefined ) { geometry.maxInstancedCount = data.meshPerAttribute * data.count; } } else { state.enableAttribute( programAttribute ); } _gl.bindBuffer( _gl.ARRAY_BUFFER, buffer ); _gl.vertexAttribPointer( programAttribute, size, _gl.FLOAT, false, stride * data.array.BYTES_PER_ELEMENT, ( startIndex * stride + offset ) * data.array.BYTES_PER_ELEMENT ); } else { if ( geometryAttribute instanceof THREE.InstancedBufferAttribute ) { state.enableAttributeAndDivisor( programAttribute, geometryAttribute.meshPerAttribute, extension ); if ( geometry.maxInstancedCount === undefined ) { geometry.maxInstancedCount = geometryAttribute.meshPerAttribute * geometryAttribute.count; } } else { state.enableAttribute( programAttribute ); } _gl.bindBuffer( _gl.ARRAY_BUFFER, buffer ); _gl.vertexAttribPointer( programAttribute, size, _gl.FLOAT, false, 0, startIndex * size * 4 ); // 4 bytes per Float32 } } else if ( materialDefaultAttributeValues !== undefined ) { var value = materialDefaultAttributeValues[ name ]; if ( value !== undefined ) { switch ( value.length ) { case 2: _gl.vertexAttrib2fv( programAttribute, value ); break; case 3: _gl.vertexAttrib3fv( programAttribute, value ); break; case 4: _gl.vertexAttrib4fv( programAttribute, value ); break; default: _gl.vertexAttrib1fv( programAttribute, value ); } } } } } state.disableUnusedAttributes(); } // Sorting function absNumericalSort( a, b ) { return Math.abs( b[ 0 ] ) - Math.abs( a[ 0 ] ); } function painterSortStable ( a, b ) { if ( a.object.renderOrder !== b.object.renderOrder ) { return a.object.renderOrder - b.object.renderOrder; } else if ( a.material.id !== b.material.id ) { return a.material.id - b.material.id; } else if ( a.z !== b.z ) { return a.z - b.z; } else { return a.id - b.id; } } function reversePainterSortStable ( a, b ) { if ( a.object.renderOrder !== b.object.renderOrder ) { return a.object.renderOrder - b.object.renderOrder; } if ( a.z !== b.z ) { return b.z - a.z; } else { return a.id - b.id; } } // Rendering this.render = function ( scene, camera, renderTarget, forceClear ) { if ( camera instanceof THREE.Camera === false ) { console.error( 'THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.' ); return; } var fog = scene.fog; // reset caching for this frame _currentGeometryProgram = ''; _currentMaterialId = - 1; _currentCamera = null; // update scene graph if ( scene.autoUpdate === true ) scene.updateMatrixWorld(); // update camera matrices and frustum if ( camera.parent === null ) camera.updateMatrixWorld(); camera.matrixWorldInverse.getInverse( camera.matrixWorld ); _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); _frustum.setFromMatrix( _projScreenMatrix ); lights.length = 0; opaqueObjectsLastIndex = - 1; transparentObjectsLastIndex = - 1; sprites.length = 0; lensFlares.length = 0; projectObject( scene, camera ); opaqueObjects.length = opaqueObjectsLastIndex + 1; transparentObjects.length = transparentObjectsLastIndex + 1; if ( _this.sortObjects === true ) { opaqueObjects.sort( painterSortStable ); transparentObjects.sort( reversePainterSortStable ); } setupLights( lights, camera ); // shadowMap.render( scene, camera ); // _infoRender.calls = 0; _infoRender.vertices = 0; _infoRender.faces = 0; _infoRender.points = 0; if ( renderTarget === undefined ) { renderTarget = null; } this.setRenderTarget( renderTarget ); if ( this.autoClear || forceClear ) { this.clear( this.autoClearColor, this.autoClearDepth, this.autoClearStencil ); } // if ( scene.overrideMaterial ) { var overrideMaterial = scene.overrideMaterial; renderObjects( opaqueObjects, camera, fog, overrideMaterial ); renderObjects( transparentObjects, camera, fog, overrideMaterial ); } else { // opaque pass (front-to-back order) state.setBlending( THREE.NoBlending ); renderObjects( opaqueObjects, camera, fog ); // transparent pass (back-to-front order) renderObjects( transparentObjects, camera, fog ); } // custom render plugins (post pass) spritePlugin.render( scene, camera ); lensFlarePlugin.render( scene, camera, _currentViewport ); // Generate mipmap if we're using any kind of mipmap filtering if ( renderTarget ) { var texture = renderTarget.texture; if ( texture.generateMipmaps && isPowerOfTwo( renderTarget ) && texture.minFilter !== THREE.NearestFilter && texture.minFilter !== THREE.LinearFilter ) { updateRenderTargetMipmap( renderTarget ); } } // Ensure depth buffer writing is enabled so it can be cleared on next render state.setDepthTest( true ); state.setDepthWrite( true ); state.setColorWrite( true ); // _gl.finish(); }; function pushRenderItem( object, geometry, material, z, group ) { var array, index; // allocate the next position in the appropriate array if ( material.transparent ) { array = transparentObjects; index = ++ transparentObjectsLastIndex; } else { array = opaqueObjects; index = ++ opaqueObjectsLastIndex; } // recycle existing render item or grow the array var renderItem = array[ index ]; if ( renderItem !== undefined ) { renderItem.id = object.id; renderItem.object = object; renderItem.geometry = geometry; renderItem.material = material; renderItem.z = _vector3.z; renderItem.group = group; } else { renderItem = { id: object.id, object: object, geometry: geometry, material: material, z: _vector3.z, group: group }; // assert( index === array.length ); array.push( renderItem ); } } function projectObject( object, camera ) { if ( object.visible === false ) return; if ( object.layers.test( camera.layers ) ) { if ( object instanceof THREE.Light ) { lights.push( object ); } else if ( object instanceof THREE.Sprite ) { if ( object.frustumCulled === false || _frustum.intersectsObject( object ) === true ) { sprites.push( object ); } } else if ( object instanceof THREE.LensFlare ) { lensFlares.push( object ); } else if ( object instanceof THREE.ImmediateRenderObject ) { if ( _this.sortObjects === true ) { _vector3.setFromMatrixPosition( object.matrixWorld ); _vector3.applyProjection( _projScreenMatrix ); } pushRenderItem( object, null, object.material, _vector3.z, null ); } else if ( object instanceof THREE.Mesh || object instanceof THREE.Line || object instanceof THREE.Points ) { if ( object instanceof THREE.SkinnedMesh ) { object.skeleton.update(); } if ( object.frustumCulled === false || _frustum.intersectsObject( object ) === true ) { var material = object.material; if ( material.visible === true ) { if ( _this.sortObjects === true ) { _vector3.setFromMatrixPosition( object.matrixWorld ); _vector3.applyProjection( _projScreenMatrix ); } var geometry = objects.update( object ); if ( material instanceof THREE.MultiMaterial ) { var groups = geometry.groups; var materials = material.materials; for ( var i = 0, l = groups.length; i < l; i ++ ) { var group = groups[ i ]; var groupMaterial = materials[ group.materialIndex ]; if ( groupMaterial.visible === true ) { pushRenderItem( object, geometry, groupMaterial, _vector3.z, group ); } } } else { pushRenderItem( object, geometry, material, _vector3.z, null ); } } } } } var children = object.children; for ( var i = 0, l = children.length; i < l; i ++ ) { projectObject( children[ i ], camera ); } } function renderObjects( renderList, camera, fog, overrideMaterial ) { for ( var i = 0, l = renderList.length; i < l; i ++ ) { var renderItem = renderList[ i ]; var object = renderItem.object; var geometry = renderItem.geometry; var material = overrideMaterial === undefined ? renderItem.material : overrideMaterial; var group = renderItem.group; object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); object.normalMatrix.getNormalMatrix( object.modelViewMatrix ); if ( object instanceof THREE.ImmediateRenderObject ) { setMaterial( material ); var program = setProgram( camera, fog, material, object ); _currentGeometryProgram = ''; object.render( function ( object ) { _this.renderBufferImmediate( object, program, material ); } ); } else { _this.renderBufferDirect( camera, fog, geometry, material, object, group ); } } } function initMaterial( material, fog, object ) { var materialProperties = properties.get( material ); var parameters = programCache.getParameters( material, _lights, fog, object ); var code = programCache.getProgramCode( material, parameters ); var program = materialProperties.program; var programChange = true; if ( program === undefined ) { // new material material.addEventListener( 'dispose', onMaterialDispose ); } else if ( program.code !== code ) { // changed glsl or parameters releaseMaterialProgramReference( material ); } else if ( parameters.shaderID !== undefined ) { // same glsl and uniform list return; } else { // only rebuild uniform list programChange = false; } if ( programChange ) { if ( parameters.shaderID ) { var shader = THREE.ShaderLib[ parameters.shaderID ]; materialProperties.__webglShader = { name: material.type, uniforms: THREE.UniformsUtils.clone( shader.uniforms ), vertexShader: shader.vertexShader, fragmentShader: shader.fragmentShader }; } else { materialProperties.__webglShader = { name: material.type, uniforms: material.uniforms, vertexShader: material.vertexShader, fragmentShader: material.fragmentShader }; } material.__webglShader = materialProperties.__webglShader; program = programCache.acquireProgram( material, parameters, code ); materialProperties.program = program; material.program = program; } var attributes = program.getAttributes(); if ( material.morphTargets ) { material.numSupportedMorphTargets = 0; for ( var i = 0; i < _this.maxMorphTargets; i ++ ) { if ( attributes[ 'morphTarget' + i ] >= 0 ) { material.numSupportedMorphTargets ++; } } } if ( material.morphNormals ) { material.numSupportedMorphNormals = 0; for ( var i = 0; i < _this.maxMorphNormals; i ++ ) { if ( attributes[ 'morphNormal' + i ] >= 0 ) { material.numSupportedMorphNormals ++; } } } materialProperties.uniformsList = []; var uniforms = materialProperties.__webglShader.uniforms, uniformLocations = materialProperties.program.getUniforms(); for ( var u in uniforms ) { var location = uniformLocations[ u ]; if ( location ) { materialProperties.uniformsList.push( [ materialProperties.__webglShader.uniforms[ u ], location ] ); } } if ( material instanceof THREE.MeshPhongMaterial || material instanceof THREE.MeshLambertMaterial || material instanceof THREE.MeshStandardMaterial || material.lights ) { // store the light setup it was created for materialProperties.lightsHash = _lights.hash; // wire up the material to this renderer's lighting state uniforms.ambientLightColor.value = _lights.ambient; uniforms.directionalLights.value = _lights.directional; uniforms.spotLights.value = _lights.spot; uniforms.pointLights.value = _lights.point; uniforms.hemisphereLights.value = _lights.hemi; uniforms.directionalShadowMap.value = _lights.directionalShadowMap; uniforms.directionalShadowMatrix.value = _lights.directionalShadowMatrix; uniforms.spotShadowMap.value = _lights.spotShadowMap; uniforms.spotShadowMatrix.value = _lights.spotShadowMatrix; uniforms.pointShadowMap.value = _lights.pointShadowMap; uniforms.pointShadowMatrix.value = _lights.pointShadowMatrix; } // detect dynamic uniforms materialProperties.hasDynamicUniforms = false; for ( var j = 0, jl = materialProperties.uniformsList.length; j < jl; j ++ ) { var uniform = materialProperties.uniformsList[ j ][ 0 ]; if ( uniform.dynamic === true ) { materialProperties.hasDynamicUniforms = true; break; } } } function setMaterial( material ) { setMaterialFaces( material ); if ( material.transparent === true ) { state.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha ); } else { state.setBlending( THREE.NoBlending ); } state.setDepthFunc( material.depthFunc ); state.setDepthTest( material.depthTest ); state.setDepthWrite( material.depthWrite ); state.setColorWrite( material.colorWrite ); state.setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits ); } function setMaterialFaces( material ) { material.side !== THREE.DoubleSide ? state.enable( _gl.CULL_FACE ) : state.disable( _gl.CULL_FACE ); state.setFlipSided( material.side === THREE.BackSide ); } function setProgram( camera, fog, material, object ) { _usedTextureUnits = 0; var materialProperties = properties.get( material ); if ( materialProperties.program === undefined ) { material.needsUpdate = true; } if ( materialProperties.lightsHash !== undefined && materialProperties.lightsHash !== _lights.hash ) { material.needsUpdate = true; } if ( material.needsUpdate ) { initMaterial( material, fog, object ); material.needsUpdate = false; } var refreshProgram = false; var refreshMaterial = false; var refreshLights = false; var program = materialProperties.program, p_uniforms = program.getUniforms(), m_uniforms = materialProperties.__webglShader.uniforms; if ( program.id !== _currentProgram ) { _gl.useProgram( program.program ); _currentProgram = program.id; refreshProgram = true; refreshMaterial = true; refreshLights = true; } if ( material.id !== _currentMaterialId ) { _currentMaterialId = material.id; refreshMaterial = true; } if ( refreshProgram || camera !== _currentCamera ) { _gl.uniformMatrix4fv( p_uniforms.projectionMatrix, false, camera.projectionMatrix.elements ); if ( capabilities.logarithmicDepthBuffer ) { _gl.uniform1f( p_uniforms.logDepthBufFC, 2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) ); } if ( camera !== _currentCamera ) { _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 instanceof THREE.ShaderMaterial || material instanceof THREE.MeshPhongMaterial || material instanceof THREE.MeshStandardMaterial || material.envMap ) { if ( p_uniforms.cameraPosition !== undefined ) { _vector3.setFromMatrixPosition( camera.matrixWorld ); _gl.uniform3f( p_uniforms.cameraPosition, _vector3.x, _vector3.y, _vector3.z ); } } if ( material instanceof THREE.MeshPhongMaterial || material instanceof THREE.MeshLambertMaterial || material instanceof THREE.MeshBasicMaterial || material instanceof THREE.MeshStandardMaterial || material instanceof THREE.ShaderMaterial || material.skinning ) { if ( p_uniforms.viewMatrix !== undefined ) { _gl.uniformMatrix4fv( p_uniforms.viewMatrix, false, camera.matrixWorldInverse.elements ); } } } // skinning uniforms must be set even if material didn't change // auto-setting of texture unit for bone texture must go before other textures // not sure why, but otherwise weird things happen if ( material.skinning ) { if ( object.bindMatrix && p_uniforms.bindMatrix !== undefined ) { _gl.uniformMatrix4fv( p_uniforms.bindMatrix, false, object.bindMatrix.elements ); } if ( object.bindMatrixInverse && p_uniforms.bindMatrixInverse !== undefined ) { _gl.uniformMatrix4fv( p_uniforms.bindMatrixInverse, false, object.bindMatrixInverse.elements ); } if ( capabilities.floatVertexTextures && object.skeleton && object.skeleton.useVertexTexture ) { if ( p_uniforms.boneTexture !== undefined ) { var textureUnit = getTextureUnit(); _gl.uniform1i( p_uniforms.boneTexture, textureUnit ); _this.setTexture( object.skeleton.boneTexture, textureUnit ); } if ( p_uniforms.boneTextureWidth !== undefined ) { _gl.uniform1i( p_uniforms.boneTextureWidth, object.skeleton.boneTextureWidth ); } if ( p_uniforms.boneTextureHeight !== undefined ) { _gl.uniform1i( p_uniforms.boneTextureHeight, object.skeleton.boneTextureHeight ); } } else if ( object.skeleton && object.skeleton.boneMatrices ) { if ( p_uniforms.boneGlobalMatrices !== undefined ) { _gl.uniformMatrix4fv( p_uniforms.boneGlobalMatrices, false, object.skeleton.boneMatrices ); } } } if ( refreshMaterial ) { if ( material instanceof THREE.MeshPhongMaterial || material instanceof THREE.MeshLambertMaterial || material instanceof THREE.MeshStandardMaterial || material.lights ) { // 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 ) { refreshUniformsFog( m_uniforms, fog ); } if ( material instanceof THREE.MeshBasicMaterial || material instanceof THREE.MeshLambertMaterial || material instanceof THREE.MeshPhongMaterial || material instanceof THREE.MeshStandardMaterial ) { refreshUniformsCommon( m_uniforms, material ); } // refresh single material specific uniforms if ( material instanceof THREE.LineBasicMaterial ) { refreshUniformsLine( m_uniforms, material ); } else if ( material instanceof THREE.LineDashedMaterial ) { refreshUniformsLine( m_uniforms, material ); refreshUniformsDash( m_uniforms, material ); } else if ( material instanceof THREE.PointsMaterial ) { refreshUniformsPoints( m_uniforms, material ); } else if ( material instanceof THREE.MeshLambertMaterial ) { refreshUniformsLambert( m_uniforms, material ); } else if ( material instanceof THREE.MeshPhongMaterial ) { refreshUniformsPhong( m_uniforms, material ); } else if ( material instanceof THREE.MeshStandardMaterial ) { refreshUniformsStandard( m_uniforms, material ); } else if ( material instanceof THREE.MeshDepthMaterial ) { m_uniforms.mNear.value = camera.near; m_uniforms.mFar.value = camera.far; m_uniforms.opacity.value = material.opacity; } else if ( material instanceof THREE.MeshNormalMaterial ) { m_uniforms.opacity.value = material.opacity; } // load common uniforms loadUniformsGeneric( materialProperties.uniformsList ); } loadUniformsMatrices( p_uniforms, object ); if ( p_uniforms.modelMatrix !== undefined ) { _gl.uniformMatrix4fv( p_uniforms.modelMatrix, false, object.matrixWorld.elements ); } if ( materialProperties.hasDynamicUniforms === true ) { updateDynamicUniforms( materialProperties.uniformsList, object, camera ); } return program; } function updateDynamicUniforms ( uniforms, object, camera ) { var dynamicUniforms = []; for ( var j = 0, jl = uniforms.length; j < jl; j ++ ) { var uniform = uniforms[ j ][ 0 ]; var onUpdateCallback = uniform.onUpdateCallback; if ( onUpdateCallback !== undefined ) { onUpdateCallback.bind( uniform )( object, camera ); dynamicUniforms.push( uniforms[ j ] ); } } loadUniformsGeneric( dynamicUniforms ); } // Uniforms (refresh uniforms objects) function refreshUniformsCommon ( uniforms, material ) { uniforms.opacity.value = material.opacity; uniforms.diffuse.value = material.color; if ( material.emissive ) { uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity ); } uniforms.map.value = material.map; uniforms.mapEncoding.value = (( material.map && material.map.encoding !== THREE.DefaultEncoding ) ? material.map.encoding : THREE.sRGBEncoding ); uniforms.specularMap.value = material.specularMap; uniforms.alphaMap.value = material.alphaMap; if ( material.aoMap ) { uniforms.aoMap.value = material.aoMap; uniforms.aoMapIntensity.value = material.aoMapIntensity; } // uv repeat and offset setting priorities // 1. color map // 2. specular map // 3. normal map // 4. bump map // 5. alpha map // 6. emissive map var uvScaleMap; if ( material.map ) { uvScaleMap = material.map; } else if ( material.specularMap ) { uvScaleMap = material.specularMap; } else if ( material.displacementMap ) { uvScaleMap = material.displacementMap; } else if ( material.normalMap ) { uvScaleMap = material.normalMap; } else if ( material.bumpMap ) { uvScaleMap = material.bumpMap; } else if ( material.roughnessMap ) { uvScaleMap = material.roughnessMap; } else if ( material.metalnessMap ) { uvScaleMap = material.metalnessMap; } else if ( material.alphaMap ) { uvScaleMap = material.alphaMap; } else if ( material.emissiveMap ) { uvScaleMap = material.emissiveMap; } if ( uvScaleMap !== undefined ) { if ( uvScaleMap instanceof THREE.WebGLRenderTarget ) { uvScaleMap = uvScaleMap.texture; } var offset = uvScaleMap.offset; var repeat = uvScaleMap.repeat; uniforms.offsetRepeat.value.set( offset.x, offset.y, repeat.x, repeat.y ); } uniforms.envMap.value = material.envMap; uniforms.envMapEncoding.value = (( material.envMap && material.envMap.encoding !== THREE.DefaultEncoding )? material.envMap.encoding : THREE.LinearEncoding ); uniforms.flipEnvMap.value = ( material.envMap instanceof THREE.WebGLRenderTargetCube ) ? 1 : - 1; uniforms.reflectivity.value = material.reflectivity; uniforms.refractionRatio.value = material.refractionRatio; } function refreshUniformsLine ( uniforms, material ) { uniforms.diffuse.value = material.color; uniforms.opacity.value = material.opacity; } function refreshUniformsDash ( uniforms, material ) { uniforms.dashSize.value = material.dashSize; uniforms.totalSize.value = material.dashSize + material.gapSize; uniforms.scale.value = material.scale; } function refreshUniformsPoints ( uniforms, material ) { uniforms.diffuse.value = material.color; uniforms.opacity.value = material.opacity; uniforms.size.value = material.size * _pixelRatio; uniforms.scale.value = _canvas.clientHeight / 2.0; // TODO: Cache this. uniforms.map.value = material.map; if ( material.map !== null ) { var offset = material.map.offset; var repeat = material.map.repeat; uniforms.offsetRepeat.value.set( offset.x, offset.y, repeat.x, repeat.y ); } } function refreshUniformsFog ( uniforms, fog ) { uniforms.fogColor.value = fog.color; if ( fog instanceof THREE.Fog ) { uniforms.fogNear.value = fog.near; uniforms.fogFar.value = fog.far; } else if ( fog instanceof THREE.FogExp2 ) { uniforms.fogDensity.value = fog.density; } } function refreshUniformsLambert ( uniforms, material ) { if ( material.lightMap ) { uniforms.lightMap.value = material.lightMap; uniforms.lightMapIntensity.value = material.lightMapIntensity; } if ( material.emissiveMap ) { uniforms.emissiveMap.value = material.emissiveMap; uniforms.emissiveMapEncoding.value = ( material.emissiveMap.encoding !== THREE.DefaultEncoding ) ? material.emissiveMap.encoding : THREE.LinearEncoding; } } function refreshUniformsPhong ( uniforms, material ) { uniforms.specular.value = material.specular; uniforms.shininess.value = Math.max( material.shininess, 1e-4 ); // to prevent pow( 0.0, 0.0 ) if ( material.lightMap ) { uniforms.lightMap.value = material.lightMap; uniforms.lightMapIntensity.value = material.lightMapIntensity; } if ( material.emissiveMap ) { uniforms.emissiveMap.value = material.emissiveMap; uniforms.emissiveMapEncoding.value = ( material.emissiveMap.encoding !== THREE.DefaultEncoding ) ? material.emissiveMap.encoding : THREE.LinearEncoding; } if ( material.bumpMap ) { uniforms.bumpMap.value = material.bumpMap; uniforms.bumpScale.value = material.bumpScale; } if ( material.normalMap ) { uniforms.normalMap.value = material.normalMap; uniforms.normalScale.value.copy( material.normalScale ); } if ( material.displacementMap ) { uniforms.displacementMap.value = material.displacementMap; uniforms.displacementScale.value = material.displacementScale; uniforms.displacementBias.value = material.displacementBias; } } function refreshUniformsStandard ( uniforms, material ) { uniforms.roughness.value = material.roughness; uniforms.metalness.value = material.metalness; if ( material.roughnessMap ) { uniforms.roughnessMap.value = material.roughnessMap; } if ( material.metalnessMap ) { uniforms.metalnessMap.value = material.metalnessMap; } if ( material.lightMap ) { uniforms.lightMap.value = material.lightMap; uniforms.lightMapIntensity.value = material.lightMapIntensity; } if ( material.emissiveMap ) { uniforms.emissiveMap.value = material.emissiveMap; uniforms.emissiveMapEncoding.value = ( material.emissiveMap.encoding !== THREE.DefaultEncoding ) ? material.emissiveMap.encoding : THREE.LinearEncoding; } if ( material.bumpMap ) { uniforms.bumpMap.value = material.bumpMap; uniforms.bumpScale.value = material.bumpScale; } if ( material.normalMap ) { uniforms.normalMap.value = material.normalMap; uniforms.normalScale.value.copy( material.normalScale ); } if ( material.displacementMap ) { uniforms.displacementMap.value = material.displacementMap; uniforms.displacementScale.value = material.displacementScale; uniforms.displacementBias.value = material.displacementBias; } if ( material.envMap ) { //uniforms.envMap.value = material.envMap; // part of uniforms common uniforms.envMapIntensity.value = material.envMapIntensity; } } // 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.directionalLights.needsUpdate = value; uniforms.pointLights.needsUpdate = value; uniforms.spotLights.needsUpdate = value; uniforms.hemisphereLights.needsUpdate = value; } // Uniforms (load to GPU) function loadUniformsMatrices ( uniforms, object ) { _gl.uniformMatrix4fv( uniforms.modelViewMatrix, false, object.modelViewMatrix.elements ); if ( uniforms.normalMatrix ) { _gl.uniformMatrix3fv( uniforms.normalMatrix, false, object.normalMatrix.elements ); } } function getTextureUnit() { var textureUnit = _usedTextureUnits; if ( textureUnit >= capabilities.maxTextures ) { console.warn( 'WebGLRenderer: trying to use ' + textureUnit + ' texture units while this GPU supports only ' + capabilities.maxTextures ); } _usedTextureUnits += 1; return textureUnit; } function loadUniformsGeneric ( uniforms ) { var texture, textureUnit; for ( var j = 0, jl = uniforms.length; j < jl; j ++ ) { var uniform = uniforms[ j ][ 0 ]; // needsUpdate property is not added to all uniforms. if ( uniform.needsUpdate === false ) continue; var type = uniform.type; var value = uniform.value; var location = uniforms[ j ][ 1 ]; switch ( type ) { case '1i': _gl.uniform1i( location, value ); break; case '1f': _gl.uniform1f( location, value ); break; case '2f': _gl.uniform2f( location, value[ 0 ], value[ 1 ] ); break; case '3f': _gl.uniform3f( location, value[ 0 ], value[ 1 ], value[ 2 ] ); break; case '4f': _gl.uniform4f( location, value[ 0 ], value[ 1 ], value[ 2 ], value[ 3 ] ); break; case '1iv': _gl.uniform1iv( location, value ); break; case '3iv': _gl.uniform3iv( location, value ); break; case '1fv': _gl.uniform1fv( location, value ); break; case '2fv': _gl.uniform2fv( location, value ); break; case '3fv': _gl.uniform3fv( location, value ); break; case '4fv': _gl.uniform4fv( location, value ); break; case 'Matrix2fv': _gl.uniformMatrix2fv( location, false, value ); break; case 'Matrix3fv': _gl.uniformMatrix3fv( location, false, value ); break; case 'Matrix4fv': _gl.uniformMatrix4fv( location, false, value ); break; // case 'i': // single integer _gl.uniform1i( location, value ); break; case 'f': // single float _gl.uniform1f( location, value ); break; case 'v2': // single THREE.Vector2 _gl.uniform2f( location, value.x, value.y ); break; case 'v3': // single THREE.Vector3 _gl.uniform3f( location, value.x, value.y, value.z ); break; case 'v4': // single THREE.Vector4 _gl.uniform4f( location, value.x, value.y, value.z, value.w ); break; case 'c': // single THREE.Color _gl.uniform3f( location, value.r, value.g, value.b ); break; /* case 's': // TODO: Optimize this. for( var propertyName in uniform.properties ) { var property = uniform.properties[ propertyName ]; var locationProperty = location[ propertyName ]; var valueProperty = value[ propertyName ]; switch( property.type ) { case 'i': _gl.uniform1i( locationProperty, valueProperty ); break; case 'f': _gl.uniform1f( locationProperty, valueProperty ); break; case 'v2': _gl.uniform2f( locationProperty, valueProperty.x, valueProperty.y ); break; case 'v3': _gl.uniform3f( locationProperty, valueProperty.x, valueProperty.y, valueProperty.z ); break; case 'v4': _gl.uniform4f( locationProperty, valueProperty.x, valueProperty.y, valueProperty.z, valueProperty.w ); break; case 'c': _gl.uniform3f( locationProperty, valueProperty.r, valueProperty.g, valueProperty.b ); break; }; } break; */ case 'sa': // TODO: Optimize this. for ( var i = 0; i < value.length; i ++ ) { for ( var propertyName in uniform.properties ) { var property = uniform.properties[ propertyName ]; var locationProperty = location[ i ][ propertyName ]; var valueProperty = value[ i ][ propertyName ]; switch ( property.type ) { case 'i': _gl.uniform1i( locationProperty, valueProperty ); break; case 'f': _gl.uniform1f( locationProperty, valueProperty ); break; case 'v2': _gl.uniform2f( locationProperty, valueProperty.x, valueProperty.y ); break; case 'v3': _gl.uniform3f( locationProperty, valueProperty.x, valueProperty.y, valueProperty.z ); break; case 'v4': _gl.uniform4f( locationProperty, valueProperty.x, valueProperty.y, valueProperty.z, valueProperty.w ); break; case 'c': _gl.uniform3f( locationProperty, valueProperty.r, valueProperty.g, valueProperty.b ); break; case 'm4': _gl.uniformMatrix4fv( locationProperty, false, valueProperty.elements ); break; } } } break; case 'iv1': // flat array of integers (JS or typed array) _gl.uniform1iv( location, value ); break; case 'iv': // flat array of integers with 3 x N size (JS or typed array) _gl.uniform3iv( location, value ); break; case 'fv1': // flat array of floats (JS or typed array) _gl.uniform1fv( location, value ); break; case 'fv': // flat array of floats with 3 x N size (JS or typed array) _gl.uniform3fv( location, value ); break; case 'v2v': // array of THREE.Vector2 if ( uniform._array === undefined ) { uniform._array = new Float32Array( 2 * value.length ); } for ( var i = 0, i2 = 0, il = value.length; i < il; i ++, i2 += 2 ) { uniform._array[ i2 + 0 ] = value[ i ].x; uniform._array[ i2 + 1 ] = value[ i ].y; } _gl.uniform2fv( location, uniform._array ); break; case 'v3v': // array of THREE.Vector3 if ( uniform._array === undefined ) { uniform._array = new Float32Array( 3 * value.length ); } for ( var i = 0, i3 = 0, il = value.length; i < il; i ++, i3 += 3 ) { uniform._array[ i3 + 0 ] = value[ i ].x; uniform._array[ i3 + 1 ] = value[ i ].y; uniform._array[ i3 + 2 ] = value[ i ].z; } _gl.uniform3fv( location, uniform._array ); break; case 'v4v': // array of THREE.Vector4 if ( uniform._array === undefined ) { uniform._array = new Float32Array( 4 * value.length ); } for ( var i = 0, i4 = 0, il = value.length; i < il; i ++, i4 += 4 ) { uniform._array[ i4 + 0 ] = value[ i ].x; uniform._array[ i4 + 1 ] = value[ i ].y; uniform._array[ i4 + 2 ] = value[ i ].z; uniform._array[ i4 + 3 ] = value[ i ].w; } _gl.uniform4fv( location, uniform._array ); break; case 'm2': // single THREE.Matrix2 _gl.uniformMatrix2fv( location, false, value.elements ); break; case 'm3': // single THREE.Matrix3 _gl.uniformMatrix3fv( location, false, value.elements ); break; case 'm3v': // array of THREE.Matrix3 if ( uniform._array === undefined ) { uniform._array = new Float32Array( 9 * value.length ); } for ( var i = 0, il = value.length; i < il; i ++ ) { value[ i ].flattenToArrayOffset( uniform._array, i * 9 ); } _gl.uniformMatrix3fv( location, false, uniform._array ); break; case 'm4': // single THREE.Matrix4 _gl.uniformMatrix4fv( location, false, value.elements ); break; case 'm4v': // array of THREE.Matrix4 if ( uniform._array === undefined ) { uniform._array = new Float32Array( 16 * value.length ); } for ( var i = 0, il = value.length; i < il; i ++ ) { value[ i ].flattenToArrayOffset( uniform._array, i * 16 ); } _gl.uniformMatrix4fv( location, false, uniform._array ); break; case 't': // single THREE.Texture (2d or cube) texture = value; textureUnit = getTextureUnit(); _gl.uniform1i( location, textureUnit ); if ( ! texture ) continue; if ( texture instanceof THREE.CubeTexture || ( Array.isArray( texture.image ) && texture.image.length === 6 ) ) { // CompressedTexture can have Array in image :/ setCubeTexture( texture, textureUnit ); } else if ( texture instanceof THREE.WebGLRenderTargetCube ) { setCubeTextureDynamic( texture.texture, textureUnit ); } else if ( texture instanceof THREE.WebGLRenderTarget ) { _this.setTexture( texture.texture, textureUnit ); } else { _this.setTexture( texture, textureUnit ); } break; case 'tv': // array of THREE.Texture (2d or cube) if ( uniform._array === undefined ) { uniform._array = []; } for ( var i = 0, il = uniform.value.length; i < il; i ++ ) { uniform._array[ i ] = getTextureUnit(); } _gl.uniform1iv( location, uniform._array ); for ( var i = 0, il = uniform.value.length; i < il; i ++ ) { texture = uniform.value[ i ]; textureUnit = uniform._array[ i ]; if ( ! texture ) continue; if ( texture instanceof THREE.CubeTexture || ( texture.image instanceof Array && texture.image.length === 6 ) ) { // CompressedTexture can have Array in image :/ setCubeTexture( texture, textureUnit ); } else if ( texture instanceof THREE.WebGLRenderTarget ) { _this.setTexture( texture.texture, textureUnit ); } else if ( texture instanceof THREE.WebGLRenderTargetCube ) { setCubeTextureDynamic( texture.texture, textureUnit ); } else { _this.setTexture( texture, textureUnit ); } } break; default: console.warn( 'THREE.WebGLRenderer: Unknown uniform type: ' + type ); } } } function setupLights ( lights, camera ) { var l, ll, light, r = 0, g = 0, b = 0, color, intensity, distance, viewMatrix = camera.matrixWorldInverse, directionalLength = 0, pointLength = 0, spotLength = 0, hemiLength = 0, shadowsLength = 0; _lights.shadowsPointLight = 0; for ( l = 0, ll = lights.length; l < ll; l ++ ) { light = lights[ l ]; color = light.color; intensity = light.intensity; distance = light.distance; if ( light instanceof THREE.AmbientLight ) { r += color.r * intensity; g += color.g * intensity; b += color.b * intensity; } else if ( light instanceof THREE.DirectionalLight ) { var uniforms = lightCache.get( light ); uniforms.color.copy( light.color ).multiplyScalar( light.intensity ); uniforms.direction.setFromMatrixPosition( light.matrixWorld ); _vector3.setFromMatrixPosition( light.target.matrixWorld ); uniforms.direction.sub( _vector3 ); uniforms.direction.transformDirection( viewMatrix ); uniforms.shadow = light.castShadow; if ( light.castShadow ) { uniforms.shadowBias = light.shadow.bias; uniforms.shadowRadius = light.shadow.radius; uniforms.shadowMapSize = light.shadow.mapSize; _lights.shadows[ shadowsLength ++ ] = light; } _lights.directionalShadowMap[ directionalLength ] = light.shadow.map; _lights.directionalShadowMatrix[ directionalLength ] = light.shadow.matrix; _lights.directional[ directionalLength ++ ] = uniforms; } else if ( light instanceof THREE.SpotLight ) { var uniforms = lightCache.get( light ); uniforms.position.setFromMatrixPosition( light.matrixWorld ); uniforms.position.applyMatrix4( viewMatrix ); uniforms.color.copy( color ).multiplyScalar( intensity ); uniforms.distance = distance; uniforms.direction.setFromMatrixPosition( light.matrixWorld ); _vector3.setFromMatrixPosition( light.target.matrixWorld ); uniforms.direction.sub( _vector3 ); uniforms.direction.transformDirection( viewMatrix ); uniforms.angleCos = Math.cos( light.angle ); uniforms.penumbra = Math.cos( light.angle ) * light.penumbra; uniforms.decay = ( light.distance === 0 ) ? 0.0 : light.decay; uniforms.shadow = light.castShadow; if ( light.castShadow ) { uniforms.shadowBias = light.shadow.bias; uniforms.shadowRadius = light.shadow.radius; uniforms.shadowMapSize = light.shadow.mapSize; _lights.shadows[ shadowsLength ++ ] = light; } _lights.spotShadowMap[ spotLength ] = light.shadow.map; _lights.spotShadowMatrix[ spotLength ] = light.shadow.matrix; _lights.spot[ spotLength ++ ] = uniforms; } else if ( light instanceof THREE.PointLight ) { var uniforms = lightCache.get( light ); uniforms.position.setFromMatrixPosition( light.matrixWorld ); uniforms.position.applyMatrix4( viewMatrix ); uniforms.color.copy( light.color ).multiplyScalar( light.intensity ); uniforms.distance = light.distance; uniforms.decay = ( light.distance === 0 ) ? 0.0 : light.decay; uniforms.shadow = light.castShadow; if ( light.castShadow ) { uniforms.shadowBias = light.shadow.bias; uniforms.shadowRadius = light.shadow.radius; uniforms.shadowMapSize = light.shadow.mapSize; _lights.shadows[ shadowsLength ++ ] = light; } _lights.pointShadowMap[ pointLength ] = light.shadow.map; if ( _lights.pointShadowMatrix[ pointLength ] === undefined ) { _lights.pointShadowMatrix[ pointLength ] = new THREE.Matrix4(); } // for point lights we set the shadow matrix to be a translation-only matrix // equal to inverse of the light's position _vector3.setFromMatrixPosition( light.matrixWorld ).negate(); _lights.pointShadowMatrix[ pointLength ].identity().setPosition( _vector3 ); _lights.point[ pointLength ++ ] = uniforms; } else if ( light instanceof THREE.HemisphereLight ) { var uniforms = lightCache.get( light ); uniforms.direction.setFromMatrixPosition( light.matrixWorld ); uniforms.direction.transformDirection( viewMatrix ); uniforms.direction.normalize(); uniforms.skyColor.copy( light.color ).multiplyScalar( intensity ); uniforms.groundColor.copy( light.groundColor ).multiplyScalar( intensity ); _lights.hemi[ hemiLength ++ ] = uniforms; } } _lights.ambient[ 0 ] = r; _lights.ambient[ 1 ] = g; _lights.ambient[ 2 ] = b; _lights.directional.length = directionalLength; _lights.spot.length = spotLength; _lights.point.length = pointLength; _lights.hemi.length = hemiLength; _lights.shadows.length = shadowsLength; _lights.hash = directionalLength + ',' + pointLength + ',' + spotLength + ',' + hemiLength + ',' + shadowsLength; } // GL state setting this.setFaceCulling = function ( cullFace, frontFaceDirection ) { if ( cullFace === THREE.CullFaceNone ) { state.disable( _gl.CULL_FACE ); } else { if ( frontFaceDirection === THREE.FrontFaceDirectionCW ) { _gl.frontFace( _gl.CW ); } else { _gl.frontFace( _gl.CCW ); } if ( cullFace === THREE.CullFaceBack ) { _gl.cullFace( _gl.BACK ); } else if ( cullFace === THREE.CullFaceFront ) { _gl.cullFace( _gl.FRONT ); } else { _gl.cullFace( _gl.FRONT_AND_BACK ); } state.enable( _gl.CULL_FACE ); } }; // Textures function setTextureParameters ( textureType, texture, isPowerOfTwoImage ) { var extension; if ( isPowerOfTwoImage ) { _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, paramThreeToGL( texture.wrapS ) ); _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, paramThreeToGL( texture.wrapT ) ); _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, paramThreeToGL( texture.magFilter ) ); _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, paramThreeToGL( texture.minFilter ) ); } else { _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_S, _gl.CLAMP_TO_EDGE ); _gl.texParameteri( textureType, _gl.TEXTURE_WRAP_T, _gl.CLAMP_TO_EDGE ); if ( texture.wrapS !== THREE.ClampToEdgeWrapping || texture.wrapT !== THREE.ClampToEdgeWrapping ) { console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.wrapS and Texture.wrapT should be set to THREE.ClampToEdgeWrapping.', texture ); } _gl.texParameteri( textureType, _gl.TEXTURE_MAG_FILTER, filterFallback( texture.magFilter ) ); _gl.texParameteri( textureType, _gl.TEXTURE_MIN_FILTER, filterFallback( texture.minFilter ) ); if ( texture.minFilter !== THREE.NearestFilter && texture.minFilter !== THREE.LinearFilter ) { console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.', texture ); } } extension = extensions.get( 'EXT_texture_filter_anisotropic' ); if ( extension ) { if ( texture.type === THREE.FloatType && extensions.get( 'OES_texture_float_linear' ) === null ) return; if ( texture.type === THREE.HalfFloatType && extensions.get( 'OES_texture_half_float_linear' ) === null ) return; if ( texture.anisotropy > 1 || properties.get( texture ).__currentAnisotropy ) { _gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, _this.getMaxAnisotropy() ) ); properties.get( texture ).__currentAnisotropy = texture.anisotropy; } } } function uploadTexture( textureProperties, texture, slot ) { if ( textureProperties.__webglInit === undefined ) { textureProperties.__webglInit = true; texture.addEventListener( 'dispose', onTextureDispose ); textureProperties.__webglTexture = _gl.createTexture(); _infoMemory.textures ++; } state.activeTexture( _gl.TEXTURE0 + slot ); state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture ); _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); _gl.pixelStorei( _gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, texture.premultiplyAlpha ); _gl.pixelStorei( _gl.UNPACK_ALIGNMENT, texture.unpackAlignment ); var image = clampToMaxSize( texture.image, capabilities.maxTextureSize ); if ( textureNeedsPowerOfTwo( texture ) && isPowerOfTwo( image ) === false ) { image = makePowerOfTwo( image ); } var isPowerOfTwoImage = isPowerOfTwo( image ), glFormat = paramThreeToGL( texture.format ), glType = paramThreeToGL( texture.type ); setTextureParameters( _gl.TEXTURE_2D, texture, isPowerOfTwoImage ); var mipmap, mipmaps = texture.mipmaps; if ( texture instanceof THREE.DataTexture ) { // use manually created mipmaps if available // if there are no manual mipmaps // set 0 level mipmap and then use GL to generate other mipmap levels if ( mipmaps.length > 0 && isPowerOfTwoImage ) { for ( var i = 0, il = mipmaps.length; i < il; i ++ ) { mipmap = mipmaps[ i ]; state.texImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); } texture.generateMipmaps = false; } else { state.texImage2D( _gl.TEXTURE_2D, 0, glFormat, image.width, image.height, 0, glFormat, glType, image.data ); } } else if ( texture instanceof THREE.CompressedTexture ) { for ( var i = 0, il = mipmaps.length; i < il; i ++ ) { mipmap = mipmaps[ i ]; if ( texture.format !== THREE.RGBAFormat && texture.format !== THREE.RGBFormat ) { if ( state.getCompressedTextureFormats().indexOf( glFormat ) > - 1 ) { state.compressedTexImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, mipmap.data ); } else { console.warn( "THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()" ); } } else { state.texImage2D( _gl.TEXTURE_2D, i, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); } } } else { // regular Texture (image, video, canvas) // use manually created mipmaps if available // if there are no manual mipmaps // set 0 level mipmap and then use GL to generate other mipmap levels if ( mipmaps.length > 0 && isPowerOfTwoImage ) { for ( var i = 0, il = mipmaps.length; i < il; i ++ ) { mipmap = mipmaps[ i ]; state.texImage2D( _gl.TEXTURE_2D, i, glFormat, glFormat, glType, mipmap ); } texture.generateMipmaps = false; } else { state.texImage2D( _gl.TEXTURE_2D, 0, glFormat, glFormat, glType, image ); } } if ( texture.generateMipmaps && isPowerOfTwoImage ) _gl.generateMipmap( _gl.TEXTURE_2D ); textureProperties.__version = texture.version; if ( texture.onUpdate ) texture.onUpdate( texture ); } this.setTexture = function ( texture, slot ) { var textureProperties = properties.get( texture ); if ( texture.version > 0 && textureProperties.__version !== texture.version ) { var image = texture.image; if ( image === undefined ) { console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is undefined', texture ); return; } if ( image.complete === false ) { console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is incomplete', texture ); return; } uploadTexture( textureProperties, texture, slot ); return; } state.activeTexture( _gl.TEXTURE0 + slot ); state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture ); }; function clampToMaxSize ( image, maxSize ) { if ( image.width > maxSize || image.height > maxSize ) { // Warning: Scaling through the canvas will only work with images that use // premultiplied alpha. var scale = maxSize / Math.max( image.width, image.height ); var canvas = document.createElement( 'canvas' ); canvas.width = Math.floor( image.width * scale ); canvas.height = Math.floor( image.height * scale ); var context = canvas.getContext( '2d' ); context.drawImage( image, 0, 0, image.width, image.height, 0, 0, canvas.width, canvas.height ); console.warn( 'THREE.WebGLRenderer: image is too big (' + image.width + 'x' + image.height + '). Resized to ' + canvas.width + 'x' + canvas.height, image ); return canvas; } return image; } function isPowerOfTwo( image ) { return THREE.Math.isPowerOfTwo( image.width ) && THREE.Math.isPowerOfTwo( image.height ); } function textureNeedsPowerOfTwo( texture ) { if ( texture.wrapS !== THREE.ClampToEdgeWrapping || texture.wrapT !== THREE.ClampToEdgeWrapping ) return true; if ( texture.minFilter !== THREE.NearestFilter && texture.minFilter !== THREE.LinearFilter ) return true; return false; } function makePowerOfTwo( image ) { if ( image instanceof HTMLImageElement || image instanceof HTMLCanvasElement ) { var canvas = document.createElement( 'canvas' ); canvas.width = THREE.Math.nearestPowerOfTwo( image.width ); canvas.height = THREE.Math.nearestPowerOfTwo( image.height ); var context = canvas.getContext( '2d' ); context.drawImage( image, 0, 0, canvas.width, canvas.height ); console.warn( 'THREE.WebGLRenderer: image is not power of two (' + image.width + 'x' + image.height + '). Resized to ' + canvas.width + 'x' + canvas.height, image ); return canvas; } return image; } function setCubeTexture ( texture, slot ) { var textureProperties = properties.get( texture ); if ( texture.image.length === 6 ) { if ( texture.version > 0 && textureProperties.__version !== texture.version ) { if ( ! textureProperties.__image__webglTextureCube ) { texture.addEventListener( 'dispose', onTextureDispose ); textureProperties.__image__webglTextureCube = _gl.createTexture(); _infoMemory.textures ++; } state.activeTexture( _gl.TEXTURE0 + slot ); state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__image__webglTextureCube ); _gl.pixelStorei( _gl.UNPACK_FLIP_Y_WEBGL, texture.flipY ); var isCompressed = texture instanceof THREE.CompressedTexture; var isDataTexture = texture.image[ 0 ] instanceof THREE.DataTexture; var cubeImage = []; for ( var i = 0; i < 6; i ++ ) { if ( _this.autoScaleCubemaps && ! isCompressed && ! isDataTexture ) { cubeImage[ i ] = clampToMaxSize( texture.image[ i ], capabilities.maxCubemapSize ); } else { cubeImage[ i ] = isDataTexture ? texture.image[ i ].image : texture.image[ i ]; } } var image = cubeImage[ 0 ], isPowerOfTwoImage = isPowerOfTwo( image ), glFormat = paramThreeToGL( texture.format ), glType = paramThreeToGL( texture.type ); setTextureParameters( _gl.TEXTURE_CUBE_MAP, texture, isPowerOfTwoImage ); for ( var i = 0; i < 6; i ++ ) { if ( ! isCompressed ) { if ( isDataTexture ) { state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, cubeImage[ i ].width, cubeImage[ i ].height, 0, glFormat, glType, cubeImage[ i ].data ); } else { state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, glFormat, glFormat, glType, cubeImage[ i ] ); } } else { var mipmap, mipmaps = cubeImage[ i ].mipmaps; for ( var j = 0, jl = mipmaps.length; j < jl; j ++ ) { mipmap = mipmaps[ j ]; if ( texture.format !== THREE.RGBAFormat && texture.format !== THREE.RGBFormat ) { if ( state.getCompressedTextureFormats().indexOf( glFormat ) > - 1 ) { state.compressedTexImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glFormat, mipmap.width, mipmap.height, 0, mipmap.data ); } else { console.warn( "THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .setCubeTexture()" ); } } else { state.texImage2D( _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, j, glFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data ); } } } } if ( texture.generateMipmaps && isPowerOfTwoImage ) { _gl.generateMipmap( _gl.TEXTURE_CUBE_MAP ); } textureProperties.__version = texture.version; if ( texture.onUpdate ) texture.onUpdate( texture ); } else { state.activeTexture( _gl.TEXTURE0 + slot ); state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__image__webglTextureCube ); } } } function setCubeTextureDynamic ( texture, slot ) { state.activeTexture( _gl.TEXTURE0 + slot ); state.bindTexture( _gl.TEXTURE_CUBE_MAP, properties.get( texture ).__webglTexture ); } // Render targets // Setup storage for target texture and bind it to correct framebuffer function setupFrameBufferTexture ( framebuffer, renderTarget, attachment, textureTarget ) { var glFormat = paramThreeToGL( renderTarget.texture.format ); var glType = paramThreeToGL( renderTarget.texture.type ); state.texImage2D( textureTarget, 0, glFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null ); _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); _gl.framebufferTexture2D( _gl.FRAMEBUFFER, attachment, textureTarget, properties.get( renderTarget.texture ).__webglTexture, 0 ); _gl.bindFramebuffer( _gl.FRAMEBUFFER, null ); } // Setup storage for internal depth/stencil buffers and bind to correct framebuffer function setupRenderBufferStorage ( renderbuffer, renderTarget ) { _gl.bindRenderbuffer( _gl.RENDERBUFFER, renderbuffer ); if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) { _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_COMPONENT16, renderTarget.width, renderTarget.height ); _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) { _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.DEPTH_STENCIL, renderTarget.width, renderTarget.height ); _gl.framebufferRenderbuffer( _gl.FRAMEBUFFER, _gl.DEPTH_STENCIL_ATTACHMENT, _gl.RENDERBUFFER, renderbuffer ); } else { // FIXME: We don't support !depth !stencil _gl.renderbufferStorage( _gl.RENDERBUFFER, _gl.RGBA4, renderTarget.width, renderTarget.height ); } _gl.bindRenderbuffer( _gl.RENDERBUFFER, null ); } // Setup GL resources for a non-texture depth buffer function setupDepthRenderbuffer( renderTarget ) { var renderTargetProperties = properties.get( renderTarget ); var isCube = ( renderTarget instanceof THREE.WebGLRenderTargetCube ); if ( isCube ) { renderTargetProperties.__webglDepthbuffer = []; for ( var i = 0; i < 6; i ++ ) { _gl.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer[ i ] ); renderTargetProperties.__webglDepthbuffer[ i ] = _gl.createRenderbuffer(); setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer[ i ], renderTarget ); } } else { _gl.bindFramebuffer( _gl.FRAMEBUFFER, renderTargetProperties.__webglFramebuffer ); renderTargetProperties.__webglDepthbuffer = _gl.createRenderbuffer(); setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer, renderTarget ); } _gl.bindFramebuffer( _gl.FRAMEBUFFER, null ); } // Set up GL resources for the render target function setupRenderTarget( renderTarget ) { var renderTargetProperties = properties.get( renderTarget ); var textureProperties = properties.get( renderTarget.texture ); renderTarget.addEventListener( 'dispose', onRenderTargetDispose ); textureProperties.__webglTexture = _gl.createTexture(); _infoMemory.textures ++; var isCube = ( renderTarget instanceof THREE.WebGLRenderTargetCube ); var isTargetPowerOfTwo = THREE.Math.isPowerOfTwo( renderTarget.width ) && THREE.Math.isPowerOfTwo( renderTarget.height ); // Setup framebuffer if ( isCube ) { renderTargetProperties.__webglFramebuffer = []; for ( var i = 0; i < 6; i ++ ) { renderTargetProperties.__webglFramebuffer[ i ] = _gl.createFramebuffer(); } } else { renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer(); } // Setup color buffer if ( isCube ) { state.bindTexture( _gl.TEXTURE_CUBE_MAP, textureProperties.__webglTexture ); setTextureParameters( _gl.TEXTURE_CUBE_MAP, renderTarget.texture, isTargetPowerOfTwo ); for ( var i = 0; i < 6; i ++ ) { setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + i ); } if ( renderTarget.texture.generateMipmaps && isTargetPowerOfTwo ) _gl.generateMipmap( _gl.TEXTURE_CUBE_MAP ); state.bindTexture( _gl.TEXTURE_CUBE_MAP, null ); } else { state.bindTexture( _gl.TEXTURE_2D, textureProperties.__webglTexture ); setTextureParameters( _gl.TEXTURE_2D, renderTarget.texture, isTargetPowerOfTwo ); setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_2D ); if ( renderTarget.texture.generateMipmaps && isTargetPowerOfTwo ) _gl.generateMipmap( _gl.TEXTURE_2D ); state.bindTexture( _gl.TEXTURE_2D, null ); } // Setup depth and stencil buffers if ( renderTarget.depthBuffer ) { setupDepthRenderbuffer( renderTarget ); } } this.setRenderTarget = function ( renderTarget ) { _currentRenderTarget = renderTarget; if ( renderTarget && properties.get( renderTarget ).__webglFramebuffer === undefined ) { setupRenderTarget( renderTarget ); } var isCube = ( renderTarget instanceof THREE.WebGLRenderTargetCube ); var framebuffer; if ( renderTarget ) { var renderTargetProperties = properties.get( renderTarget ); if ( isCube ) { framebuffer = renderTargetProperties.__webglFramebuffer[ renderTarget.activeCubeFace ]; } else { framebuffer = renderTargetProperties.__webglFramebuffer; } _currentScissor.copy( renderTarget.scissor ); _currentScissorTest = renderTarget.scissorTest; _currentViewport.copy( renderTarget.viewport ); } else { framebuffer = null; _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ); _currentScissorTest = _scissorTest; _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ); } if ( _currentFramebuffer !== framebuffer ) { _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); _currentFramebuffer = framebuffer; } state.scissor( _currentScissor ); state.setScissorTest( _currentScissorTest ); state.viewport( _currentViewport ); if ( isCube ) { var textureProperties = properties.get( renderTarget.texture ); _gl.framebufferTexture2D( _gl.FRAMEBUFFER, _gl.COLOR_ATTACHMENT0, _gl.TEXTURE_CUBE_MAP_POSITIVE_X + renderTarget.activeCubeFace, textureProperties.__webglTexture, 0 ); } }; this.readRenderTargetPixels = function ( renderTarget, x, y, width, height, buffer ) { if ( renderTarget instanceof THREE.WebGLRenderTarget === false ) { console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' ); return; } var framebuffer = properties.get( renderTarget ).__webglFramebuffer; if ( framebuffer ) { var restore = false; if ( framebuffer !== _currentFramebuffer ) { _gl.bindFramebuffer( _gl.FRAMEBUFFER, framebuffer ); restore = true; } try { var texture = renderTarget.texture; if ( texture.format !== THREE.RGBAFormat && paramThreeToGL( texture.format ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_FORMAT ) ) { console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.' ); return; } if ( texture.type !== THREE.UnsignedByteType && paramThreeToGL( texture.type ) !== _gl.getParameter( _gl.IMPLEMENTATION_COLOR_READ_TYPE ) && ! ( texture.type === THREE.FloatType && extensions.get( 'WEBGL_color_buffer_float' ) ) && ! ( texture.type === THREE.HalfFloatType && extensions.get( 'EXT_color_buffer_half_float' ) ) ) { console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.' ); return; } if ( _gl.checkFramebufferStatus( _gl.FRAMEBUFFER ) === _gl.FRAMEBUFFER_COMPLETE ) { _gl.readPixels( x, y, width, height, paramThreeToGL( texture.format ), paramThreeToGL( texture.type ), buffer ); } else { console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: readPixels from renderTarget failed. Framebuffer not complete.' ); } } finally { if ( restore ) { _gl.bindFramebuffer( _gl.FRAMEBUFFER, _currentFramebuffer ); } } } }; function updateRenderTargetMipmap( renderTarget ) { var target = renderTarget instanceof THREE.WebGLRenderTargetCube ? _gl.TEXTURE_CUBE_MAP : _gl.TEXTURE_2D; var texture = properties.get( renderTarget.texture ).__webglTexture; state.bindTexture( target, texture ); _gl.generateMipmap( target ); state.bindTexture( target, null ); } // Fallback filters for non-power-of-2 textures function filterFallback ( f ) { if ( f === THREE.NearestFilter || f === THREE.NearestMipMapNearestFilter || f === THREE.NearestMipMapLinearFilter ) { return _gl.NEAREST; } return _gl.LINEAR; } // Map three.js constants to WebGL constants function paramThreeToGL ( p ) { var extension; if ( p === THREE.RepeatWrapping ) return _gl.REPEAT; if ( p === THREE.ClampToEdgeWrapping ) return _gl.CLAMP_TO_EDGE; if ( p === THREE.MirroredRepeatWrapping ) return _gl.MIRRORED_REPEAT; if ( p === THREE.NearestFilter ) return _gl.NEAREST; if ( p === THREE.NearestMipMapNearestFilter ) return _gl.NEAREST_MIPMAP_NEAREST; if ( p === THREE.NearestMipMapLinearFilter ) return _gl.NEAREST_MIPMAP_LINEAR; if ( p === THREE.LinearFilter ) return _gl.LINEAR; if ( p === THREE.LinearMipMapNearestFilter ) return _gl.LINEAR_MIPMAP_NEAREST; if ( p === THREE.LinearMipMapLinearFilter ) return _gl.LINEAR_MIPMAP_LINEAR; if ( p === THREE.UnsignedByteType ) return _gl.UNSIGNED_BYTE; if ( p === THREE.UnsignedShort4444Type ) return _gl.UNSIGNED_SHORT_4_4_4_4; if ( p === THREE.UnsignedShort5551Type ) return _gl.UNSIGNED_SHORT_5_5_5_1; if ( p === THREE.UnsignedShort565Type ) return _gl.UNSIGNED_SHORT_5_6_5; if ( p === THREE.ByteType ) return _gl.BYTE; if ( p === THREE.ShortType ) return _gl.SHORT; if ( p === THREE.UnsignedShortType ) return _gl.UNSIGNED_SHORT; if ( p === THREE.IntType ) return _gl.INT; if ( p === THREE.UnsignedIntType ) return _gl.UNSIGNED_INT; if ( p === THREE.FloatType ) return _gl.FLOAT; extension = extensions.get( 'OES_texture_half_float' ); if ( extension !== null ) { if ( p === THREE.HalfFloatType ) return extension.HALF_FLOAT_OES; } if ( p === THREE.AlphaFormat ) return _gl.ALPHA; if ( p === THREE.RGBFormat ) return _gl.RGB; if ( p === THREE.RGBAFormat ) return _gl.RGBA; if ( p === THREE.LuminanceFormat ) return _gl.LUMINANCE; if ( p === THREE.LuminanceAlphaFormat ) return _gl.LUMINANCE_ALPHA; if ( p === THREE.AddEquation ) return _gl.FUNC_ADD; if ( p === THREE.SubtractEquation ) return _gl.FUNC_SUBTRACT; if ( p === THREE.ReverseSubtractEquation ) return _gl.FUNC_REVERSE_SUBTRACT; if ( p === THREE.ZeroFactor ) return _gl.ZERO; if ( p === THREE.OneFactor ) return _gl.ONE; if ( p === THREE.SrcColorFactor ) return _gl.SRC_COLOR; if ( p === THREE.OneMinusSrcColorFactor ) return _gl.ONE_MINUS_SRC_COLOR; if ( p === THREE.SrcAlphaFactor ) return _gl.SRC_ALPHA; if ( p === THREE.OneMinusSrcAlphaFactor ) return _gl.ONE_MINUS_SRC_ALPHA; if ( p === THREE.DstAlphaFactor ) return _gl.DST_ALPHA; if ( p === THREE.OneMinusDstAlphaFactor ) return _gl.ONE_MINUS_DST_ALPHA; if ( p === THREE.DstColorFactor ) return _gl.DST_COLOR; if ( p === THREE.OneMinusDstColorFactor ) return _gl.ONE_MINUS_DST_COLOR; if ( p === THREE.SrcAlphaSaturateFactor ) return _gl.SRC_ALPHA_SATURATE; extension = extensions.get( 'WEBGL_compressed_texture_s3tc' ); if ( extension !== null ) { if ( p === THREE.RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_RGB_S3TC_DXT1_EXT; if ( p === THREE.RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT; if ( p === THREE.RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT; if ( p === THREE.RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT; } extension = extensions.get( 'WEBGL_compressed_texture_pvrtc' ); if ( extension !== null ) { if ( p === THREE.RGB_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_4BPPV1_IMG; if ( p === THREE.RGB_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_2BPPV1_IMG; if ( p === THREE.RGBA_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG; if ( p === THREE.RGBA_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG; } extension = extensions.get( 'WEBGL_compressed_texture_etc1' ); if ( extension !== null ) { if ( p === THREE.RGB_ETC1_Format ) return extension.COMPRESSED_RGB_ETC1_WEBGL; } extension = extensions.get( 'EXT_blend_minmax' ); if ( extension !== null ) { if ( p === THREE.MinEquation ) return extension.MIN_EXT; if ( p === THREE.MaxEquation ) return extension.MAX_EXT; } return 0; } };