/** * @author supereggbert / http://www.paulbrunt.co.uk/ * @author mrdoob / http://mrdoob.com/ * @author alteredq / http://alteredqualia.com/ * @author szimek / https://github.com/szimek/ * @author gero3 / https://github.com/gero3/ */ THREE.WebGLRenderer = function ( parameters ) { console.log( 'THREE.WebGLRenderer', THREE.REVISION ); parameters = parameters || {}; var info = { memory: { programs: 0, geometries: 0, textures: 0 }, render: { calls: 0, vertices: 0, faces: 0, points: 0 } }; var renderer = new THREE.WebGLRenderer.LowLevelRenderer(parameters); var meshRenderer = new THREE.WebGLRenderer.MeshRenderer(renderer, info); var particleRenderer = new THREE.WebGLRenderer.ParticleRenderer(renderer, info); var lineRenderer = new THREE.WebGLRenderer.LineRenderer(renderer, info); var ribbonRenderer = new THREE.WebGLRenderer.RibbonRenderer(renderer, info); var shaderBuilder = new THREE.WebGLRenderer.ShaderBuilder(renderer, info); // clearing this.autoClear = true; this.autoClearColor = true; this.autoClearDepth = true; this.autoClearStencil = true; // scene graph this.sortObjects = true; this.autoUpdateObjects = true; // physically based shading this.gammaInput = false; this.gammaOutput = false; this.physicallyBasedShading = false; // shadow map this.shadowMapEnabled = false; this.shadowMapAutoUpdate = true; this.shadowMapType = THREE.PCFShadowMap; this.shadowMapCullFace = THREE.CullFaceFront; this.shadowMapDebug = false; this.shadowMapCascade = false; // morphs this.maxMorphTargets = 8; this.maxMorphNormals = 4; // flags this.autoScaleCubemaps = true; // custom render plugins this.renderPluginsPre = []; this.renderPluginsPost = []; // info this.info = info; // internal properties var _this = this, // internal state cache _currentProgram = null, _currentFramebuffer = null, _currentMaterialId = -1, _currentGeometryGroupHash = null, _currentCamera = null, _geometryGroupCounter = 0, _usedTextureUnits = 0, // GL state _viewportX = 0, _viewportY = 0, _viewportWidth = 0, _viewportHeight = 0, _currentWidth = 0, _currentHeight = 0, _enabledAttributes = {}, // frustum _frustum = new THREE.Frustum(), // camera matrices cache _projScreenMatrix = new THREE.Matrix4(), _projScreenMatrixPS = new THREE.Matrix4(), _vector3 = new THREE.Vector3(), // light arrays cache _direction = new THREE.Vector3(), _lightsNeedUpdate = true, _lights = { ambient: [ 0, 0, 0 ], directional: { length: 0, colors: [], positions: [] }, point: { length: 0, colors: [], positions: [], distances: [] }, spot: { length: 0, colors: [], positions: [], distances: [], directions: [], anglesCos: [], exponents: [] }, hemi: { length: 0, skyColors: [], groundColors: [], positions: [] } }; // initialize this.context = renderer.getContext(); this.domElement = renderer.getDomElement(); this.getPrecision = renderer.getPrecision; // low level API this.getPrecision = renderer.getPrecision; this.getContext = renderer.getContext; this.supportsVertexTextures = renderer.supportsVertexTextures; this.supportsFloatTextures = renderer.supportsFloatTextures; this.supportsStandardDerivatives = renderer.supportsStandardDerivatives; this.supportsCompressedTextureS3TC = renderer.supportsCompressedTextureS3TC; this.getMaxAnisotropy = renderer.getMaxAnisotropy; this.setSize = renderer.setSize; this.setViewport = renderer.setViewport; this.setScissor = renderer.setScissor; this.enableScissorTest = renderer.enableScissorTest; this.setDepthWrite = renderer.setDepthWrite; this.setDepthTest = renderer.setDepthTest; this.setRenderTarget = renderer.setRenderTarget; this.setBlending = renderer.setBlending; this.setTexture = renderer.setTexture; this.setMaterialFaces = renderer.setMaterialFaces; this.setFaceCulling = renderer.setFaceCulling; // Clearing this.setClearColorHex = renderer.setClearColorHex; this.setClearColor = renderer.setClearColor; this.getClearColor = renderer.getClearColor; this.getClearAlpha = renderer.getClearAlpha; this.clear = renderer.clear; this.clearTarget = renderer.clearTarget; // Plugins this.addPostPlugin = function ( plugin ) { plugin.init( this ); this.renderPluginsPost.push( plugin ); }; this.addPrePlugin = function ( plugin ) { plugin.init( this ); this.renderPluginsPre.push( plugin ); }; // Rendering this.updateShadowMap = function ( scene, camera ) { _currentProgram = null; _currentGeometryGroupHash = -1; _currentMaterialId = -1; _lightsNeedUpdate = true; renderer.resetState(); this.shadowMapPlugin.update( scene, camera ); }; // Events var onGeometryDispose = function ( event ) { var geometry = event.target; geometry.removeEventListener( 'dispose', onGeometryDispose ); deallocateGeometry( geometry ); _this.info.memory.geometries --; }; var onTextureDispose = function ( event ) { var texture = event.target; texture.removeEventListener( 'dispose', onTextureDispose ); deallocateTexture( texture ); _this.info.memory.textures --; }; var onRenderTargetDispose = function ( event ) { var renderTarget = event.target; renderTarget.removeEventListener( 'dispose', onRenderTargetDispose ); deallocateRenderTarget( renderTarget ); _this.info.memory.textures --; }; var onMaterialDispose = function ( event ) { var material = event.target; material.removeEventListener( 'dispose', onMaterialDispose ); deallocateMaterial( material ); }; // Buffer deallocation var deallocateGeometry = function ( geometry ) { var m,ml; geometry.__webglInit = undefined; if ( geometry.__webglVertexBuffer !== undefined ) renderer.deleteBuffer( geometry.__webglVertexBuffer ); if ( geometry.__webglNormalBuffer !== undefined ) renderer.deleteBuffer( geometry.__webglNormalBuffer ); if ( geometry.__webglTangentBuffer !== undefined ) renderer.deleteBuffer( geometry.__webglTangentBuffer ); if ( geometry.__webglColorBuffer !== undefined ) renderer.deleteBuffer( geometry.__webglColorBuffer ); if ( geometry.__webglUVBuffer !== undefined ) renderer.deleteBuffer( geometry.__webglUVBuffer ); if ( geometry.__webglUV2Buffer !== undefined ) renderer.deleteBuffer( geometry.__webglUV2Buffer ); if ( geometry.__webglSkinIndicesBuffer !== undefined ) renderer.deleteBuffer( geometry.__webglSkinIndicesBuffer ); if ( geometry.__webglSkinWeightsBuffer !== undefined ) renderer.deleteBuffer( geometry.__webglSkinWeightsBuffer ); if ( geometry.__webglFaceBuffer !== undefined ) renderer.deleteBuffer( geometry.__webglFaceBuffer ); if ( geometry.__webglLineBuffer !== undefined ) renderer.deleteBuffer( geometry.__webglLineBuffer ); if ( geometry.__webglLineDistanceBuffer !== undefined ) renderer.deleteBuffer( geometry.__webglLineDistanceBuffer ); // geometry groups if ( geometry.geometryGroups !== undefined ) { for ( var g in geometry.geometryGroups ) { var geometryGroup = geometry.geometryGroups[ g ]; if ( geometryGroup.numMorphTargets !== undefined ) { for ( m = 0, ml = geometryGroup.numMorphTargets; m < ml; m ++ ) { renderer.deleteBuffer( geometryGroup.__webglMorphTargetsBuffers[ m ] ); } } if ( geometryGroup.numMorphNormals !== undefined ) { for ( m = 0, ml = geometryGroup.numMorphNormals; m < ml; m ++ ) { renderer.deleteBuffer( geometryGroup.__webglMorphNormalsBuffers[ m ] ); } } deleteCustomAttributesBuffers( geometryGroup ); } } deleteCustomAttributesBuffers( geometry ); }; var deallocateTexture = function ( texture ) { if ( texture.image && texture.image.__webglTextureCube ) { // cube texture renderer.deleteTexture( texture.image.__webglTextureCube ); } else { // 2D texture if ( ! texture.__webglInit ) return; texture.__webglInit = false; renderer.deleteTexture( texture.__webglTexture ); } }; var deallocateRenderTarget = function ( renderTarget ) { if ( !renderTarget || ! renderTarget.__webglTexture ) return; renderer.deleteTexture( renderTarget.__webglTexture ); if ( renderTarget instanceof THREE.WebGLRenderTargetCube ) { for ( var i = 0; i < 6; i ++ ) { renderer.deleteFramebuffer( renderTarget.__webglFramebuffer[ i ] ); renderer.deleteRenderbuffer( renderTarget.__webglRenderbuffer[ i ] ); } } else { renderer.deleteFramebuffer( renderTarget.__webglFramebuffer ); renderer.deleteRenderbuffer( renderTarget.__webglRenderbuffer ); } }; var deallocateMaterial = function ( material ) { var program = material.program; if ( program === undefined ) return; material.program = undefined; // only deallocate GL program if this was the last use of shared program // assumed there is only single copy of any program in the _programs list // (that's how it's constructed) shaderBuilder.removeProgram(program) }; function deleteCustomAttributesBuffers( geometry ) { if ( geometry.__webglCustomAttributesList ) { for ( var id in geometry.__webglCustomAttributesList ) { renderer.deleteBuffer( geometry.__webglCustomAttributesList[ id ].buffer ); } } }; // Buffer initialization function initCustomAttributes ( geometry, object ) { var nvertices = geometry.vertices.length; var material = object.material; if ( material.attributes ) { if ( geometry.__webglCustomAttributesList === undefined ) { geometry.__webglCustomAttributesList = []; } for ( var a in material.attributes ) { var attribute = material.attributes[ a ]; if ( !attribute.__webglInitialized || attribute.createUniqueBuffers ) { attribute.__webglInitialized = true; var size = 1; // "f" and "i" if ( attribute.type === "v2" ) size = 2; else if ( attribute.type === "v3" ) size = 3; else if ( attribute.type === "v4" ) size = 4; else if ( attribute.type === "c" ) size = 3; attribute.size = size; attribute.array = new Float32Array( nvertices * size ); attribute.buffer = renderer.createBuffer(); attribute.buffer.belongsToAttribute = a; attribute.needsUpdate = true; } geometry.__webglCustomAttributesList.push( attribute ); } } }; function getBufferMaterial( object, geometryGroup ) { return object.material instanceof THREE.MeshFaceMaterial ? object.material.materials[ geometryGroup.materialIndex ] : object.material; }; // function initDirectBuffers( geometry ) { var a, attribute; for ( a in geometry.attributes ) { attribute = geometry.attributes[ a ]; if ( attribute.numItems === undefined ) { attribute.numItems = attribute.array.length; } attribute.buffer = renderer.createBuffer(); if ( a === "index" ) { renderer.setStaticIndexBuffer(attribute.buffer,attribute.array); } else { renderer.setStaticArrayBuffer(attribute.buffer,attribute.array); } } }; // Buffer setting function setDirectBuffers ( geometry, dispose ) { var attributes = geometry.attributes; var attributeName, attributeItem; for ( attributeName in attributes ) { attributeItem = attributes[ attributeName ]; if ( attributeItem.needsUpdate ) { if ( attributeName === 'index' ) { renderer.setDynamicIndexBuffer( attributeItem.buffer, attributeItem.array ); } else { renderer.setDynamicArrayBuffer( attributeItem.buffer, attributeItem.array ); } attributeItem.needsUpdate = false; } if ( dispose && ! attributeItem.dynamic ) { delete attributeItem.array; } } }; // Buffer rendering this.renderBufferImmediate = function ( object, program, material ) { if ( object.hasPositions && ! object.__webglVertexBuffer ) object.__webglVertexBuffer = renderer.createBuffer(); if ( object.hasNormals && ! object.__webglNormalBuffer ) object.__webglNormalBuffer = renderer.createBuffer(); if ( object.hasUvs && ! object.__webglUvBuffer ) object.__webglUvBuffer = renderer.createBuffer(); if ( object.hasColors && ! object.__webglColorBuffer ) object.__webglColorBuffer = renderer.createBuffer(); if ( object.hasPositions ) { renderer.setDynamicArrayBuffer( object.__webglVertexBuffer, object.positionArray); renderer.setFloatAttribute(program.attributes.position, object.__webglVertexBuffer, 3, 0); } if ( object.hasNormals ) { if ( material.shading === THREE.FlatShading ) { var nx, ny, nz, nax, nbx, ncx, nay, nby, ncy, naz, nbz, ncz, normalArray, i, il = object.count * 3; for( i = 0; i < il; i += 9 ) { normalArray = object.normalArray; nax = normalArray[ i ]; nay = normalArray[ i + 1 ]; naz = normalArray[ i + 2 ]; nbx = normalArray[ i + 3 ]; nby = normalArray[ i + 4 ]; nbz = normalArray[ i + 5 ]; ncx = normalArray[ i + 6 ]; ncy = normalArray[ i + 7 ]; ncz = normalArray[ i + 8 ]; nx = ( nax + nbx + ncx ) / 3; ny = ( nay + nby + ncy ) / 3; nz = ( naz + nbz + ncz ) / 3; normalArray[ i ] = nx; normalArray[ i + 1 ] = ny; normalArray[ i + 2 ] = nz; normalArray[ i + 3 ] = nx; normalArray[ i + 4 ] = ny; normalArray[ i + 5 ] = nz; normalArray[ i + 6 ] = nx; normalArray[ i + 7 ] = ny; normalArray[ i + 8 ] = nz; } } renderer.setDynamicArrayBuffer( object.__webglNormalBuffer, object.normalArray); renderer.setFloatAttribute(program.attributes.normal, object.__webglNormalBuffer, 3, 0); } if ( object.hasUvs && material.map ) { renderer.setDynamicArrayBuffer( object.__webglUvBuffer, object.uvArray); renderer.setFloatAttribute(program.attributes.uv, object.__webglUvBuffer, 2, 0); } if ( object.hasColors && material.vertexColors !== THREE.NoColors ) { renderer.setDynamicArrayBuffer( object.__webglColorBuffer, object.colorArray); renderer.setFloatAttribute(program.attributes.color, object.__webglColorBuffer, 3, 0); } renderer.drawTriangles(object.count ); object.count = 0; }; this.renderBufferDirect = function ( camera, lights, fog, material, geometry, object ) { if ( material.visible === false ) return; var program, programAttributes, linewidth, primitives, a, attribute, geometryAttributes; var attributeItem, attributeName, attributePointer, attributeSize; program = setProgram( camera, lights, fog, material, object ); programAttributes = program.attributes; geometryAttributes = geometry.attributes; var updateBuffers = false, wireframeBit = material.wireframe ? 1 : 0, geometryHash = ( geometry.id * 0xffffff ) + ( program.id * 2 ) + wireframeBit; if ( geometryHash !== _currentGeometryGroupHash ) { _currentGeometryGroupHash = geometryHash; updateBuffers = true; } if ( updateBuffers ) { renderer.disableAttributes(); } // render mesh if ( object instanceof THREE.Mesh ) { var index = geometryAttributes[ "index" ]; // indexed triangles if ( index ) { var offsets = geometry.offsets; // if there is more than 1 chunk // must set attribute pointers to use new offsets for each chunk // even if geometry and materials didn't change if ( offsets.length > 1 ) updateBuffers = true; for ( var i = 0, il = offsets.length; i < il; i ++ ) { var startIndex = offsets[ i ].index; if ( updateBuffers ) { for ( attributeName in geometryAttributes ) { if ( attributeName === 'index' ) continue; attributePointer = programAttributes[ attributeName ]; attributeItem = geometryAttributes[ attributeName ]; attributeSize = attributeItem.itemSize; if ( attributePointer >= 0 ) { renderer.setFloatAttribute( attributePointer , attributeItem.buffer, attributeSize, startIndex * attributeSize * 4 ); } } } // render indexed triangles renderer.drawTriangleElements(index.buffer, offsets[ i ].count, offsets[ i ].start * 2); _this.info.render.calls ++; _this.info.render.vertices += offsets[ i ].count; // not really true, here vertices can be shared _this.info.render.faces += offsets[ i ].count / 3; } // non-indexed triangles } else { if ( updateBuffers ) { for ( attributeName in geometryAttributes ) { attributePointer = programAttributes[ attributeName ]; attributeItem = geometryAttributes[ attributeName ]; attributeSize = attributeItem.itemSize; if ( attributePointer >= 0 ) { renderer.setFloatAttribute( attributePointer , attributeItem.buffer, attributeSize, 0 ); } } } var position = geometry.attributes[ "position" ]; // render non-indexed triangles renderer.drawTriangles( position.numItems / 3) _this.info.render.calls ++; _this.info.render.vertices += position.numItems / 3; _this.info.render.faces += position.numItems / 3 / 3; } // render particles } else if ( object instanceof THREE.ParticleSystem ) { if ( updateBuffers ) { for ( attributeName in geometryAttributes ) { attributePointer = programAttributes[ attributeName ]; attributeItem = geometryAttributes[ attributeName ]; attributeSize = attributeItem.itemSize; if ( attributePointer >= 0 ) { renderer.setFloatAttribute( attributePointer , attributeItem.buffer, attributeSize, 0 ); } } var position = geometryAttributes[ "position" ]; // render particles renderer.drawPoints(position.numItems / 3); _this.info.render.calls ++; _this.info.render.points += position.numItems / 3; } } else if ( object instanceof THREE.Line ) { if ( updateBuffers ) { for ( attributeName in geometryAttributes ) { attributePointer = programAttributes[ attributeName ]; attributeItem = geometryAttributes[ attributeName ]; attributeSize = attributeItem.itemSize; if ( attributePointer >= 0 ) { renderer.setFloatAttribute( attributePointer , attributeItem.buffer, attributeSize, 0 ); } } var position = geometryAttributes[ "position" ]; // render lines renderer.setLineWidth( material.linewidth ); renderer.drawLineStrip(position.numItems / 3); _this.info.render.calls ++; _this.info.render.points += position.numItems; } } }; this.renderBuffer = function ( camera, lights, fog, material, geometryGroup, object ) { if ( material.visible === false ) return; var program, attributes, linewidth, primitives, a, attribute, i, il; program = setProgram( camera, lights, fog, material, object ); attributes = program.attributes; var updateBuffers = false, wireframeBit = material.wireframe ? 1 : 0, geometryGroupHash = ( geometryGroup.id * 0xffffff ) + ( program.id * 2 ) + wireframeBit; if ( geometryGroupHash !== _currentGeometryGroupHash ) { _currentGeometryGroupHash = geometryGroupHash; updateBuffers = true; } if ( updateBuffers ) { renderer.disableAttributes(); } // vertices if ( !material.morphTargets && attributes.position >= 0 ) { if ( updateBuffers ) { renderer.setFloatAttribute(attributes.position , geometryGroup.__webglVertexBuffer, 3, 0); } } else { if ( object.morphTargetBase ) { setupMorphTargets( material, geometryGroup, object ); } } if ( updateBuffers ) { // custom attributes // Use the per-geometryGroup custom attribute arrays which are setup in initMeshBuffers if ( geometryGroup.__webglCustomAttributesList ) { for ( i = 0, il = geometryGroup.__webglCustomAttributesList.length; i < il; i ++ ) { attribute = geometryGroup.__webglCustomAttributesList[ i ]; if ( attributes[ attribute.buffer.belongsToAttribute ] >= 0 ) { renderer.setFloatAttribute(attributes[ attribute.buffer.belongsToAttribute ] , attribute.buffer, attribute.size, 0); } } } // colors if ( attributes.color >= 0 ) { renderer.setFloatAttribute(attributes.color , geometryGroup.__webglColorBuffer,3, 0); } // normals if ( attributes.normal >= 0 ) { renderer.setFloatAttribute(attributes.normal, geometryGroup.__webglNormalBuffer, 3, 0); } // tangents if ( attributes.tangent >= 0 ) { renderer.setFloatAttribute(attributes.tangent, geometryGroup.__webglTangentBuffer, 4, 0); } // uvs if ( attributes.uv >= 0 ) { renderer.setFloatAttribute(attributes.uv, geometryGroup.__webglUVBuffer, 2, 0); } if ( attributes.uv2 >= 0 ) { renderer.setFloatAttribute(attributes.uv2, geometryGroup.__webglUV2Buffer, 2, 0); } if ( material.skinning && attributes.skinIndex >= 0 && attributes.skinWeight >= 0 ) { renderer.setFloatAttribute(attributes.skinIndex, geometryGroup.__webglSkinIndicesBuffer, 4, 0); renderer.setFloatAttribute(attributes.skinWeight, geometryGroup.__webglSkinWeightsBuffer, 4, 0); } // line distances if ( attributes.lineDistance >= 0 ) { renderer.setFloatAttribute(attributes.lineDistance, geometryGroup.__webglLineDistanceBuffer, 1, 0); } } // render mesh if ( object instanceof THREE.Mesh ) { // wireframe if ( material.wireframe ) { renderer.setLineWidth( material.wireframeLinewidth ); renderer.drawLineElements(geometryGroup.__webglLineBuffer,geometryGroup.__webglLineCount,0); // triangles } else { renderer.drawTriangleElements( geometryGroup.__webglFaceBuffer, geometryGroup.__webglFaceCount, 0); } _this.info.render.calls ++; _this.info.render.vertices += geometryGroup.__webglFaceCount; _this.info.render.faces += geometryGroup.__webglFaceCount / 3; // render lines } else if ( object instanceof THREE.Line ) { renderer.setLineWidth( material.linewidth ); if (object.type === THREE.LineStrip) { renderer.drawLineStrip(geometryGroup.__webglLineCount); } else { renderer.drawLines(geometryGroup.__webglLineCount); } _this.info.render.calls ++; // render particles } else if ( object instanceof THREE.ParticleSystem ) { renderer.drawPoints(geometryGroup.__webglParticleCount); _this.info.render.calls ++; _this.info.render.points += geometryGroup.__webglParticleCount; // render ribbon } else if ( object instanceof THREE.Ribbon ) { renderer.drawTriangleStrip(geometryGroup.__webglVertexCount); _this.info.render.calls ++; } }; function setupMorphTargets ( material, geometryGroup, object ) { // set base var attributes = material.program.attributes; if ( object.morphTargetBase !== -1 && attributes.position >= 0 ) { renderer.setFloatAttribute(attributes.position, geometryGroup.__webglMorphTargetsBuffers[ object.morphTargetBase ], 3, 0); } else if ( attributes.position >= 0 ) { renderer.setFloatAttribute(attributes.position, geometryGroup.__webglVertexBuffer, 3, 0); } if ( object.morphTargetForcedOrder.length ) { // set forced order var m = 0; var order = object.morphTargetForcedOrder; var influences = object.morphTargetInfluences; while ( m < material.numSupportedMorphTargets && m < order.length ) { if ( attributes[ "morphTarget" + m ] >= 0 ) { renderer.setFloatAttribute(attributes[ "morphTarget" + m ], geometryGroup.__webglMorphTargetsBuffers[ order[ m ] ], 3, 0); } if ( attributes[ "morphNormal" + m ] >= 0 && material.morphNormals ) { renderer.setFloatAttribute(attributes[ "morphNormal" + m ], geometryGroup.__webglMorphNormalsBuffers[ order[ m ] ], 3, 0); } object.__webglMorphTargetInfluences[ m ] = influences[ order[ m ] ]; m ++; } } else { // find the most influencing var influence, activeInfluenceIndices = []; var influences = object.morphTargetInfluences; var i, il = influences.length; for ( i = 0; i < il; i ++ ) { influence = influences[ i ]; if ( influence > 0 ) { activeInfluenceIndices.push( [ influence, i ] ); } } if ( activeInfluenceIndices.length > material.numSupportedMorphTargets ) { activeInfluenceIndices.sort( numericalSort ); activeInfluenceIndices.length = material.numSupportedMorphTargets; } else if ( activeInfluenceIndices.length > material.numSupportedMorphNormals ) { activeInfluenceIndices.sort( numericalSort ); } else if ( activeInfluenceIndices.length === 0 ) { activeInfluenceIndices.push( [ 0, 0 ] ); }; var influenceIndex, m = 0; while ( m < material.numSupportedMorphTargets ) { if ( activeInfluenceIndices[ m ] ) { influenceIndex = activeInfluenceIndices[ m ][ 1 ]; if ( attributes[ "morphTarget" + m ] >= 0 ) { renderer.setFloatAttribute(attributes[ "morphTarget" + m ], geometryGroup.__webglMorphTargetsBuffers[ influenceIndex ], 3, 0); } if ( attributes[ "morphNormal" + m ] >= 0 && material.morphNormals ) { renderer.setFloatAttribute(attributes[ "morphNormal" + m ], geometryGroup.__webglMorphNormalsBuffers[ influenceIndex ], 3, 0); } object.__webglMorphTargetInfluences[ m ] = influences[ influenceIndex ]; } else { /* _gl.vertexAttribPointer( attributes[ "morphTarget" + m ], 3, _gl.FLOAT, false, 0, 0 ); if ( material.morphNormals ) { _gl.vertexAttribPointer( attributes[ "morphNormal" + m ], 3, _gl.FLOAT, false, 0, 0 ); } */ object.__webglMorphTargetInfluences[ m ] = 0; } m ++; } } // load updated influences uniform if ( material.program.uniforms.morphTargetInfluences !== null ) { renderer.uniform1fv( material.program.uniforms.morphTargetInfluences, object.__webglMorphTargetInfluences ); } }; // Sorting function painterSortStable ( a, b ) { if ( a.z !== b.z ) { return b.z - a.z; } else { return a.id - b.id; } }; function numericalSort ( a, b ) { return b[ 0 ] - a[ 0 ]; }; // 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 i, il, webglObject, object, renderList, lights = scene.__lights, fog = scene.fog; // reset caching for this frame _currentMaterialId = -1; _lightsNeedUpdate = true; // update scene graph if ( scene.autoUpdate === true ) scene.updateMatrixWorld(); // update camera matrices and frustum if ( camera.parent === undefined ) camera.updateMatrixWorld(); camera.matrixWorldInverse.getInverse( camera.matrixWorld ); _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse ); _frustum.setFromMatrix( _projScreenMatrix ); // update WebGL objects if ( this.autoUpdateObjects ) this.initWebGLObjects( scene ); // custom render plugins (pre pass) renderPlugins( this.renderPluginsPre, scene, camera ); // _this.info.render.calls = 0; _this.info.render.vertices = 0; _this.info.render.faces = 0; _this.info.render.points = 0; renderer.setRenderTarget( renderTarget ); if ( this.autoClear || forceClear ) { this.clear( this.autoClearColor, this.autoClearDepth, this.autoClearStencil ); } // set matrices for regular objects (frustum culled) renderList = scene.__webglObjects; for ( i = 0, il = renderList.length; i < il; i ++ ) { webglObject = renderList[ i ]; object = webglObject.object; webglObject.id = i; webglObject.render = false; if ( object.visible ) { if ( ! ( object instanceof THREE.Mesh || object instanceof THREE.ParticleSystem ) || ! ( object.frustumCulled ) || _frustum.intersectsObject( object ) ) { setupMatrices( object, camera ); unrollBufferMaterial( webglObject ); webglObject.render = true; if ( this.sortObjects === true ) { if ( object.renderDepth !== null ) { webglObject.z = object.renderDepth; } else { _vector3.getPositionFromMatrix( object.matrixWorld ); _vector3.applyProjection(_projScreenMatrix); webglObject.z = _vector3.z; } } } } } if ( this.sortObjects ) { renderList.sort( painterSortStable ); } // set matrices for immediate objects renderList = scene.__webglObjectsImmediate; for ( i = 0, il = renderList.length; i < il; i ++ ) { webglObject = renderList[ i ]; object = webglObject.object; if ( object.visible ) { setupMatrices( object, camera ); unrollImmediateBufferMaterial( webglObject ); } } if ( scene.overrideMaterial ) { var material = scene.overrideMaterial; renderer.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst ); renderer.setDepthTest( material.depthTest ); renderer.setDepthWrite( material.depthWrite ); renderer.setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits ); renderObjects( scene.__webglObjects, false, "", camera, lights, fog, true, material ); renderObjectsImmediate( scene.__webglObjectsImmediate, "", camera, lights, fog, false, material ); } else { var material = null; // opaque pass (front-to-back order) renderer.setBlending( THREE.NoBlending ); renderObjects( scene.__webglObjects, true, "opaque", camera, lights, fog, false, material ); renderObjectsImmediate( scene.__webglObjectsImmediate, "opaque", camera, lights, fog, false, material ); // transparent pass (back-to-front order) renderObjects( scene.__webglObjects, false, "transparent", camera, lights, fog, true, material ); renderObjectsImmediate( scene.__webglObjectsImmediate, "transparent", camera, lights, fog, true, material ); } // custom render plugins (post pass) renderPlugins( this.renderPluginsPost, scene, camera ); // Generate mipmap if we're using any kind of mipmap filtering if ( renderTarget && renderTarget.generateMipmaps && renderTarget.minFilter !== THREE.NearestFilter && renderTarget.minFilter !== THREE.LinearFilter ) { renderer.updateRenderTargetMipmap( renderTarget ); } // Ensure depth buffer writing is enabled so it can be cleared on next render renderer.setDepthTest( true ); renderer.setDepthWrite( true ); // _gl.finish(); }; function renderPlugins( plugins, scene, camera ) { if ( ! plugins.length ) return; for ( var i = 0, il = plugins.length; i < il; i ++ ) { // reset state for plugin (to start from clean slate) _currentProgram = null; _currentCamera = null; _currentGeometryGroupHash = -1; _currentMaterialId = -1; _lightsNeedUpdate = true; renderer.resetState(); plugins[ i ].render( scene, camera, renderer.getCurrentWidth(), renderer.getCurrentHeight() ); // reset state after plugin (anything could have changed) _currentProgram = null; _currentCamera = null; _currentGeometryGroupHash = -1; _currentMaterialId = -1; _lightsNeedUpdate = true; renderer.resetState(); } }; function renderObjects ( renderList, reverse, materialType, camera, lights, fog, useBlending, overrideMaterial ) { var webglObject, object, buffer, material, start, end, delta; if ( reverse ) { start = renderList.length - 1; end = -1; delta = -1; } else { start = 0; end = renderList.length; delta = 1; } for ( var i = start; i !== end; i += delta ) { webglObject = renderList[ i ]; if ( webglObject.render ) { object = webglObject.object; buffer = webglObject.buffer; if ( overrideMaterial ) { material = overrideMaterial; } else { material = webglObject[ materialType ]; if ( ! material ) continue; if ( useBlending ) renderer.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst ); renderer.setDepthTest( material.depthTest ); renderer.setDepthWrite( material.depthWrite ); renderer.setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits ); } renderer.setMaterialFaces( material ); if ( buffer instanceof THREE.BufferGeometry ) { _this.renderBufferDirect( camera, lights, fog, material, buffer, object ); } else { _this.renderBuffer( camera, lights, fog, material, buffer, object ); } } } }; function renderObjectsImmediate ( renderList, materialType, camera, lights, fog, useBlending, overrideMaterial ) { var webglObject, object, material, program; for ( var i = 0, il = renderList.length; i < il; i ++ ) { webglObject = renderList[ i ]; object = webglObject.object; if ( object.visible ) { if ( overrideMaterial ) { material = overrideMaterial; } else { material = webglObject[ materialType ]; if ( ! material ) continue; if ( useBlending ) renderer.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst ); renderer.setDepthTest( material.depthTest ); renderer.setDepthWrite( material.depthWrite ); renderer.setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits ); } _this.renderImmediateObject( camera, lights, fog, material, object ); } } }; this.renderImmediateObject = function ( camera, lights, fog, material, object ) { var program = setProgram( camera, lights, fog, material, object ); _currentGeometryGroupHash = -1; renderer.setMaterialFaces( material ); if ( object.immediateRenderCallback ) { object.immediateRenderCallback( program, renderer.getContext(), _frustum ); } else { object.render( function( object ) { _this.renderBufferImmediate( object, program, material ); } ); } }; function unrollImmediateBufferMaterial ( globject ) { var object = globject.object, material = object.material; if ( material.transparent ) { globject.transparent = material; globject.opaque = null; } else { globject.opaque = material; globject.transparent = null; } }; function unrollBufferMaterial ( globject ) { var object = globject.object, buffer = globject.buffer, material, materialIndex, meshMaterial; meshMaterial = object.material; if ( meshMaterial instanceof THREE.MeshFaceMaterial ) { materialIndex = buffer.materialIndex; material = meshMaterial.materials[ materialIndex ]; if ( material.transparent ) { globject.transparent = material; globject.opaque = null; } else { globject.opaque = material; globject.transparent = null; } } else { material = meshMaterial; if ( material ) { if ( material.transparent ) { globject.transparent = material; globject.opaque = null; } else { globject.opaque = material; globject.transparent = null; } } } }; // Geometry splitting function sortFacesByMaterial ( geometry, material ) { var f, fl, face, materialIndex, vertices, groupHash, hash_map = {}; var numMorphTargets = geometry.morphTargets.length; var numMorphNormals = geometry.morphNormals.length; var usesFaceMaterial = material instanceof THREE.MeshFaceMaterial; geometry.geometryGroups = {}; for ( f = 0, fl = geometry.faces.length; f < fl; f ++ ) { face = geometry.faces[ f ]; materialIndex = usesFaceMaterial ? face.materialIndex : 0; if ( hash_map[ materialIndex ] === undefined ) { hash_map[ materialIndex ] = { 'hash': materialIndex, 'counter': 0 }; } groupHash = hash_map[ materialIndex ].hash + '_' + hash_map[ materialIndex ].counter; if ( geometry.geometryGroups[ groupHash ] === undefined ) { geometry.geometryGroups[ groupHash ] = { 'faces3': [], 'faces4': [], 'materialIndex': materialIndex, 'vertices': 0, 'numMorphTargets': numMorphTargets, 'numMorphNormals': numMorphNormals }; } vertices = face instanceof THREE.Face3 ? 3 : 4; if ( geometry.geometryGroups[ groupHash ].vertices + vertices > 65535 ) { hash_map[ materialIndex ].counter += 1; groupHash = hash_map[ materialIndex ].hash + '_' + hash_map[ materialIndex ].counter; if ( geometry.geometryGroups[ groupHash ] === undefined ) { geometry.geometryGroups[ groupHash ] = { 'faces3': [], 'faces4': [], 'materialIndex': materialIndex, 'vertices': 0, 'numMorphTargets': numMorphTargets, 'numMorphNormals': numMorphNormals }; } } if ( face instanceof THREE.Face3 ) { geometry.geometryGroups[ groupHash ].faces3.push( f ); } else { geometry.geometryGroups[ groupHash ].faces4.push( f ); } geometry.geometryGroups[ groupHash ].vertices += vertices; } geometry.geometryGroupsList = []; for ( var g in geometry.geometryGroups ) { geometry.geometryGroups[ g ].id = _geometryGroupCounter ++; geometry.geometryGroupsList.push( geometry.geometryGroups[ g ] ); } }; // Objects refresh this.initWebGLObjects = function ( scene ) { if ( !scene.__webglObjects ) { scene.__webglObjects = []; scene.__webglObjectsImmediate = []; scene.__webglSprites = []; scene.__webglFlares = []; } while ( scene.__objectsAdded.length ) { addObject( scene.__objectsAdded[ 0 ], scene ); scene.__objectsAdded.splice( 0, 1 ); } while ( scene.__objectsRemoved.length ) { removeObject( scene.__objectsRemoved[ 0 ], scene ); scene.__objectsRemoved.splice( 0, 1 ); } // update must be called after objects adding / removal for ( var o = 0, ol = scene.__webglObjects.length; o < ol; o ++ ) { updateObject( scene.__webglObjects[ o ].object ); } }; // Objects adding function addObject( object, scene ) { var g, geometry, material, geometryGroup; if ( ! object.__webglInit ) { object.__webglInit = true; object._modelViewMatrix = new THREE.Matrix4(); object._normalMatrix = new THREE.Matrix3(); if ( object.geometry !== undefined && object.geometry.__webglInit === undefined ) { object.geometry.__webglInit = true; object.geometry.addEventListener( 'dispose', onGeometryDispose ); } if ( object instanceof THREE.Mesh ) { geometry = object.geometry; material = object.material; if ( geometry instanceof THREE.Geometry ) { if ( geometry.geometryGroups === undefined ) { sortFacesByMaterial( geometry, material ); } // create separate VBOs per geometry chunk for ( g in geometry.geometryGroups ) { geometryGroup = geometry.geometryGroups[ g ]; // initialise VBO on the first access if ( ! geometryGroup.__webglVertexBuffer ) { meshRenderer.createBuffers( geometryGroup ); meshRenderer.initBuffers( geometryGroup, object ); geometry.verticesNeedUpdate = true; geometry.morphTargetsNeedUpdate = true; geometry.elementsNeedUpdate = true; geometry.uvsNeedUpdate = true; geometry.normalsNeedUpdate = true; geometry.tangentsNeedUpdate = true; geometry.colorsNeedUpdate = true; } } } else if ( geometry instanceof THREE.BufferGeometry ) { initDirectBuffers( geometry ); } } else if ( object instanceof THREE.Ribbon ) { geometry = object.geometry; if ( ! geometry.__webglVertexBuffer ) { ribbonRenderer.createBuffers( geometry ); ribbonRenderer.initBuffers( geometry, object ); geometry.verticesNeedUpdate = true; geometry.colorsNeedUpdate = true; geometry.normalsNeedUpdate = true; } } else if ( object instanceof THREE.Line ) { geometry = object.geometry; if ( ! geometry.__webglVertexBuffer ) { if ( geometry instanceof THREE.Geometry ) { lineRenderer.createBuffers( geometry ); lineRenderer.initBuffers( geometry, object ); geometry.verticesNeedUpdate = true; geometry.colorsNeedUpdate = true; geometry.lineDistancesNeedUpdate = true; } else if ( geometry instanceof THREE.BufferGeometry ) { initDirectBuffers( geometry ); } } } else if ( object instanceof THREE.ParticleSystem ) { geometry = object.geometry; if ( ! geometry.__webglVertexBuffer ) { if ( geometry instanceof THREE.Geometry ) { particleRenderer.createBuffers( geometry ); particleRenderer.initBuffers( geometry, object ); geometry.verticesNeedUpdate = true; geometry.colorsNeedUpdate = true; } else if ( geometry instanceof THREE.BufferGeometry ) { initDirectBuffers( geometry ); } } } } if ( ! object.__webglActive ) { if ( object instanceof THREE.Mesh ) { geometry = object.geometry; if ( geometry instanceof THREE.BufferGeometry ) { addBuffer( scene.__webglObjects, geometry, object ); } else if ( geometry instanceof THREE.Geometry ) { for ( g in geometry.geometryGroups ) { geometryGroup = geometry.geometryGroups[ g ]; addBuffer( scene.__webglObjects, geometryGroup, object ); } } } else if ( object instanceof THREE.Ribbon || object instanceof THREE.Line || object instanceof THREE.ParticleSystem ) { geometry = object.geometry; addBuffer( scene.__webglObjects, geometry, object ); } else if ( object instanceof THREE.ImmediateRenderObject || object.immediateRenderCallback ) { addBufferImmediate( scene.__webglObjectsImmediate, object ); } else if ( object instanceof THREE.Sprite ) { scene.__webglSprites.push( object ); } else if ( object instanceof THREE.LensFlare ) { scene.__webglFlares.push( object ); } object.__webglActive = true; } }; function addBuffer ( objlist, buffer, object ) { objlist.push( { id: null, buffer: buffer, object: object, opaque: null, transparent: null, render: false, z: 0 } ); }; function addBufferImmediate ( objlist, object ) { objlist.push( { object: object, opaque: null, transparent: null } ); }; // Objects updates function updateObject ( object ) { var geometry = object.geometry, geometryGroup, customAttributesDirty, material; if ( object instanceof THREE.Mesh ) { if ( geometry instanceof THREE.BufferGeometry ) { if ( geometry.verticesNeedUpdate || geometry.elementsNeedUpdate || geometry.uvsNeedUpdate || geometry.normalsNeedUpdate || geometry.colorsNeedUpdate || geometry.tangentsNeedUpdate ) { setDirectBuffers( geometry, !geometry.dynamic ); } geometry.verticesNeedUpdate = false; geometry.elementsNeedUpdate = false; geometry.uvsNeedUpdate = false; geometry.normalsNeedUpdate = false; geometry.colorsNeedUpdate = false; geometry.tangentsNeedUpdate = false; } else { // check all geometry groups for( var i = 0, il = geometry.geometryGroupsList.length; i < il; i ++ ) { geometryGroup = geometry.geometryGroupsList[ i ]; material = getBufferMaterial( object, geometryGroup ); if ( geometry.buffersNeedUpdate ) { meshRenderer.initBuffers( geometryGroup, object ); } customAttributesDirty = material.attributes && areCustomAttributesDirty( material ); if ( geometry.verticesNeedUpdate || geometry.morphTargetsNeedUpdate || geometry.elementsNeedUpdate || geometry.uvsNeedUpdate || geometry.normalsNeedUpdate || geometry.colorsNeedUpdate || geometry.tangentsNeedUpdate || customAttributesDirty ) { meshRenderer.setBuffers( geometryGroup, object, !geometry.dynamic, material ); } } geometry.verticesNeedUpdate = false; geometry.morphTargetsNeedUpdate = false; geometry.elementsNeedUpdate = false; geometry.uvsNeedUpdate = false; geometry.normalsNeedUpdate = false; geometry.colorsNeedUpdate = false; geometry.tangentsNeedUpdate = false; geometry.buffersNeedUpdate = false; material.attributes && clearCustomAttributes( material ); } } else if ( object instanceof THREE.Ribbon ) { material = getBufferMaterial( object, geometry ); customAttributesDirty = material.attributes && areCustomAttributesDirty( material ); if ( geometry.verticesNeedUpdate || geometry.colorsNeedUpdate || geometry.normalsNeedUpdate || customAttributesDirty ) { ribbonRenderer.setBuffers( geometry); } geometry.verticesNeedUpdate = false; geometry.colorsNeedUpdate = false; geometry.normalsNeedUpdate = false; material.attributes && clearCustomAttributes( material ); } else if ( object instanceof THREE.Line ) { if ( geometry instanceof THREE.BufferGeometry ) { if ( geometry.verticesNeedUpdate || geometry.colorsNeedUpdate ) { setDirectBuffers( geometry, !geometry.dynamic ); } geometry.verticesNeedUpdate = false; geometry.colorsNeedUpdate = false; } else { material = getBufferMaterial( object, geometry ); customAttributesDirty = material.attributes && areCustomAttributesDirty( material ); if ( geometry.verticesNeedUpdate || geometry.colorsNeedUpdate || geometry.lineDistancesNeedUpdate || customAttributesDirty ) { lineRenderer.setBuffers( geometry); } geometry.verticesNeedUpdate = false; geometry.colorsNeedUpdate = false; geometry.lineDistancesNeedUpdate = false; material.attributes && clearCustomAttributes( material ); } } else if ( object instanceof THREE.ParticleSystem ) { if ( geometry instanceof THREE.BufferGeometry ) { if ( geometry.verticesNeedUpdate || geometry.colorsNeedUpdate ) { setDirectBuffers( geometry, !geometry.dynamic ); } geometry.verticesNeedUpdate = false; geometry.colorsNeedUpdate = false; } else { material = getBufferMaterial( object, geometry ); customAttributesDirty = material.attributes && areCustomAttributesDirty( material ); if ( geometry.verticesNeedUpdate || geometry.colorsNeedUpdate || object.sortParticles || customAttributesDirty ) { particleRenderer.setBuffers( geometry, object, _projScreenMatrix); } geometry.verticesNeedUpdate = false; geometry.colorsNeedUpdate = false; material.attributes && clearCustomAttributes( material ); } } }; // Objects updates - custom attributes check function areCustomAttributesDirty ( material ) { for ( var a in material.attributes ) { if ( material.attributes[ a ].needsUpdate ) return true; } return false; }; function clearCustomAttributes ( material ) { for ( var a in material.attributes ) { material.attributes[ a ].needsUpdate = false; } }; // Objects removal function removeObject ( object, scene ) { if ( object instanceof THREE.Mesh || object instanceof THREE.ParticleSystem || object instanceof THREE.Ribbon || object instanceof THREE.Line ) { removeInstances( scene.__webglObjects, object ); } else if ( object instanceof THREE.Sprite ) { removeInstancesDirect( scene.__webglSprites, object ); } else if ( object instanceof THREE.LensFlare ) { removeInstancesDirect( scene.__webglFlares, object ); } else if ( object instanceof THREE.ImmediateRenderObject || object.immediateRenderCallback ) { removeInstances( scene.__webglObjectsImmediate, object ); } object.__webglActive = false; }; function removeInstances ( objlist, object ) { for ( var o = objlist.length - 1; o >= 0; o -- ) { if ( objlist[ o ].object === object ) { objlist.splice( o, 1 ); } } }; function removeInstancesDirect ( objlist, object ) { for ( var o = objlist.length - 1; o >= 0; o -- ) { if ( objlist[ o ] === object ) { objlist.splice( o, 1 ); } } }; // Materials this.initMaterial = function ( material, lights, fog, object ) { material.addEventListener( 'dispose', onMaterialDispose ); var u, a, identifiers, i, parameters, maxLightCount, maxBones, maxShadows, shaderID; if ( material instanceof THREE.MeshDepthMaterial ) { shaderID = 'depth'; } else if ( material instanceof THREE.MeshNormalMaterial ) { shaderID = 'normal'; } else if ( material instanceof THREE.MeshBasicMaterial ) { shaderID = 'basic'; } else if ( material instanceof THREE.MeshLambertMaterial ) { shaderID = 'lambert'; } else if ( material instanceof THREE.MeshPhongMaterial ) { shaderID = 'phong'; } else if ( material instanceof THREE.LineBasicMaterial ) { shaderID = 'basic'; } else if ( material instanceof THREE.LineDashedMaterial ) { shaderID = 'dashed'; } else if ( material instanceof THREE.ParticleBasicMaterial ) { shaderID = 'particle_basic'; } if ( shaderID ) { setMaterialShaders( material, THREE.ShaderLib[ shaderID ] ); } // heuristics to create shader parameters according to lights in the scene // (not to blow over maxLights budget) maxLightCount = allocateLights( lights ); maxShadows = allocateShadows( lights ); maxBones = allocateBones( object ); parameters = { map: !!material.map, envMap: !!material.envMap, lightMap: !!material.lightMap, bumpMap: !!material.bumpMap, normalMap: !!material.normalMap, specularMap: !!material.specularMap, vertexColors: material.vertexColors, fog: fog, useFog: material.fog, fogExp: fog instanceof THREE.FogExp2, sizeAttenuation: material.sizeAttenuation, skinning: material.skinning, maxBones: maxBones, useVertexTexture: renderer.supportsBoneTextures && object && object.useVertexTexture, boneTextureWidth: object && object.boneTextureWidth, boneTextureHeight: object && object.boneTextureHeight, morphTargets: material.morphTargets, morphNormals: material.morphNormals, maxMorphTargets: this.maxMorphTargets, maxMorphNormals: this.maxMorphNormals, maxDirLights: maxLightCount.directional, maxPointLights: maxLightCount.point, maxSpotLights: maxLightCount.spot, maxHemiLights: maxLightCount.hemi, maxShadows: maxShadows, shadowMapEnabled: this.shadowMapEnabled && object.receiveShadow, shadowMapType: this.shadowMapType, shadowMapDebug: this.shadowMapDebug, shadowMapCascade: this.shadowMapCascade, alphaTest: material.alphaTest, metal: material.metal, perPixel: material.perPixel, wrapAround: material.wrapAround, doubleSided: material.side === THREE.DoubleSide, flipSided: material.side === THREE.BackSide, gammaInput : this.gammaInput, gammaOutput : this.gammaOutput , physicallyBasedShading : this.physicallyBasedShading }; material.program = shaderBuilder.buildProgram( shaderID, material.fragmentShader, material.vertexShader, material.uniforms, material.attributes, material.defines, parameters ); var attributes = material.program.attributes; if ( material.morphTargets ) { material.numSupportedMorphTargets = 0; var id, base = "morphTarget"; for ( i = 0; i < this.maxMorphTargets; i ++ ) { id = base + i; if ( attributes[ id ] >= 0 ) { material.numSupportedMorphTargets ++; } } } if ( material.morphNormals ) { material.numSupportedMorphNormals = 0; var id, base = "morphNormal"; for ( i = 0; i < this.maxMorphNormals; i ++ ) { id = base + i; if ( attributes[ id ] >= 0 ) { material.numSupportedMorphNormals ++; } } } material.uniformsList = []; for ( u in material.uniforms ) { material.uniformsList.push( [ material.uniforms[ u ], u ] ); } }; function setMaterialShaders( material, shaders ) { material.uniforms = THREE.UniformsUtils.clone( shaders.uniforms ); material.vertexShader = shaders.vertexShader; material.fragmentShader = shaders.fragmentShader; }; function setProgram( camera, lights, fog, material, object ) { _usedTextureUnits = 0; if ( material.needsUpdate ) { if ( material.program ) deallocateMaterial( material ); _this.initMaterial( material, lights, fog, object ); material.needsUpdate = false; } if ( material.morphTargets ) { if ( ! object.__webglMorphTargetInfluences ) { object.__webglMorphTargetInfluences = new Float32Array( _this.maxMorphTargets ); } } var refreshMaterial = false; var program = material.program, p_uniforms = program.uniforms, m_uniforms = material.uniforms; if ( program !== _currentProgram ) { renderer.useProgram( program ); _currentProgram = program; refreshMaterial = true; } if ( material.id !== _currentMaterialId ) { _currentMaterialId = material.id; refreshMaterial = true; } if ( refreshMaterial || camera !== _currentCamera ) { renderer.uniformMatrix4fv( p_uniforms.projectionMatrix, camera.projectionMatrix.elements ); if ( camera !== _currentCamera ) _currentCamera = camera; } // 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 ( renderer.supportsBoneTextures && object.useVertexTexture ) { if ( p_uniforms.boneTexture !== null ) { var textureUnit = getTextureUnit(); renderer.uniform1i( p_uniforms.boneTexture, textureUnit ); renderer.setTexture( object.boneTexture, textureUnit ); } } else { if ( p_uniforms.boneGlobalMatrices !== null ) { renderer.uniformMatrix4fv( p_uniforms.boneGlobalMatrices, object.boneMatrices ); } } } if ( refreshMaterial ) { // refresh uniforms common to several materials if ( fog && material.fog ) { refreshUniformsFog( m_uniforms, fog ); } if ( material instanceof THREE.MeshPhongMaterial || material instanceof THREE.MeshLambertMaterial || material.lights ) { if ( _lightsNeedUpdate ) { setupLights( program, lights ); _lightsNeedUpdate = false; } refreshUniformsLights( m_uniforms, _lights ); } if ( material instanceof THREE.MeshBasicMaterial || material instanceof THREE.MeshLambertMaterial || material instanceof THREE.MeshPhongMaterial ) { 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.ParticleBasicMaterial ) { refreshUniformsParticle( m_uniforms, material ); } else if ( material instanceof THREE.MeshPhongMaterial ) { refreshUniformsPhong( m_uniforms, material ); } else if ( material instanceof THREE.MeshLambertMaterial ) { refreshUniformsLambert( 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; } if ( object.receiveShadow && ! material._shadowPass ) { refreshUniformsShadow( m_uniforms, lights ); } // load common uniforms loadUniformsGeneric( program, material.uniformsList ); // load material specific uniforms // (shader material also gets them for the sake of genericity) if ( material instanceof THREE.ShaderMaterial || material instanceof THREE.MeshPhongMaterial || material.envMap ) { if ( p_uniforms.cameraPosition !== null ) { _vector3.getPositionFromMatrix( camera.matrixWorld ); renderer.uniform3f( p_uniforms.cameraPosition, _vector3.x, _vector3.y, _vector3.z ); } } if ( material instanceof THREE.MeshPhongMaterial || material instanceof THREE.MeshLambertMaterial || material instanceof THREE.ShaderMaterial || material.skinning ) { if ( p_uniforms.viewMatrix !== null ) { renderer.uniformMatrix4fv( p_uniforms.viewMatrix, camera.matrixWorldInverse.elements ); } } } loadUniformsMatrices( p_uniforms, object ); if ( p_uniforms.modelMatrix !== null ) { renderer.uniformMatrix4fv( p_uniforms.modelMatrix, object.matrixWorld.elements ); } return program; }; // Uniforms (refresh uniforms objects) function refreshUniformsCommon ( uniforms, material ) { uniforms.opacity.value = material.opacity; if ( _this.gammaInput ) { uniforms.diffuse.value.copyGammaToLinear( material.color ); } else { uniforms.diffuse.value = material.color; } uniforms.map.value = material.map; uniforms.lightMap.value = material.lightMap; uniforms.specularMap.value = material.specularMap; 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 ); } // uv repeat and offset setting priorities // 1. color map // 2. specular map // 3. normal map // 4. bump map var uvScaleMap; if ( material.map ) { uvScaleMap = material.map; } else if ( material.specularMap ) { uvScaleMap = material.specularMap; } else if ( material.normalMap ) { uvScaleMap = material.normalMap; } else if ( material.bumpMap ) { uvScaleMap = material.bumpMap; } if ( uvScaleMap !== undefined ) { 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.flipEnvMap.value = ( material.envMap instanceof THREE.WebGLRenderTargetCube ) ? 1 : -1; if ( _this.gammaInput ) { //uniforms.reflectivity.value = material.reflectivity * material.reflectivity; uniforms.reflectivity.value = material.reflectivity; } else { uniforms.reflectivity.value = material.reflectivity; } uniforms.refractionRatio.value = material.refractionRatio; uniforms.combine.value = material.combine; uniforms.useRefract.value = material.envMap && material.envMap.mapping instanceof THREE.CubeRefractionMapping; }; 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 refreshUniformsParticle ( uniforms, material ) { uniforms.psColor.value = material.color; uniforms.opacity.value = material.opacity; uniforms.size.value = material.size; uniforms.scale.value = renderer.getDomElement().height / 2.0; // TODO: Cache this. uniforms.map.value = material.map; }; 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 refreshUniformsPhong ( uniforms, material ) { uniforms.shininess.value = material.shininess; if ( _this.gammaInput ) { uniforms.ambient.value.copyGammaToLinear( material.ambient ); uniforms.emissive.value.copyGammaToLinear( material.emissive ); uniforms.specular.value.copyGammaToLinear( material.specular ); } else { uniforms.ambient.value = material.ambient; uniforms.emissive.value = material.emissive; uniforms.specular.value = material.specular; } if ( material.wrapAround ) { uniforms.wrapRGB.value.copy( material.wrapRGB ); } }; function refreshUniformsLambert ( uniforms, material ) { if ( _this.gammaInput ) { uniforms.ambient.value.copyGammaToLinear( material.ambient ); uniforms.emissive.value.copyGammaToLinear( material.emissive ); } else { uniforms.ambient.value = material.ambient; uniforms.emissive.value = material.emissive; } if ( material.wrapAround ) { uniforms.wrapRGB.value.copy( material.wrapRGB ); } }; function refreshUniformsLights ( uniforms, lights ) { uniforms.ambientLightColor.value = lights.ambient; uniforms.directionalLightColor.value = lights.directional.colors; uniforms.directionalLightDirection.value = lights.directional.positions; uniforms.pointLightColor.value = lights.point.colors; uniforms.pointLightPosition.value = lights.point.positions; uniforms.pointLightDistance.value = lights.point.distances; uniforms.spotLightColor.value = lights.spot.colors; uniforms.spotLightPosition.value = lights.spot.positions; uniforms.spotLightDistance.value = lights.spot.distances; uniforms.spotLightDirection.value = lights.spot.directions; uniforms.spotLightAngleCos.value = lights.spot.anglesCos; uniforms.spotLightExponent.value = lights.spot.exponents; uniforms.hemisphereLightSkyColor.value = lights.hemi.skyColors; uniforms.hemisphereLightGroundColor.value = lights.hemi.groundColors; uniforms.hemisphereLightDirection.value = lights.hemi.positions; }; function refreshUniformsShadow ( uniforms, lights ) { if ( uniforms.shadowMatrix ) { var j = 0; for ( var i = 0, il = lights.length; i < il; i ++ ) { var light = lights[ i ]; if ( ! light.castShadow ) continue; if ( light instanceof THREE.SpotLight || ( light instanceof THREE.DirectionalLight && ! light.shadowCascade ) ) { uniforms.shadowMap.value[ j ] = light.shadowMap; uniforms.shadowMapSize.value[ j ] = light.shadowMapSize; uniforms.shadowMatrix.value[ j ] = light.shadowMatrix; uniforms.shadowDarkness.value[ j ] = light.shadowDarkness; uniforms.shadowBias.value[ j ] = light.shadowBias; j ++; } } } }; // Uniforms (load to GPU) function loadUniformsMatrices ( uniforms, object ) { renderer.uniformMatrix4fv( uniforms.modelViewMatrix, object._modelViewMatrix.elements ); if ( uniforms.normalMatrix ) { renderer.uniformMatrix3fv( uniforms.normalMatrix, object._normalMatrix.elements ); } }; function getTextureUnit() { var textureUnit = _usedTextureUnits; if ( textureUnit >= renderer.maxTextures ) { console.warn( "WebGLRenderer: trying to use " + textureUnit + " texture units while this GPU supports only " + renderer.maxTextures ); } _usedTextureUnits += 1; return textureUnit; }; function loadUniformsGeneric ( program, uniforms ) { var uniform, value, type, location, texture, textureUnit, i, il, j, jl, offset; for ( j = 0, jl = uniforms.length; j < jl; j ++ ) { location = program.uniforms[ uniforms[ j ][ 1 ] ]; if ( !location ) continue; uniform = uniforms[ j ][ 0 ]; type = uniform.type; value = uniform.value; if ( type === "i" ) { // single integer renderer.uniform1i( location, value ); } else if ( type === "f" ) { // single float renderer.uniform1f( location, value ); } else if ( type === "v2" ) { // single THREE.Vector2 renderer.uniform2f( location, value.x, value.y ); } else if ( type === "v3" ) { // single THREE.Vector3 renderer.uniform3f( location, value.x, value.y, value.z ); } else if ( type === "v4" ) { // single THREE.Vector4 renderer.uniform4f( location, value.x, value.y, value.z, value.w ); } else if ( type === "c" ) { // single THREE.Color renderer.uniform3f( location, value.r, value.g, value.b ); } else if ( type === "iv1" ) { // flat array of integers (JS or typed array) renderer.uniform1iv( location, value ); } else if ( type === "iv" ) { // flat array of integers with 3 x N size (JS or typed array) renderer.uniform3iv( location, value ); } else if ( type === "fv1" ) { // flat array of floats (JS or typed array) renderer.uniform1fv( location, value ); } else if ( type === "fv" ) { // flat array of floats with 3 x N size (JS or typed array) renderer.uniform3fv( location, value ); } else if ( type === "v2v" ) { // array of THREE.Vector2 if ( uniform._array === undefined ) { uniform._array = new Float32Array( 2 * value.length ); } for ( i = 0, il = value.length; i < il; i ++ ) { offset = i * 2; uniform._array[ offset ] = value[ i ].x; uniform._array[ offset + 1 ] = value[ i ].y; } renderer.uniform2fv( location, uniform._array ); } else if ( type === "v3v" ) { // array of THREE.Vector3 if ( uniform._array === undefined ) { uniform._array = new Float32Array( 3 * value.length ); } for ( i = 0, il = value.length; i < il; i ++ ) { offset = i * 3; uniform._array[ offset ] = value[ i ].x; uniform._array[ offset + 1 ] = value[ i ].y; uniform._array[ offset + 2 ] = value[ i ].z; } renderer.uniform3fv( location, uniform._array ); } else if ( type === "v4v" ) { // array of THREE.Vector4 if ( uniform._array === undefined ) { uniform._array = new Float32Array( 4 * value.length ); } for ( i = 0, il = value.length; i < il; i ++ ) { offset = i * 4; uniform._array[ offset ] = value[ i ].x; uniform._array[ offset + 1 ] = value[ i ].y; uniform._array[ offset + 2 ] = value[ i ].z; uniform._array[ offset + 3 ] = value[ i ].w; } renderer.uniform4fv( location, uniform._array ); } else if ( type === "m4") { // single THREE.Matrix4 if ( uniform._array === undefined ) { uniform._array = new Float32Array( 16 ); } value.flattenToArray( uniform._array ); renderer.uniformMatrix4fv( location, uniform._array ); } else if ( type === "m4v" ) { // array of THREE.Matrix4 if ( uniform._array === undefined ) { uniform._array = new Float32Array( 16 * value.length ); } for ( i = 0, il = value.length; i < il; i ++ ) { value[ i ].flattenToArrayOffset( uniform._array, i * 16 ); } renderer.uniformMatrix4fv( location, uniform._array ); } else if ( type === "t" ) { // single THREE.Texture (2d or cube) texture = value; textureUnit = getTextureUnit(); renderer.uniform1i( location, textureUnit ); if ( !texture ) continue; if ( texture.image instanceof Array && texture.image.length === 6 ) { renderer.setCubeTexture( texture, textureUnit ); } else if ( texture instanceof THREE.WebGLRenderTargetCube ) { renderer.setCubeTextureDynamic( texture, textureUnit ); } else { renderer.setTexture( texture, textureUnit ); } } else if ( type === "tv" ) { // array of THREE.Texture (2d) if ( uniform._array === undefined ) { uniform._array = []; } for( i = 0, il = uniform.value.length; i < il; i ++ ) { uniform._array[ i ] = getTextureUnit(); } renderer.uniform1iv( location, uniform._array ); for( i = 0, il = uniform.value.length; i < il; i ++ ) { texture = uniform.value[ i ]; textureUnit = uniform._array[ i ]; if ( !texture ) continue; renderer.setTexture( texture, textureUnit ); } } } }; function setupMatrices ( object, camera ) { object._modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld ); object._normalMatrix.getNormalMatrix( object._modelViewMatrix ); }; // function setColorGamma( array, offset, color, intensitySq ) { array[ offset ] = color.r * color.r * intensitySq; array[ offset + 1 ] = color.g * color.g * intensitySq; array[ offset + 2 ] = color.b * color.b * intensitySq; }; function setColorLinear( array, offset, color, intensity ) { array[ offset ] = color.r * intensity; array[ offset + 1 ] = color.g * intensity; array[ offset + 2 ] = color.b * intensity; }; function setupLights ( program, lights ) { var l, ll, light, n, r = 0, g = 0, b = 0, color, skyColor, groundColor, intensity, intensitySq, position, distance, zlights = _lights, dirColors = zlights.directional.colors, dirPositions = zlights.directional.positions, pointColors = zlights.point.colors, pointPositions = zlights.point.positions, pointDistances = zlights.point.distances, spotColors = zlights.spot.colors, spotPositions = zlights.spot.positions, spotDistances = zlights.spot.distances, spotDirections = zlights.spot.directions, spotAnglesCos = zlights.spot.anglesCos, spotExponents = zlights.spot.exponents, hemiSkyColors = zlights.hemi.skyColors, hemiGroundColors = zlights.hemi.groundColors, hemiPositions = zlights.hemi.positions, dirLength = 0, pointLength = 0, spotLength = 0, hemiLength = 0, dirCount = 0, pointCount = 0, spotCount = 0, hemiCount = 0, dirOffset = 0, pointOffset = 0, spotOffset = 0, hemiOffset = 0; for ( l = 0, ll = lights.length; l < ll; l ++ ) { light = lights[ l ]; if ( light.onlyShadow ) continue; color = light.color; intensity = light.intensity; distance = light.distance; if ( light instanceof THREE.AmbientLight ) { if ( ! light.visible ) continue; if ( _this.gammaInput ) { r += color.r * color.r; g += color.g * color.g; b += color.b * color.b; } else { r += color.r; g += color.g; b += color.b; } } else if ( light instanceof THREE.DirectionalLight ) { dirCount += 1; if ( ! light.visible ) continue; _direction.getPositionFromMatrix( light.matrixWorld ); _vector3.getPositionFromMatrix( light.target.matrixWorld ); _direction.sub( _vector3 ); _direction.normalize(); // skip lights with undefined direction // these create troubles in OpenGL (making pixel black) if ( _direction.x === 0 && _direction.y === 0 && _direction.z === 0 ) continue; dirOffset = dirLength * 3; dirPositions[ dirOffset ] = _direction.x; dirPositions[ dirOffset + 1 ] = _direction.y; dirPositions[ dirOffset + 2 ] = _direction.z; if ( _this.gammaInput ) { setColorGamma( dirColors, dirOffset, color, intensity * intensity ); } else { setColorLinear( dirColors, dirOffset, color, intensity ); } dirLength += 1; } else if ( light instanceof THREE.PointLight ) { pointCount += 1; if ( ! light.visible ) continue; pointOffset = pointLength * 3; if ( _this.gammaInput ) { setColorGamma( pointColors, pointOffset, color, intensity * intensity ); } else { setColorLinear( pointColors, pointOffset, color, intensity ); } _vector3.getPositionFromMatrix( light.matrixWorld ); pointPositions[ pointOffset ] = _vector3.x; pointPositions[ pointOffset + 1 ] = _vector3.y; pointPositions[ pointOffset + 2 ] = _vector3.z; pointDistances[ pointLength ] = distance; pointLength += 1; } else if ( light instanceof THREE.SpotLight ) { spotCount += 1; if ( ! light.visible ) continue; spotOffset = spotLength * 3; if ( _this.gammaInput ) { setColorGamma( spotColors, spotOffset, color, intensity * intensity ); } else { setColorLinear( spotColors, spotOffset, color, intensity ); } _vector3.getPositionFromMatrix( light.matrixWorld ); spotPositions[ spotOffset ] = _vector3.x; spotPositions[ spotOffset + 1 ] = _vector3.y; spotPositions[ spotOffset + 2 ] = _vector3.z; spotDistances[ spotLength ] = distance; _direction.copy( _vector3 ); _vector3.getPositionFromMatrix( light.target.matrixWorld ); _direction.sub( _vector3 ); _direction.normalize(); spotDirections[ spotOffset ] = _direction.x; spotDirections[ spotOffset + 1 ] = _direction.y; spotDirections[ spotOffset + 2 ] = _direction.z; spotAnglesCos[ spotLength ] = Math.cos( light.angle ); spotExponents[ spotLength ] = light.exponent; spotLength += 1; } else if ( light instanceof THREE.HemisphereLight ) { hemiCount += 1; if ( ! light.visible ) continue; _direction.getPositionFromMatrix( light.matrixWorld ); _direction.normalize(); // skip lights with undefined direction // these create troubles in OpenGL (making pixel black) if ( _direction.x === 0 && _direction.y === 0 && _direction.z === 0 ) continue; hemiOffset = hemiLength * 3; hemiPositions[ hemiOffset ] = _direction.x; hemiPositions[ hemiOffset + 1 ] = _direction.y; hemiPositions[ hemiOffset + 2 ] = _direction.z; skyColor = light.color; groundColor = light.groundColor; if ( _this.gammaInput ) { intensitySq = intensity * intensity; setColorGamma( hemiSkyColors, hemiOffset, skyColor, intensitySq ); setColorGamma( hemiGroundColors, hemiOffset, groundColor, intensitySq ); } else { setColorLinear( hemiSkyColors, hemiOffset, skyColor, intensity ); setColorLinear( hemiGroundColors, hemiOffset, groundColor, intensity ); } hemiLength += 1; } } // null eventual remains from removed lights // (this is to avoid if in shader) for ( l = dirLength * 3, ll = Math.max( dirColors.length, dirCount * 3 ); l < ll; l ++ ) dirColors[ l ] = 0.0; for ( l = pointLength * 3, ll = Math.max( pointColors.length, pointCount * 3 ); l < ll; l ++ ) pointColors[ l ] = 0.0; for ( l = spotLength * 3, ll = Math.max( spotColors.length, spotCount * 3 ); l < ll; l ++ ) spotColors[ l ] = 0.0; for ( l = hemiLength * 3, ll = Math.max( hemiSkyColors.length, hemiCount * 3 ); l < ll; l ++ ) hemiSkyColors[ l ] = 0.0; for ( l = hemiLength * 3, ll = Math.max( hemiGroundColors.length, hemiCount * 3 ); l < ll; l ++ ) hemiGroundColors[ l ] = 0.0; zlights.directional.length = dirLength; zlights.point.length = pointLength; zlights.spot.length = spotLength; zlights.hemi.length = hemiLength; zlights.ambient[ 0 ] = r; zlights.ambient[ 1 ] = g; zlights.ambient[ 2 ] = b; }; // Allocations function allocateBones ( object ) { if ( renderer.supportsBoneTextures && object && object.useVertexTexture ) { return 1024; } else { // default for when object is not specified // ( for example when prebuilding shader // to be used with multiple objects ) // // - leave some extra space for other uniforms // - limit here is ANGLE's 254 max uniform vectors // (up to 54 should be safe) var nVertexUniforms = renderer.maxVertexUniformVectors var nVertexMatrices = Math.floor( ( nVertexUniforms - 20 ) / 4 ); var maxBones = nVertexMatrices; if ( object !== undefined && object instanceof THREE.SkinnedMesh ) { maxBones = Math.min( object.bones.length, maxBones ); if ( maxBones < object.bones.length ) { console.warn( "WebGLRenderer: too many bones - " + object.bones.length + ", this GPU supports just " + maxBones + " (try OpenGL instead of ANGLE)" ); } } return maxBones; } }; function allocateLights ( lights ) { var l, ll, light, dirLights, pointLights, spotLights, hemiLights; dirLights = pointLights = spotLights = hemiLights = 0; for ( l = 0, ll = lights.length; l < ll; l ++ ) { light = lights[ l ]; if ( light.onlyShadow ) continue; if ( light instanceof THREE.DirectionalLight ) dirLights ++; if ( light instanceof THREE.PointLight ) pointLights ++; if ( light instanceof THREE.SpotLight ) spotLights ++; if ( light instanceof THREE.HemisphereLight ) hemiLights ++; } return { 'directional' : dirLights, 'point' : pointLights, 'spot': spotLights, 'hemi': hemiLights }; }; function allocateShadows ( lights ) { var l, ll, light, maxShadows = 0; for ( l = 0, ll = lights.length; l < ll; l++ ) { light = lights[ l ]; if ( ! light.castShadow ) continue; if ( light instanceof THREE.SpotLight ) maxShadows ++; if ( light instanceof THREE.DirectionalLight && ! light.shadowCascade ) maxShadows ++; } return maxShadows; }; // default plugins (order is important) this.shadowMapPlugin = new THREE.ShadowMapPlugin(); this.addPrePlugin( this.shadowMapPlugin ); this.addPostPlugin( new THREE.SpritePlugin() ); this.addPostPlugin( new THREE.LensFlarePlugin() ); };