three
Version:
JavaScript 3D library
2,196 lines (1,252 loc) • 87 kB
JavaScript
/**
* @author supereggbert / http://www.paulbrunt.co.uk/
* @author mrdoob / http://mrdoob.com/
* @author alteredq / http://alteredqualia.com/
* @author szimek / https://github.com/szimek/
* @author tschw
*/
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;
// user-defined clipping
this.clippingPlanes = [];
this.localClippingEnabled = false;
// physically based shading
this.gammaFactor = 2.0; // for backwards compatibility
this.gammaInput = false;
this.gammaOutput = false;
// physical lights
this.physicallyCorrectLights = false;
// tone mapping
this.toneMapping = THREE.LinearToneMapping;
this.toneMappingExposure = 1.0;
this.toneMappingWhitePoint = 1.0;
// 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(),
// clipping
_clipping = new THREE.WebGLClipping(),
_clippingEnabled = false,
_localClippingEnabled = false,
_sphere = new THREE.Sphere(),
// 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: []
},
// 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.';
}
}
// Some experimental-webgl implementations do not have getShaderPrecisionFormat
if ( _gl.getShaderPrecisionFormat === undefined ) {
_gl.getShaderPrecisionFormat = function () {
return { 'rangeMin': 1, 'rangeMax': 1, 'precision': 1 };
};
}
_canvas.addEventListener( 'webglcontextlost', onContextLost, false );
} catch ( error ) {
console.error( 'THREE.WebGLRenderer: ' + error );
}
var _isWebGL2 = (typeof WebGL2RenderingContext !== 'undefined' && _gl instanceof WebGL2RenderingContext);
var extensions = new THREE.WebGLExtensions( _gl );
extensions.get( 'WEBGL_depth_texture' );
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 ) return;
if ( textureProperties.__webglTexture !== undefined ) {
_gl.deleteTexture( textureProperties.__webglTexture );
}
if ( renderTarget.depthTexture ) {
renderTarget.depthTexture.dispose();
}
if ( renderTarget instanceof THREE.WebGLRenderTargetCube ) {
for ( var i = 0; i < 6; i ++ ) {
_gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ] );
if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer[ i ] );
}
} else {
_gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer );
if ( renderTargetProperties.__webglDepthbuffer ) _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.type !== 'MeshPhysicalMaterial' && 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 );
}
}
program.getUniforms().setValue(
_gl, '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 ) {
if ( 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 type = _gl.FLOAT;
var array = geometryAttribute.array;
var normalized = geometryAttribute.normalized;
if ( array instanceof Float32Array ) {
type = _gl.FLOAT;
} else if ( array instanceof Float64Array ) {
console.warn("Unsupported data buffer format: Float64Array");
} else if ( array instanceof Uint16Array ) {
type = _gl.UNSIGNED_SHORT;
} else if ( array instanceof Int16Array ) {
type = _gl.SHORT;
} else if ( array instanceof Uint32Array ) {
type = _gl.UNSIGNED_INT;
} else if ( array instanceof Int32Array ) {
type = _gl.INT;
} else if ( array instanceof Int8Array ) {
type = _gl.BYTE;
} else if ( array instanceof Uint8Array ) {
type = _gl.UNSIGNED_BYTE;
}
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, type, normalized, 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, type, normalized, 0, startIndex * size * geometryAttribute.array.BYTES_PER_ELEMENT );
}
} 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;
_localClippingEnabled = this.localClippingEnabled;
_clippingEnabled = _clipping.init(
this.clippingPlanes, _localClippingEnabled, camera );
projectObject( scene, camera );
opaqueObjects.length = opaqueObjectsLastIndex + 1;
transparentObjects.length = transparentObjectsLastIndex + 1;
if ( _this.sortObjects === true ) {
opaqueObjects.sort( painterSortStable );
transparentObjects.sort( reversePainterSortStable );
}
//
if ( _clippingEnabled ) _clipping.beginShadows();
setupShadows( lights );
shadowMap.render( scene, camera );
setupLights( lights, camera );
if ( _clippingEnabled ) _clipping.endShadows();
//
_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 );
}
}
// TODO Duplicated code (Frustum)
function isObjectViewable( object ) {
var geometry = object.geometry;
if ( geometry.boundingSphere === null )
geometry.computeBoundingSphere();
_sphere.copy( geometry.boundingSphere ).
applyMatrix4( object.matrixWorld );
return isSphereViewable( _sphere );
}
function isSpriteViewable( sprite ) {
_sphere.center.set( 0, 0, 0 );
_sphere.radius = 0.7071067811865476;
_sphere.applyMatrix4( sprite.matrixWorld );
return isSphereViewable( _sphere );
}
function isSphereViewable( sphere ) {
if ( ! _frustum.intersectsSphere( sphere ) ) return false;
var numPlanes = _clipping.numPlanes;
if ( numPlanes === 0 ) return true;
var planes = _this.clippingPlanes,
center = sphere.center,
negRad = - sphere.radius,
i = 0;
do {
// out when deeper than radius in the negative halfspace
if ( planes[ i ].distanceToPoint( center ) < negRad ) return false;
} while ( ++ i !== numPlanes );
return true;
}
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 || isSpriteViewable( 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 || isObjectViewable( 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, _clipping.numPlanes, 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 ++;
}
}
}
var uniforms = materialProperties.__webglShader.uniforms;
if ( ! ( material instanceof THREE.ShaderMaterial ) &&
! ( material instanceof THREE.RawShaderMaterial ) ||
material.clipping === true ) {
materialProperties.numClippingPlanes = _clipping.numPlanes;
uniforms.clippingPlanes = _clipping.uniform;
}
if ( 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;
}
var progUniforms = materialProperties.program.getUniforms(),
uniformsList =
THREE.WebGLUniforms.seqWithValue( progUniforms.seq, uniforms );
materialProperties.uniformsList = uniformsList;
materialProperties.dynamicUniforms =
THREE.WebGLUniforms.splitDynamic( uniformsList, uniforms );
}
function setMaterial( material ) {
if ( material.side !== THREE.DoubleSide )
state.enable( _gl.CULL_FACE );
else
state.disable( _gl.CULL_FACE );
state.setFlipSided( material.side === THREE.BackSide );
if ( material.transparent === true ) {
state.setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.premultipliedAlpha );
} 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 setProgram( camera, fog, material, object ) {
_usedTextureUnits = 0;
var materialProperties = properties.get( material );
if ( _clippingEnabled ) {
if ( _localClippingEnabled || camera !== _currentCamera ) {
var useCache =
camera === _currentCamera &&
material.id === _currentMaterialId;
// we might want to call this function with some ClippingGroup
// object instead of the material, once it becomes feasible
// (#8465, #8379)
_clipping.setState(
material.clippingPlanes, material.clipShadows,
camera, materialProperties, useCache );
}
if ( materialProperties.numClippingPlanes !== undefined &&
materialProperties.numClippingPlanes !== _clipping.numPlanes ) {
material.needsUpdate = true;
}
}
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 ) {
p_uniforms.set( _gl, camera, 'projectionMatrix' );
if ( capabilities.logarithmicDepthBuffer ) {
p_uniforms.setValue( _gl, '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 ) {
var uCamPos = p_uniforms.map.cameraPosition;
if ( uCamPos !== undefined ) {
uCamPos.setValue( _gl,
_vector3.setFromMatrixPosition( camera.matrixWorld ) );
}
}
if ( material instanceof THREE.MeshPhongMaterial ||
material instanceof THREE.MeshLambertMaterial ||
material instanceof THREE.MeshBasicMaterial ||
material instanceof THREE.MeshStandardMaterial ||
material instanceof THREE.ShaderMaterial ||
material.skinning ) {
p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse );
}
p_uniforms.set( _gl, _this, 'toneMappingExposure' );
p_uniforms.set( _gl, _this, 'toneMappingWhitePoint' );
}
// 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 ) {
p_uniforms.setOptional( _gl, object, 'bindMatrix' );
p_uniforms.setOptional( _gl, object, 'bindMatrixInverse' );
var skeleton = object.skeleton;
if ( skeleton ) {
if ( capabilities.floatVertexTextures && skeleton.useVertexTexture ) {
p_uniforms.set( _gl, skeleton, 'boneTexture' );
p_uniforms.set( _gl, skeleton, 'boneTextureWidth' );
p_uniforms.set( _gl, skeleton, 'boneTextureHeight' );
} else {
p_uniforms.setOptional( _gl, skeleton, 'boneMatrices' );
}
}
}
if ( refreshMaterial ) {
if ( 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 ||
material instanceof THREE.MeshDepthMaterial ) {
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.MeshPhysicalMaterial ) {
refreshUniformsPhysical( m_uniforms, material );
} else if ( material instanceof THREE.MeshStandardMaterial ) {
refreshUniformsStandard( m_uniforms, material );
} else if ( material instanceof THREE.MeshDepthMaterial ) {
if ( material.displacementMap ) {
m_uniforms.displacementMap.value = material.displacementMap;
m_uniforms.displacementScale.value = material.displacementScale;
m_uniforms.displacementBias.value = material.displacementBias;
}
} else if ( material instanceof THREE.MeshNormalMaterial ) {
m_uniforms.opacity.value = material.opacity;
}
THREE.WebGLUniforms.upload(
_gl, materialProperties.uniformsList, m_uniforms, _this );
}
// common matrices
p_uniforms.set( _gl, object, 'modelViewMatrix' );
p_uniforms.set( _gl, object, 'normalMatrix' );
p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld );
// dynamic uniforms
var dynUniforms = materialProperties.dynamicUniforms;
if ( dynUniforms !== null ) {
THREE.WebGLUniforms.evalDynamic(
dynUniforms, m_uniforms, object, camera );
THREE.WebGLUniforms.upload( _gl, dynUniforms, m_uniforms, _this );
}
return program;
}
// 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.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 ) {
// backwards compatibility
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;
// don't flip CubeTexture envMaps, flip everything else:
// WebGLRenderTargetCube will be flipped for backwards compatibility
// WebGLRenderTargetCube.texture will be flipped because it's a Texture and NOT a CubeTexture
// this check must be handled differently, or removed entirely, if WebGLRenderTargetCube uses a CubeTexture in the future
uniforms.flipEnvMap.value = ( ! ( material.envMap instanceof THREE.CubeTexture ) ) ? 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 * 0.5;
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;
}
}
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;
}
if ( material.bumpMap ) {
uniforms.bumpMap.value = material.bumpMap;
uniforms.bumpScale.value = material.bumpScale;
}
if ( material.normalMap ) {
uniforms.n