threepipe
Version:
A 3D viewer framework built on top of three.js in TypeScript with a focus on quality rendering, modularity and extensibility.
161 lines (137 loc) • 5.8 kB
text/typescript
import {OrthographicCamera, PerspectiveCamera} from 'three'
import {AViewerPluginSync, ThreeViewer} from '../../viewer'
import {uiFolderContainer, uiSlider, uiToggle} from 'uiconfig.js'
import {IEvent, onChange, serialize} from 'ts-browser-helpers'
import {ICamera, ILight} from '../../core'
import {ProgressivePlugin} from './ProgressivePlugin'
export type SSAAPluginEventTypes = ''
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
*/
export class SSAAPlugin extends AViewerPluginSync<SSAAPluginEventTypes> {
public static readonly PluginType = 'SSAAPlugin'
enabled = true
rendersPerFrame = 1
jitterRenderCamera = true
jitterLightCameras = true
private _hasSetOffsetRC = false
private _hasSetOffsetLC = false
public trackedJitterCameras = new Set<[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.scene.addEventListener('addSceneObject', this._addSceneObject)
}
onRemove(viewer: ThreeViewer): void {
viewer.removeEventListener('preRender', this._preRender)
viewer.removeEventListener('postRender', this._postRender)
viewer.scene.removeEventListener('addSceneObject', this._addSceneObject)
return super.onRemove(viewer)
}
setDirty() {
if (!this._viewer) return
this._viewer.rendersPerFrame = this.rendersPerFrame
this._viewer.setDirty()
this.uiConfig?.uiRefresh?.(true, 'postFrame')
}
private _addSceneObject = (event: IEvent<string>)=> {
event.object?.traverse((o: ILight)=>{
if (o && o.shadow && o.shadow.camera && o.shadow.mapSize) {
this.trackedJitterCameras.add([o.shadow.camera as TCamera, o.shadow.mapSize])
}
// if (o?.material) {
// if (o.material.alphaMap) console.log(o.material) //todo why?
// }
})
}
private _jitter(camera: TCamera, size: {
width: number,
height: number
}, frameCount: number) {
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)
// }
camera.setViewOffset(size.width, size.height, sample.x, sample.y, size.width, size.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)
if (this.jitterLightCameras)
this.trackedJitterCameras.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.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},
]
}