d2k
Version:
rendering engine from the Dark side of the Force - wip
362 lines (227 loc) • 9.48 kB
JavaScript
import { oftype } from 'u3s';
import { Mouse } from './d2k.mouse';
import fshaderSource from '../glsl/renderer.fragment.glsl';
import vshaderSource from '../glsl/renderer.vertex.glsl';
/**
* @author monsieurbadia / https://monsieurbadia.com
*/
let animationFrameId = 0;
export class Renderer {
gl = null;
radian = 0;
program = null;
isContextLost = false;
isRendering = false;
animationFrame = null;
lastFrame = null;
failIfMajorPerformanceCaveat = true;
powerPreference = 'default';
preserveDrawingBuffer = false;
constructor ( {
alpha = false,
antialias = true,
autoClear = true,
canvas = document.createElement( 'canvas' ),
width = 200,
height = 200,
dpr = window.devicePixelRatio
} = {} ) {
this.onAnimationFrame = this.onAnimationFrame.bind( this );
Object.assign( this, { alpha, antialias, autoClear, canvas, width, height, dpr } );
canvas.addEventListener( 'webglcontextlost', this.oncontextlost, false );
canvas.addEventListener( 'webglcontextrestored', this.oncontextrestore, false );
this.gl = canvas.getContext( 'experimental-webgl', {
alpha,
antialias,
failIfMajorPerformanceCaveat: this.failIfMajorPerformanceCaveat,
powerPreference: this.powerPreference,
preserveDrawingBuffer: this.preserveDrawingBuffer
} );
this.mouse = new Mouse( 0, 0, canvas );
this.mouse.enable = true;
this
.initProgram()
.initGL()
.resize();
}
oncontextlost ( event ) {
event.preventDefault();
console.log( '<• renderer context lost.' );
this.isContextLost = true;
}
oncontextrestore ( event ) {
console.log( '<• renderer context lost.' );
this.isContextLost = false;
this
.initProgram()
.initGL()
.resize();
}
render ( { scene, camera } ) {
let object3d;
if ( this.isContextLost ) return;
for ( let i = 0; i < scene.children.length; i++ ) {
object3d = scene.children[ i ];
if ( object3d ) {
if ( !object3d.__positionBuffer ) object3d.__positionBuffer = this.gl.createBuffer();
this.gl.bindBuffer( this.gl.ARRAY_BUFFER, object3d.__positionBuffer );
this.gl.bufferData( this.gl.ARRAY_BUFFER, new Float32Array( object3d.vertices ), this.gl.STATIC_DRAW );
if ( !object3d.__colorBuffer ) object3d.__colorBuffer = this.gl.createBuffer();
this.gl.bindBuffer( this.gl.ARRAY_BUFFER, object3d.__colorBuffer );
this.gl.bufferData( this.gl.ARRAY_BUFFER, new Float32Array( object3d.colors ), this.gl.STATIC_DRAW );
if ( object3d.normals.length > 0 ) {
if ( !object3d.__normalBuffer ) object3d.__normalBuffer = this.gl.createBuffer();
this.gl.bindBuffer( this.gl.ARRAY_BUFFER, object3d.__normalBuffer );
this.gl.bufferData( this.gl.ARRAY_BUFFER, new Float32Array( object3d.normals ), this.gl.STATIC_DRAW );
}
if ( !object3d.__indexBuffer ) object3d.__indexBuffer = this.gl.createBuffer();
this.gl.bindBuffer( this.gl.ELEMENT_ARRAY_BUFFER, object3d.__indexBuffer );
this.gl.bufferData( this.gl.ELEMENT_ARRAY_BUFFER, new Uint16Array( object3d.indices ), this.gl.STATIC_DRAW );
this.gl.bindBuffer( this.gl.ELEMENT_ARRAY_BUFFER, object3d.__indexBuffer );
this.setupMatrices( { object3d, camera } );
this.gl.bindBuffer( this.gl.ARRAY_BUFFER, object3d.__positionBuffer );
this.gl.vertexAttribPointer( this.program.position, 3, this.gl.FLOAT, false, 0, 0 );
this.gl.enableVertexAttribArray( this.program.position );
this.gl.bindBuffer( this.gl.ARRAY_BUFFER, object3d.__colorBuffer );
this.gl.vertexAttribPointer( this.program.color, 4, this.gl.FLOAT, false, 0, 0 );
this.gl.enableVertexAttribArray( this.program.color );
if ( object3d.normals.length > 0 ) {
this.gl.bindBuffer( this.gl.ARRAY_BUFFER, object3d.__normalBuffer );
this.gl.vertexAttribPointer( this.program.normal, 3, this.gl.FLOAT, false, 0, 0 );
this.gl.enableVertexAttribArray( this.program.normal );
}
this.gl.uniformMatrix4fv( this.program.modelViewMatrix, false, camera.modelViewMatrix.value );
this.gl.uniformMatrix4fv( this.program.normalMatrix, false, camera.normalMatrix.value );
this.gl.uniformMatrix4fv( this.program.projectionMatrix, false, camera.projectionMatrix.value );
this.gl.uniformMatrix4fv( this.program.object3dMatrix, false, object3d.matrix.value );
this.gl.drawElements( this.gl.TRIANGLES, object3d.indices.length, this.gl.UNSIGNED_SHORT, 0 );
this.gl.flush();
}
}
this.radian += Math.PI / 180;
return this;
}
clear () {
this.gl.clearColor( 0.0, 0.0, 0.0, 1.0 );
this.gl.clearDepth( 1.0 );
this.gl.clear( this.gl.COLOR_BUFFER_BIT | this.gl.DEPTH_BUFFER_BIT );
this.clean();
return this
}
clean () {
this.canvas.removeEventListener( 'webglcontextlost', this.oncontextlost, false );
this.canvas.removeEventListener( 'webglcontextrestored', this.oncontextrestore, false );
window.cancelAnimationFrame( animationFrameId );
}
getShader = ( { type, codeSource } ) => {
const shader = {
fragment: this.gl.createShader( this.gl.FRAGMENT_SHADER ),
vertex: this.gl.createShader( this.gl.VERTEX_SHADER )
};
const currentShader = shader[ type ];
this.gl.shaderSource( currentShader, codeSource );
this.gl.compileShader( currentShader );
if ( !this.gl.getShaderParameter( currentShader, this.gl.COMPILE_STATUS ) ) {
console.log( this.gl.getShaderInfoLog( currentShader ) );
return null;
}
return currentShader;
}
initGL () {
this.gl.enable( this.gl.DEPTH_TEST );
this.gl.depthFunc( this.gl.LEQUAL );
return this;
}
initProgram () {
this.program = this.gl.createProgram();
this.gl.attachShader( this.program, this.getShader( { type: 'fragment', codeSource: fshaderSource } ) );
this.gl.attachShader( this.program, this.getShader( { type: 'vertex', codeSource: vshaderSource } ) );
this.gl.linkProgram( this.program );
if ( !this.gl.getProgramParameter( this.program, this.gl.LINK_STATUS ) ) {
alert( `Could not initialise shader program, ${ gl.getProgramInfoLog( this.program ) }` );
}
this.gl.useProgram( this.program );
this.program.color = this.gl.getAttribLocation( this.program, 'color' );
this.program.normal = this.gl.getAttribLocation( this.program, 'normal' );
this.program.position = this.gl.getAttribLocation( this.program, 'position' );
this.program.modelViewMatrix = this.gl.getUniformLocation( this.program, 'modelViewMatrix' );
this.program.normalMatrix = this.gl.getUniformLocation( this.program, 'normalMatrix' );
this.program.object3dMatrix = this.gl.getUniformLocation( this.program, 'object3dMatrix' );
this.program.projectionMatrix = this.gl.getUniformLocation( this.program, 'projectionMatrix' );
this.program.uSampler = this.gl.getUniformLocation( this.program, 'uSampler' );
return this;
}
resize () {
this
.setSize()
.clear();
return this;
}
setSize ( { width = window.innerWidth, height = window.innerHeight } = {} ) {
this.canvas.width = width * this.dpr;
this.canvas.height = height * this.dpr;
this.setViewportSize();
Object.assign( this.canvas.style, {
width: `${ width }px`,
height: `${ height }px`,
} );
return this;
}
setupMatrices ( { object3d, camera } = {} ) {
switch ( camera.type ) {
case 'perspective':
camera.perspective( {
fov: camera.fov,
aspect: camera.aspect,
near: camera.near,
far: camera.far
} );
break;
case 'orthogonal':
camera.orthogonal( {
left: camera.left,
right: camera.right,
bottom: camera.bottom,
top: camera.top,
near: camera.near,
far: camera.far
} );
break;
default:
return;
}
camera.modelViewMatrix.identity();
camera.modelViewMatrix.translate( camera.modelViewMatrix.value, camera.position.value );
camera.modelViewMatrix.rotate( camera.modelViewMatrix.value, this.radian, camera.rotation.value );
object3d.matrix.identity();
object3d.matrix.translate( object3d.matrix.value, object3d.position.value );
object3d.matrix.rotateX( object3d.matrix.value, this.mouse.PHI );
object3d.matrix.rotateY( object3d.matrix.value, this.mouse.THETA );
this.mouse.render();
return this;
}
onAnimationFrame ( time = 0 ) {
if ( !this.isRendering ) return;
if ( oftype( this.animationFrame ) === 'function' ) {
this.animationFrame( { time } );
animationFrameId = window.requestAnimationFrame( this.onAnimationFrame );
}
}
start () {
if ( this.isRendering ) return;
if ( this.animationFrame === null ) return;
animationFrameId = window.requestAnimationFrame( this.onAnimationFrame );
this.isRendering = true;
}
stop () {
this.isRendering = false;
}
onrender ( f ) {
this.animationFrame = f;
this.start();
}
setViewportSize ( width = this.canvas.width, height = this.canvas.height ) {
this.gl.viewport( 0, 0, width, height );
return this;
}
}