threex
Version:
Game Extensions for three.js http://www.threejsgames.com/extensions/
657 lines (394 loc) • 14.1 kB
JavaScript
/**
* @author mrdoob / http://mrdoob.com/
*
* parameters = {
* canvas: canvas,
* contextAttributes: {
* alpha: true,
* depth: true,
* stencil: false,
* antialias: true,
* premultipliedAlpha: true,
* preserveDrawingBuffer: false
* }
* }
*
*/
THREE.WebGLRenderer3 = function ( parameters ) {
console.log( 'THREE.WebGLRenderer3', THREE.REVISION );
parameters = parameters || {};
var scope = this;
var canvas = parameters.canvas !== undefined ? parameters.canvas : document.createElement( 'canvas' );
var devicePixelRatio = parameters.devicePixelRatio !== undefined
? parameters.devicePixelRatio
: window.devicePixelRatio !== undefined
? window.devicePixelRatio
: 1;
var gl;
try {
var attributes = parameters.contextAttributes || {};
gl = canvas.getContext( 'webgl', attributes ) || canvas.getContext( 'experimental-webgl', attributes );
} catch ( exception ) {
console.error( exception );
}
var precision = 'highp';
var extensions = {};
if ( gl !== null ) {
extensions.element_index_uint = gl.getExtension( 'OES_element_index_uint' );
extensions.texture_float = gl.getExtension( 'OES_texture_float' );
extensions.texture_float_linear = gl.getExtension( 'OES_texture_float_linear' );
extensions.standard_derivatives = gl.getExtension( 'OES_standard_derivatives' );
extensions.texture_filter_anisotropic = gl.getExtension( 'EXT_texture_filter_anisotropic' ) || gl.getExtension( 'MOZ_EXT_texture_filter_anisotropic' ) || gl.getExtension( 'WEBKIT_EXT_texture_filter_anisotropic' );
extensions.compressed_texture_s3tc = gl.getExtension( 'WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'MOZ_WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_s3tc' );
gl.clearColor( 0, 0, 0, 1 );
gl.clearDepth( 1 );
gl.clearStencil( 0 );
gl.enable( gl.DEPTH_TEST );
gl.depthFunc( gl.LEQUAL );
gl.enable( gl.CULL_FACE );
gl.frontFace( gl.CCW );
gl.cullFace( gl.BACK );
gl.enable( gl.BLEND );
gl.blendEquation( gl.FUNC_ADD );
gl.blendFunc( gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA );
gl.clearColor( 0, 0, 0, 0 );
}
var clearColor = new THREE.Color( 0x000000 );
var clearAlpha = 0;
//
var vector3 = new THREE.Vector3();
var frustum = new THREE.Frustum();
var normalMatrix = new THREE.Matrix3();
var modelViewMatrix = new THREE.Matrix4();
var cameraViewProjectionMatrix = new THREE.Matrix4();
// buffers
var buffers = {};
var getBuffer = function ( geometry, material ) {
var hash = geometry.id.toString() + '+' + material.id.toString();
if ( buffers[ hash ] !== undefined ) {
return buffers[ hash ];
}
var vertices = geometry.vertices;
var faces = geometry.faces;
//
var positions = [];
var addPosition = function ( position ) {
positions.push( position.x, position.y, position.z );
}
var normals = [];
var addNormal = function ( normal ) {
normals.push( normal.x, normal.y, normal.z );
}
for ( var i = 0, l = faces.length; i < l; i ++ ) {
var face = faces[ i ];
var vertexNormals = face.vertexNormals.length > 0;
addPosition( vertices[ face.a ] );
addPosition( vertices[ face.b ] );
addPosition( vertices[ face.c ] );
if ( vertexNormals === true ) {
addNormal( face.vertexNormals[ 0 ] );
addNormal( face.vertexNormals[ 1 ] );
addNormal( face.vertexNormals[ 2 ] );
} else {
addNormal( face.normal );
addNormal( face.normal );
addNormal( face.normal );
}
if ( face instanceof THREE.Face4 ) {
addPosition( vertices[ face.a ] );
addPosition( vertices[ face.c ] );
addPosition( vertices[ face.d ] );
if ( vertexNormals === true ) {
addNormal( face.vertexNormals[ 0 ] );
addNormal( face.vertexNormals[ 2 ] );
addNormal( face.vertexNormals[ 3 ] );
} else {
addNormal( face.normal );
addNormal( face.normal );
addNormal( face.normal );
}
}
}
var buffer = {
positions: gl.createBuffer(),
normals: gl.createBuffer(),
count: positions.length / 3
};
gl.bindBuffer( gl.ARRAY_BUFFER, buffer.positions );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( positions ), gl.STATIC_DRAW );
gl.bindBuffer( gl.ARRAY_BUFFER, buffer.normals );
gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( normals ), gl.STATIC_DRAW );
buffers[ hash ] = buffer;
scope.info.memory.geometries ++;
return buffer;
};
// programs
var programs = {};
var programsCache = {};
var getProgram = function ( material ) {
if ( programs[ material.id ] !== undefined ) {
return programs[ material.id ];
}
var vertexShader = [
'precision ' + precision + ' float;',
'attribute vec3 position;',
'attribute vec3 normal;',
'uniform mat4 modelViewMatrix;',
'uniform mat3 normalMatrix;',
'uniform mat4 projectionMatrix;',
''
].join( '\n' );
var fragmentShader = [
'precision ' + precision + ' float;',
''
].join( '\n' );
if ( material instanceof THREE.ShaderMaterial ) {
vertexShader += material.vertexShader;
fragmentShader += material.fragmentShader;
} else if ( material instanceof THREE.MeshNormalMaterial ) {
vertexShader += [
'varying vec3 vNormal;',
'void main() {',
' vNormal = normalize( normalMatrix * normal );',
' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
'}'
].join( '\n' );
fragmentShader += [
'varying vec3 vNormal;',
'uniform float opacity;',
'void main() {',
' gl_FragColor = vec4( 0.5 * normalize( vNormal ) + 0.5, opacity );',
'}'
].join( '\n' );
} else {
vertexShader += [
'void main() {',
' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
'}'
].join( '\n' );
fragmentShader += [
'void main() {',
' gl_FragColor = vec4( 1.0, 0, 0, 1.0 );',
'}'
].join( '\n' );
}
var program;
var code = vertexShader + fragmentShader;
if ( programsCache[ code ] !== undefined ) {
program = programsCache[ code ];
programs[ material.id ] = program;
} else {
program = gl.createProgram();
gl.attachShader( program, createShader( gl.VERTEX_SHADER, vertexShader ) );
gl.attachShader( program, createShader( gl.FRAGMENT_SHADER, fragmentShader ) );
gl.linkProgram( program );
if ( gl.getProgramParameter( program, gl.LINK_STATUS ) === true ) {
programsCache[ code ] = program;
programs[ material.id ] = program;
scope.info.memory.programs ++;
} else {
console.error( 'VALIDATE_STATUS: ' + gl.getProgramParameter( program, gl.VALIDATE_STATUS ) );
console.error( 'GL_ERROR: ' + gl.getError() );
// fallback
program = getProgram( new THREE.MeshBasicMaterial() );
programs[ material.id ] = program;
}
}
return program;
};
var createShader = function ( type, string ) {
var shader = gl.createShader( type );
gl.shaderSource( shader, string );
gl.compileShader( shader );
if ( gl.getShaderParameter( shader, gl.COMPILE_STATUS ) === true ) {
// console.log( string );
} else {
console.error( gl.getShaderInfoLog( shader ), string );
return null;
}
return shader;
};
this.info = {
memory: {
programs: 0,
geometries: 0,
textures: 0
},
render: {
calls: 0,
vertices: 0,
faces: 0,
points: 0
}
};
this.domElement = canvas;
this.extensions = extensions;
this.autoClear = true; // TODO: Make private
this.setClearColor = function ( color, alpha ) {
clearColor.set( color );
clearAlpha = alpha !== undefined ? alpha : 1;
gl.clearColor( clearColor.r, clearColor.g, clearColor.b, clearAlpha );
};
this.setSize = function ( width, height, updateStyle ) {
canvas.width = width * devicePixelRatio;
canvas.height = height * devicePixelRatio;
if ( devicePixelRatio !== 1 && updateStyle !== false ) {
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
}
gl.viewport( 0, 0, canvas.width, canvas.height );
};
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 );
};
// blending
var currentBlending = null;
var setBlending = function ( blending ) {
if ( blending !== currentBlending ) {
if ( blending === THREE.NoBlending ) {
gl.disable( gl.BLEND );
} else {
gl.enable( gl.BLEND );
}
currentBlending = blending;
}
};
// depthTest
var currentDepthTest = null;
var setDepthTest = function ( value ) {
if ( value !== currentDepthTest ) {
value === true ? gl.enable( gl.DEPTH_TEST ) : gl.disable( gl.DEPTH_TEST );
currentDepthTest = value;
}
};
// depthWrite
var currentDepthWrite = null;
var setDepthWrite = function ( value ) {
if ( value !== currentDepthWrite ) {
gl.depthMask( value );
currentDepthWrite = value;
}
};
var objectsOpaque = [];
var objectsTransparent = [];
var projectObject = function ( object, camera ) {
if ( object.visible === false ) return;
if ( object instanceof THREE.Mesh && frustum.intersectsObject( object ) === true ) {
// TODO: Do not polute scene graph with .z
if ( object.renderDepth !== null ) {
object.z = object.renderDepth;
} else {
vector3.getPositionFromMatrix( object.matrixWorld );
vector3.applyProjection( cameraViewProjectionMatrix );
object.z = vector3.z;
}
if ( object.material.transparent === true ) {
objectsTransparent.push( object );
} else {
objectsOpaque.push( object );
}
}
for ( var i = 0, l = object.children.length; i < l; i ++ ) {
projectObject( object.children[ i ], camera );
}
};
var sortOpaque = function ( a, b ) {
return a.z - b.z;
};
var sortTransparent = function ( a, b ) {
return a.z !== b.z ? b.z - a.z : b.id - a.id;
};
var currentBuffer, currentMaterial, currentProgram;
var locations = {};
var renderObject = function ( object, camera ) {
var buffer = getBuffer( object.geometry, object.material );
var material = object.material;
if ( material !== currentMaterial ) {
var program = getProgram( object.material );
if ( program !== currentProgram ) {
gl.useProgram( program );
locations.modelViewMatrix = gl.getUniformLocation( program, 'modelViewMatrix' );
locations.normalMatrix = gl.getUniformLocation( program, 'normalMatrix' );
locations.projectionMatrix = gl.getUniformLocation( program, 'projectionMatrix' );
locations.position = gl.getAttribLocation( program, 'position' );
locations.normal = gl.getAttribLocation( program, 'normal' );
gl.uniformMatrix4fv( locations.projectionMatrix, false, camera.projectionMatrix.elements );
currentProgram = program;
}
if ( material instanceof THREE.MeshNormalMaterial ) {
gl.uniform1f( gl.getUniformLocation( program, 'opacity' ), material.opacity );
} else if ( material instanceof THREE.ShaderMaterial ) {
var uniforms = material.uniforms;
for ( var uniform in uniforms ) {
var location = gl.getUniformLocation( program, uniform );
var type = uniforms[ uniform ].type;
var value = uniforms[ uniform ].value;
if ( type === "i" ) { // single integer
gl.uniform1i( location, value );
} else if ( type === "f" ) { // single float
gl.uniform1f( location, value );
} else if ( type === "v2" ) { // single THREE.Vector2
gl.uniform2f( location, value.x, value.y );
} else if ( type === "v3" ) { // single THREE.Vector3
gl.uniform3f( location, value.x, value.y, value.z );
} else if ( type === "v4" ) { // single THREE.Vector4
gl.uniform4f( location, value.x, value.y, value.z, value.w );
} else if ( type === "c" ) { // single THREE.Color
gl.uniform3f( location, value.r, value.g, value.b );
}
}
}
currentMaterial = material;
}
if ( buffer !== currentBuffer ) {
gl.bindBuffer( gl.ARRAY_BUFFER, buffer.positions );
gl.enableVertexAttribArray( locations.position );
gl.vertexAttribPointer( locations.position, 3, gl.FLOAT, false, 0, 0 );
if ( locations.normal >= 0 ) {
gl.bindBuffer( gl.ARRAY_BUFFER, buffer.normals );
gl.enableVertexAttribArray( locations.normal );
gl.vertexAttribPointer( locations.normal, 3, gl.FLOAT, false, 0, 0 );
}
currentBuffer = buffer;
}
modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
normalMatrix.getNormalMatrix( modelViewMatrix );
gl.uniformMatrix4fv( locations.modelViewMatrix, false, modelViewMatrix.elements );
gl.uniformMatrix3fv( locations.normalMatrix, false, normalMatrix.elements );
gl.drawArrays( gl.TRIANGLES, 0, buffer.count );
scope.info.render.calls ++;
};
this.render = function ( scene, camera ) {
if ( this.autoClear === true ) this.clear();
scene.updateMatrixWorld();
if ( camera.parent === undefined ) camera.updateMatrixWorld();
camera.matrixWorldInverse.getInverse( camera.matrixWorld );
cameraViewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
frustum.setFromMatrix( cameraViewProjectionMatrix );
objectsOpaque.length = 0;
objectsTransparent.length = 0;
scope.info.render.calls = 0;
currentBuffer = undefined;
currentMaterial = undefined;
currentProgram = undefined;
projectObject( scene, camera );
if ( objectsOpaque.length > 0 ) {
objectsOpaque.sort( sortOpaque );
setBlending( THREE.NoBlending );
for ( var i = 0, l = objectsOpaque.length; i < l; i ++ ) {
renderObject( objectsOpaque[ i ], camera );
}
}
if ( objectsTransparent.length > 0 ) {
objectsTransparent.sort( sortTransparent );
setBlending( THREE.NormalBlending );
for ( var i = 0, l = objectsTransparent.length; i < l; i ++ ) {
renderObject( objectsTransparent[ i ], camera );
}
}
};
};