UNPKG

threepipe

Version:

A modern 3D viewer framework built on top of three.js, written in TypeScript, designed to make creating high-quality, modular, and extensible 3D experiences on the web simple and enjoyable.

171 lines (146 loc) 6.4 kB
import {OrthographicCamera, PerspectiveCamera} from 'three' import {AViewerPluginSync, ThreeViewer} from '../../viewer' import {uiFolderContainer, uiSlider, uiToggle} from 'uiconfig.js' import {onChange, serialize} from 'ts-browser-helpers' import {ICamera, ILight, IObject3D} from '../../core' import {ProgressivePlugin} from './ProgressivePlugin' export type TCamera = ICamera & (PerspectiveCamera|OrthographicCamera) /** * SSAA Plugin * * Jitters the render camera and optionally other cameras in the scene * to create a super-sampled anti-aliasing effect. * This is done across multiple frames by integrating with the ProgressivePlugin * @category Plugins */ @uiFolderContainer('SSAA Plugin') export class SSAAPlugin extends AViewerPluginSync { public static readonly PluginType = 'SSAAPlugin' @serialize() @uiToggle('Enabled') @onChange(SSAAPlugin.prototype.setDirty) enabled = true @serialize() @uiSlider('Renders/Frame', [1, 32], 1) @onChange(SSAAPlugin.prototype.setDirty) rendersPerFrame = 1 @serialize() @uiToggle('Render Camera') @onChange(SSAAPlugin.prototype.setDirty) jitterRenderCamera = true @serialize() @uiToggle('Light Cameras') @onChange(SSAAPlugin.prototype.setDirty) jitterLightCameras = true private _hasSetOffsetRC = false private _hasSetOffsetLC = false public trackedJitterCameras = new Map<TCamera, {width: number, height: number}>() // todo register other cameras and light shadows cameras when added to the scene and changed. dependencies = [ProgressivePlugin] constructor(rendersPerFrame = 1) { super() this.rendersPerFrame = rendersPerFrame } onAdded(viewer: ThreeViewer) { super.onAdded(viewer) viewer.addEventListener('preRender', this._preRender) viewer.addEventListener('postRender', this._postRender) viewer.object3dManager.getObjects().forEach(object=>this._objectAdd({object})) viewer.object3dManager.addEventListener('objectAdd', this._objectAdd) viewer.object3dManager.addEventListener('objectRemove', this._objectRemove) } onRemove(viewer: ThreeViewer): void { viewer.removeEventListener('preRender', this._preRender) viewer.removeEventListener('postRender', this._postRender) viewer.object3dManager.removeEventListener('objectAdd', this._objectAdd) viewer.object3dManager.removeEventListener('objectRemove', this._objectRemove) viewer.object3dManager.getObjects().forEach(object=>this._objectRemove({object})) return super.onRemove(viewer) } setDirty() { if (!this._viewer) return this._viewer.rendersPerFrame = this.rendersPerFrame this._viewer.setDirty() this.uiConfig?.uiRefresh?.(true, 'postFrame') } private _objectAdd = (e: {object?: IObject3D})=>{ const obj = e.object as ILight if (obj && obj.shadow && obj.shadow.camera && obj.shadow.mapSize) { this.trackedJitterCameras.set(obj.shadow.camera as TCamera, obj.shadow.mapSize) } } private _objectRemove = (e: {object?: IObject3D})=>{ const obj = e.object as ILight if (obj && obj.shadow && obj.shadow.camera) { const camera = obj.shadow.camera as TCamera this._clearJitter(camera) this.trackedJitterCameras.delete(camera) } } private _jitter(camera: TCamera, size: { width: number, height: number }, frameCount: number, main = false) { if (camera.userData.disableJitter) return if (camera.userData.__jittered) { this._viewer?.console.warn('SSAAPlugin: Camera already jittered') return } const sample = {...this.jitterOffsets[frameCount % this.jitterOffsets.length]} // const sample = {...offsets[Math.floor(Math.random() * (offsets.length - 0.001))]} // { // sample.x += 1 * (Math.random() - 0.5) // sample.y += 1 * (Math.random() - 0.5) // } const aspect = camera.aspect const height = camera.autoAspect || !main ? size.height : size.width / aspect camera.setViewOffset(size.width, height, sample.x, sample.y, size.width, height) camera.userData.__jittered = true } private _clearJitter(camera: TCamera) { if (!camera.userData.__jittered) return camera.clearViewOffset() delete camera.userData.__jittered } private _preRender = ()=> { const v = this._viewer if (!v || !this.enabled || v.renderManager.frameCount <= 1) return this.rendersPerFrame = v.rendersPerFrame // just to sync. todo: should this be here?. ideally there should be a event fired from the viewer when the prop changes const cam = v.scene.renderCamera as TCamera if (this.jitterRenderCamera) this._jitter(cam, { width: v.renderManager.renderSize.x * v.renderManager.renderScale, height: v.renderManager.renderSize.y * v.renderManager.renderScale, }, v.renderManager.frameCount, true) if (this.jitterLightCameras) this.trackedJitterCameras.entries().forEach((a) => this._jitter(...a, v.renderManager.frameCount)) this._hasSetOffsetRC = this.jitterRenderCamera this._hasSetOffsetLC = this.jitterLightCameras v.renderManager.resetShadows() } private _postRender = ()=> { const v = this._viewer if (!v) return if (this._hasSetOffsetRC) { this._clearJitter(v.scene.renderCamera as TCamera) this._hasSetOffsetRC = false } if (this._hasSetOffsetLC) { this.trackedJitterCameras.keys().forEach((camera) => this._clearJitter(camera)) this._hasSetOffsetLC = false } } jitterOffsets = [ {x: 0, y: 0}, {x: -0.5, y: 0}, {x: -0.375, y: -0.25}, {x: -0.1875, y: -0.125}, {x: -0.125, y: -0.375}, {x: 0.0625, y: -0.0625}, {x: 0.125, y: -0.3125}, {x: 0.375, y: -0.4375}, {x: 0.3125, y: -0.1875}, {x: 0.25, y: 0.0625}, {x: 0.4375, y: 0.25}, {x: 0.1875, y: 0.3125}, {x: 0, y: 0.4375}, {x: -0.0625, y: 0.1875}, {x: -0.25, y: 0.375}, {x: -0.4375, y: 0.5}, {x: -0.3125, y: 0.125}, ] }