UNPKG

pex-renderer

Version:

Physically Based Renderer for Pex

229 lines (198 loc) 5.96 kB
const Signal = require('signals') const mat4 = require('pex-math/mat4') const vec3 = require('pex-math/vec3') function Camera(opts) { const gl = opts.ctx.gl this.type = 'Camera' this.projection = opts.projection || 'perspective' this.enabled = true this.changed = new Signal() this.near = 0.1 this.far = 100 this.aspect = 1 this.exposure = 1 this.viewMatrix = mat4.create() this.inverseViewMatrix = mat4.create() this.focalLength = 50 // mm this.fStop = 2.8 this.sensorSize = [36, 24] //mm this.actualSensorHeight = 24 //mm this.sensorFit = 'vertical' if (this.projection === 'perspective') { this.fov = Math.PI / 4 this.projectionMatrix = mat4.perspective( mat4.create(), this.fov, this.aspect, this.near, this.far ) } else if (this.projection === 'orthographic') { this.left = -1 this.right = 1 this.bottom = -1 this.top = 1 this.zoom = 1 this.projectionMatrix = mat4.ortho( mat4.create(), this.left, this.right, this.bottom, this.top, this.near, this.far ) } this.viewport = [0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight] this.view = null this.set(opts) } Camera.prototype.init = function(entity) { this.entity = entity } Camera.prototype.set = function(opts) { Object.assign(this, opts) if (opts.viewport) { this.aspect = this.viewport[2] / this.viewport[3] const postProcessingCmp = this.entity && this.entity.getComponent('PostProcessing') if (postProcessingCmp) { postProcessingCmp.set({ viewport: this.viewport, viewMatrix: this.viewMatrix }) } } // calculate new fov based on sensor size and focal length if ( opts.sensorSize || opts.sensorFit || opts.focalLength || opts.viewport || opts.fov ) { let sensorWidth = this.sensorSize[0] let sensorHeight = this.sensorSize[1] const sensorAspectRatio = sensorWidth / sensorHeight if (this.aspect > sensorAspectRatio) { if (this.sensorFit === 'horizontal' || this.sensorFit === 'fill') { sensorHeight = sensorWidth / this.aspect } } else { //this.aspect <= sensorAspectRatio if (this.sensorFit === 'horizontal' || this.sensorFit === 'overscan') { sensorHeight = sensorWidth / this.aspect } } if (opts.fov) { this.focalLength = sensorHeight / 2 / Math.tan(this.fov / 2) } else { this.fov = 2 * Math.atan(sensorHeight / 2 / this.focalLength) } this.actualSensorHeight = sensorHeight } if ( this.projection === 'perspective' && (opts.aspect || opts.near || opts.far || opts.fov || opts.view || opts.viewport || opts.sensorSize || opts.sensorFit || opts.focalLength) ) { if (this.view) { const aspectRatio = this.view.totalSize[0] / this.view.totalSize[1] const top = Math.tan(this.fov * 0.5) * this.near const bottom = -top const left = aspectRatio * bottom const right = aspectRatio * top const width = Math.abs(right - left) const height = Math.abs(top - bottom) const widthNormalized = width / this.view.totalSize[0] const heightNormalized = height / this.view.totalSize[1] const l = left + this.view.offset[0] * widthNormalized const r = left + (this.view.offset[0] + this.view.size[0]) * widthNormalized const b = top - (this.view.offset[1] + this.view.size[1]) * heightNormalized const t = top - this.view.offset[1] * heightNormalized mat4.frustum(this.projectionMatrix, l, r, b, t, this.near, this.far) } else { mat4.perspective( this.projectionMatrix, this.fov, this.aspect, this.near, this.far ) } } else if ( this.projection === 'orthographic' && (opts.left || opts.right || opts.bottom || opts.top || opts.zoom || opts.near || opts.far || opts.view) ) { const dx = (this.right - this.left) / (2 / this.zoom) const dy = (this.top - this.bottom) / (2 / this.zoom) const cx = (this.right + this.left) / 2 const cy = (this.top + this.bottom) / 2 let left = cx - dx let right = cx + dx let top = cy + dy let bottom = cy - dy if (this.view) { const zoomW = 1 / this.zoom / (this.view.size[0] / this.view.totalSize[0]) const zoomH = 1 / this.zoom / (this.view.size[1] / this.view.totalSize[1]) const scaleW = (this.right - this.left) / this.view.size[0] const scaleH = (this.top - this.bottom) / this.view.size[1] left += scaleW * (this.view.offset[0] / zoomW) right = left + scaleW * (this.view.size[0] / zoomW) top -= scaleH * (this.view.offset[1] / zoomH) bottom = top - scaleH * (this.view.size[1] / zoomH) } mat4.ortho( this.projectionMatrix, left, right, bottom, top, this.near, this.far ) } Object.keys(opts).forEach((prop) => this.changed.dispatch(prop)) } Camera.prototype.getViewRay = function(x, y, windowWidth, windowHeight) { if (this.view) { x += this.view.offset[0] y += this.view.offset[1] windowWidth = this.view.totalSize[0] windowHeight = this.view.totalSize[1] } let nx = (2 * x) / windowWidth - 1 let ny = 1 - (2 * y) / windowHeight let hNear = 2 * Math.tan(this.fov / 2) * this.near let wNear = hNear * this.aspect nx *= wNear * 0.5 ny *= hNear * 0.5 let origin = [0, 0, 0] let direction = vec3.normalize([nx, ny, -this.near]) let ray = [origin, direction] return ray } Camera.prototype.update = function() { mat4.set(this.inverseViewMatrix, this.entity.transform.modelMatrix) mat4.set(this.viewMatrix, this.entity.transform.modelMatrix) mat4.invert(this.viewMatrix) } module.exports = function createCamera(opts) { return new Camera(opts) }