| - [TonemapPlugin](#tonemapplugin) - Add tonemap to the final screen pass | - [TonemapPlugin](#tonemapplugin) - Add tonemap to the final screen pass | ||||
| - [DropzonePlugin](#dropzoneplugin) - Drag and drop local files to import and load | - [DropzonePlugin](#dropzoneplugin) - Drag and drop local files to import and load | ||||
| - [ProgressivePlugin](#progressiveplugin) - Post-render pass to blend the last frame with the current frame | - [ProgressivePlugin](#progressiveplugin) - Post-render pass to blend the last frame with the current frame | ||||
| - [SSAAPlugin](#ssaaplugin) - Add Super Sample Anti-Aliasing by applying jitter to the camera. | |||||
| - [DepthBufferPlugin](#depthbufferplugin) - Pre-rendering of depth buffer | - [DepthBufferPlugin](#depthbufferplugin) - Pre-rendering of depth buffer | ||||
| - [NormalBufferPlugin](#normalbufferplugin) - Pre-rendering of normal buffer | - [NormalBufferPlugin](#normalbufferplugin) - Pre-rendering of normal buffer | ||||
| - [GBufferPlugin](#gbufferplugin) - Pre-rendering of depth-normal and flags buffers in a single pass | - [GBufferPlugin](#gbufferplugin) - Pre-rendering of depth-normal and flags buffers in a single pass | ||||
| This is used as a dependency in other plugins for progressive rendering effect which is useful for progressive shadows, gi, denoising, baking, anti-aliasing, and many other effects. The helper function `convergedPromise` returns a new promise that can be used to wait for the progressive rendering to converge. | This is used as a dependency in other plugins for progressive rendering effect which is useful for progressive shadows, gi, denoising, baking, anti-aliasing, and many other effects. The helper function `convergedPromise` returns a new promise that can be used to wait for the progressive rendering to converge. | ||||
| ## SSAAPlugin | |||||
| [//]: # (todo: image) | |||||
| [Example](https://threepipe.org/examples/#ssaa-plugin/) — | |||||
| [Source Code](./src/plugins/pipeline/SSAAPlugin.ts) — | |||||
| [API Reference](https://threepipe.org/docs/classes/SSAAPlugin.html) | |||||
| SSAA Plugin adds support for [Super Sampling Anti-Aliasing](https://en.wikipedia.org/wiki/Supersampling) to the viewer. Simply add the plugin to the viewer to use it. | |||||
| It jitters the camera view offset over multiple frames, which are then blended by the [ProgressivePlugin](#progressiveplugin) to create a higher quality image. This is useful for reducing aliasing artifacts in the scene. | |||||
| By default, the pipeline only renders once per request animation frame. So we don't get any anti-aliasing while moving. For that, either use the TAA(Temporal Anti-aliasing) plugin or for the case of simple scenes - render multiple times per frame which can be done by setting `plugin.rendersPerFrame` or `viewer.rendersPerFrame`. Check out the [example](https://threepipe.org/examples/#ssaa-plugin/) to see the effect on frame rate. | |||||
| ```typescript | |||||
| const ssaa = viewer.addPluginSync(new SSAAPlugin()) | |||||
| ssaa.enabled = true // toggle jittering(if you want to set custom view offset) | |||||
| ssaa.rendersPerFrame = 4 // render 4 times per frame (max 32 is useful) | |||||
| ``` | |||||
| ## DepthBufferPlugin | ## DepthBufferPlugin | ||||
| [//]: # (todo: image) | [//]: # (todo: image) |
| <ul> | <ul> | ||||
| <li><a href="./progressive-plugin/">Progressive Plugin </a></li> | <li><a href="./progressive-plugin/">Progressive Plugin </a></li> | ||||
| <li><a href="./custom-pipeline/">Custom Pipeline specification </a></li> | <li><a href="./custom-pipeline/">Custom Pipeline specification </a></li> | ||||
| <li><a href="./ssaa-plugin/">SSAA Plugin </a></li> | |||||
| <li><a href="./depth-buffer-plugin/">Depth Buffer Plugin </a></li> | <li><a href="./depth-buffer-plugin/">Depth Buffer Plugin </a></li> | ||||
| <li><a href="./normal-buffer-plugin/">Normal Buffer Plugin </a></li> | <li><a href="./normal-buffer-plugin/">Normal Buffer Plugin </a></li> | ||||
| <li><a href="./gbuffer-plugin/">GBuffer Plugin <br/>(NormalDepth+Flags) </a></li> | <li><a href="./gbuffer-plugin/">GBuffer Plugin <br/>(NormalDepth+Flags) </a></li> |
| ProgressivePlugin, | ProgressivePlugin, | ||||
| ShaderChunk, | ShaderChunk, | ||||
| shaderReplaceString, | shaderReplaceString, | ||||
| SSAAPlugin, | |||||
| ThreeViewer, | ThreeViewer, | ||||
| Vector3, | Vector3, | ||||
| } from 'threepipe' | } from 'threepipe' | ||||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | ||||
| msaa: false, | msaa: false, | ||||
| rgbm: false, | rgbm: false, | ||||
| plugins: [new ProgressivePlugin((window as any).TESTING ? 20 : 200)], | |||||
| plugins: [new ProgressivePlugin((window as any).TESTING ? 20 : 200), SSAAPlugin], | |||||
| dropzone: { | |||||
| addOptions: { | |||||
| disposeSceneObjects: true, | |||||
| autoSetEnvironment: true, | |||||
| autoSetBackground: true, | |||||
| }, | |||||
| }, | |||||
| }) | }) | ||||
| const directionalLight = createDirLight(viewer) | const directionalLight = createDirLight(viewer) |
| PlaneGeometry, | PlaneGeometry, | ||||
| ProgressivePlugin, | ProgressivePlugin, | ||||
| RenderTargetPreviewPlugin, | RenderTargetPreviewPlugin, | ||||
| SSAAPlugin, | |||||
| ThreeViewer, | ThreeViewer, | ||||
| Vector3, | Vector3, | ||||
| } from 'threepipe' | } from 'threepipe' | ||||
| import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | ||||
| async function init() { | async function init() { | ||||
| const viewer = new ThreeViewer({ | const viewer = new ThreeViewer({ | ||||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | ||||
| msaa: true, | msaa: true, | ||||
| rgbm: false, | |||||
| dropzone: { | |||||
| addOptions: { | |||||
| disposeSceneObjects: true, | |||||
| importConfig: true, | |||||
| }, | |||||
| }, | |||||
| }) | }) | ||||
| viewer.addPluginSync(new ProgressivePlugin((window as any).TESTING ? 20 : 200)) | |||||
| viewer.addPluginSync(new SSAAPlugin()) | |||||
| // viewer.scene.addObject(new HemisphereLight(0xffffff, 0x444444, 10)) | // viewer.scene.addObject(new HemisphereLight(0xffffff, 0x444444, 10)) | ||||
| const result = await viewer.load<IObject3D>('https://threejs.org/examples/models/fbx/Samba Dancing.fbx', { | const result = await viewer.load<IObject3D>('https://threejs.org/examples/models/fbx/Samba Dancing.fbx', { | ||||
| const rt = viewer.addPluginSync(RenderTargetPreviewPlugin) | const rt = viewer.addPluginSync(RenderTargetPreviewPlugin) | ||||
| rt.addTarget(()=>directionalLight.shadow.map || undefined, 'shadow', true, true, true) | rt.addTarget(()=>directionalLight.shadow.map || undefined, 'shadow', true, true, true) | ||||
| viewer.addPluginSync(new ProgressivePlugin((window as any).TESTING ? 20 : 200)) | |||||
| viewer.addEventListener('postFrame', ()=>{ | viewer.addEventListener('postFrame', ()=>{ | ||||
| if (viewer.renderManager.frameCount < 1) return | if (viewer.renderManager.frameCount < 1) return | ||||
| <!DOCTYPE html> | |||||
| <html lang="en"> | |||||
| <head> | |||||
| <meta charset="UTF-8"> | |||||
| <title>SSAA Plugin</title> | |||||
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |||||
| <!-- Import maps polyfill --> | |||||
| <!-- Remove this when import maps will be widely supported --> | |||||
| <script async src="https://unpkg.com/es-module-shims@1.6.3/dist/es-module-shims.js"></script> | |||||
| <script type="importmap"> | |||||
| { | |||||
| "imports": { | |||||
| "threepipe": "./../../dist/index.mjs", | |||||
| "@threepipe/plugin-tweakpane": "./../../plugins/tweakpane/dist/index.mjs" | |||||
| } | |||||
| } | |||||
| </script> | |||||
| <style id="example-style"> | |||||
| html, body, #canvas-container, #mcanvas { | |||||
| width: 100%; | |||||
| height: 100%; | |||||
| margin: 0; | |||||
| overflow: hidden; | |||||
| } | |||||
| </style> | |||||
| <script type="module" src="../examples-utils/simple-code-preview.mjs"></script> | |||||
| <script id="example-script" type="module" src="./script.js" data-scripts="./script.ts;./script.js"></script> | |||||
| </head> | |||||
| <body> | |||||
| <div id="canvas-container"> | |||||
| <canvas id="mcanvas"></canvas> | |||||
| </div> | |||||
| </body> |
| import {_testFinish, IObject3D, PhysicalMaterial, ProgressivePlugin, SSAAPlugin, ThreeViewer} from 'threepipe' | |||||
| import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | |||||
| async function init() { | |||||
| const viewer = new ThreeViewer({ | |||||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | |||||
| debug: true, | |||||
| msaa: false, | |||||
| rgbm: true, | |||||
| renderScale: 1, | |||||
| dropzone: { | |||||
| addOptions: { | |||||
| disposeSceneObjects: true, | |||||
| importConfig: true, | |||||
| }, | |||||
| }, | |||||
| }) | |||||
| viewer.addPluginSync(new SSAAPlugin()) | |||||
| await Promise.all([ | |||||
| viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr'), | |||||
| // viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/IridescenceLamp.glb', { | |||||
| // autoCenter: true, | |||||
| // autoScale: true, | |||||
| // }), | |||||
| viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/LittlestTokyo.glb', { | |||||
| autoCenter: true, | |||||
| autoScale: true, | |||||
| }), | |||||
| ]) | |||||
| viewer.scene.overrideMaterial = new PhysicalMaterial({ | |||||
| color: 'white', | |||||
| roughness: 1, | |||||
| metalness: 0, | |||||
| wireframe: true, | |||||
| }) | |||||
| viewer.scene.mainCamera.position.set(0, 0, 3.5) | |||||
| const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true)) | |||||
| ui.appendChild({ | |||||
| type: 'toggle', | |||||
| label: 'Auto Rotate', | |||||
| property: [viewer.scene.mainCamera.controls, 'autoRotate'], | |||||
| }) | |||||
| ui.setupPluginUi(SSAAPlugin, { | |||||
| expanded: true, | |||||
| }) | |||||
| ui.setupPlugins(ProgressivePlugin) | |||||
| await viewer.getPlugin(ProgressivePlugin)?.convergedPromise | |||||
| console.log('converged') | |||||
| } | |||||
| init().finally(_testFinish) |
| import { | |||||
| _testFinish, | |||||
| ProgressivePlugin, | |||||
| RenderTargetPreviewPlugin, | |||||
| SSAOPlugin, | |||||
| ThreeViewer, | |||||
| UnsignedByteType, | |||||
| } from 'threepipe' | |||||
| import {_testFinish, RenderTargetPreviewPlugin, SSAAPlugin, SSAOPlugin, ThreeViewer, UnsignedByteType} from 'threepipe' | |||||
| import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane' | ||||
| const viewer = new ThreeViewer({ | const viewer = new ThreeViewer({ | ||||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | ||||
| msaa: true, | msaa: true, | ||||
| plugins: [ProgressivePlugin], | |||||
| plugins: [SSAAPlugin], | |||||
| tonemap: false, | tonemap: false, | ||||
| }) | }) | ||||
| RenderTargetPreviewPlugin, | RenderTargetPreviewPlugin, | ||||
| Rhino3dmLoadPlugin, | Rhino3dmLoadPlugin, | ||||
| SceneUiConfigPlugin, | SceneUiConfigPlugin, | ||||
| SSAAPlugin, | |||||
| SSAOPlugin, | SSAOPlugin, | ||||
| STLLoadPlugin, | STLLoadPlugin, | ||||
| ThreeFirstPersonControlsPlugin, | ThreeFirstPersonControlsPlugin, | ||||
| await viewer.addPlugins([ | await viewer.addPlugins([ | ||||
| new ProgressivePlugin(), | new ProgressivePlugin(), | ||||
| new SSAAPlugin(), | |||||
| GLTFAnimationPlugin, | GLTFAnimationPlugin, | ||||
| PickingPlugin, | PickingPlugin, | ||||
| new TransformControlsPlugin(false), | new TransformControlsPlugin(false), | ||||
| editor.loadPlugins({ | editor.loadPlugins({ | ||||
| ['Viewer']: [ViewerUiConfigPlugin, SceneUiConfigPlugin, DropzonePlugin, FullScreenPlugin, TweakpaneUiPlugin], | ['Viewer']: [ViewerUiConfigPlugin, SceneUiConfigPlugin, DropzonePlugin, FullScreenPlugin, TweakpaneUiPlugin], | ||||
| ['Scene']: [ContactShadowGroundPlugin], | |||||
| ['Scene']: [SSAAPlugin, ContactShadowGroundPlugin], | |||||
| ['Interaction']: [HierarchyUiPlugin, TransformControlsPlugin, PickingPlugin, Object3DGeneratorPlugin, GeometryGeneratorPlugin, EditorViewWidgetPlugin, Object3DWidgetsPlugin, MeshOptSimplifyModifierPlugin], | ['Interaction']: [HierarchyUiPlugin, TransformControlsPlugin, PickingPlugin, Object3DGeneratorPlugin, GeometryGeneratorPlugin, EditorViewWidgetPlugin, Object3DWidgetsPlugin, MeshOptSimplifyModifierPlugin], | ||||
| ['GBuffer']: [GBufferPlugin, DepthBufferPlugin, NormalBufferPlugin], | ['GBuffer']: [GBufferPlugin, DepthBufferPlugin, NormalBufferPlugin], | ||||
| ['Post-processing']: [TonemapPlugin, ProgressivePlugin, SSAOPlugin, FrameFadePlugin, VignettePlugin, ChromaticAberrationPlugin, FilmicGrainPlugin], | ['Post-processing']: [TonemapPlugin, ProgressivePlugin, SSAOPlugin, FrameFadePlugin, VignettePlugin, ChromaticAberrationPlugin, FilmicGrainPlugin], |
| PerspectiveCamera2, | PerspectiveCamera2, | ||||
| ProgressivePlugin, | ProgressivePlugin, | ||||
| RenderTargetPreviewPlugin, | RenderTargetPreviewPlugin, | ||||
| SSAAPlugin, | |||||
| ThreeViewer, | ThreeViewer, | ||||
| Vector3, | Vector3, | ||||
| VirtualCamerasPlugin, | VirtualCamerasPlugin, | ||||
| const viewer = new ThreeViewer({ | const viewer = new ThreeViewer({ | ||||
| canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | canvas: document.getElementById('mcanvas') as HTMLCanvasElement, | ||||
| debug: true, | debug: true, | ||||
| plugins: [new ProgressivePlugin(16)], | |||||
| plugins: [new ProgressivePlugin(16), SSAAPlugin], | |||||
| }) | }) | ||||
| const virtualCameras = viewer.addPluginSync(VirtualCamerasPlugin) | const virtualCameras = viewer.addPluginSync(VirtualCamerasPlugin) | ||||
| export type {GBufferPluginEventTypes, GBufferPluginPass, GBufferUpdater, GBufferUpdaterContext} from './pipeline/GBufferPlugin' | export type {GBufferPluginEventTypes, GBufferPluginPass, GBufferUpdater, GBufferUpdaterContext} from './pipeline/GBufferPlugin' | ||||
| export type {DepthBufferPluginEventTypes, DepthBufferPluginPass, DepthBufferPluginTarget} from './pipeline/DepthBufferPlugin' | export type {DepthBufferPluginEventTypes, DepthBufferPluginPass, DepthBufferPluginTarget} from './pipeline/DepthBufferPlugin' | ||||
| export type {NormalBufferPluginEventTypes, NormalBufferPluginPass, NormalBufferPluginTarget} from './pipeline/NormalBufferPlugin' | export type {NormalBufferPluginEventTypes, NormalBufferPluginPass, NormalBufferPluginTarget} from './pipeline/NormalBufferPlugin' | ||||
| export {SSAAPlugin, type SSAAPluginEventTypes} from './pipeline/SSAAPlugin' | |||||
| export {SSAOPlugin, SSAOPluginPass, type SSAOPluginEventTypes, type SSAOPluginTarget} from './pipeline/SSAOPlugin' | export {SSAOPlugin, SSAOPluginPass, type SSAOPluginEventTypes, type SSAOPluginTarget} from './pipeline/SSAOPlugin' | ||||
| // ui | // ui |
| import {IUniform, Texture, TextureDataType, UnsignedByteType, WebGLRenderTarget} from 'three' | import {IUniform, Texture, TextureDataType, UnsignedByteType, WebGLRenderTarget} from 'three' | ||||
| import {IPassID, IPipelinePass} from '../../postprocessing' | import {IPassID, IPipelinePass} from '../../postprocessing' | ||||
| import {ThreeViewer} from '../../viewer' | |||||
| import {ISerializedConfig, ThreeViewer} from '../../viewer' | |||||
| import {PipelinePassPlugin} from '../base/PipelinePassPlugin' | import {PipelinePassPlugin} from '../base/PipelinePassPlugin' | ||||
| import {uiFolderContainer, uiImage, uiInput} from 'uiconfig.js' | import {uiFolderContainer, uiImage, uiInput} from 'uiconfig.js' | ||||
| import {ICamera, IRenderManager, IScene, IWebGLRenderer} from '../../core' | import {ICamera, IRenderManager, IScene, IWebGLRenderer} from '../../core' | ||||
| import {AddBlendTexturePass} from '../../postprocessing/AddBlendTexturePass' | import {AddBlendTexturePass} from '../../postprocessing/AddBlendTexturePass' | ||||
| import {getOrCall, serialize, ValOrFunc} from 'ts-browser-helpers' | import {getOrCall, serialize, ValOrFunc} from 'ts-browser-helpers' | ||||
| import {IShaderPropertiesUpdater} from '../../materials' | import {IShaderPropertiesUpdater} from '../../materials' | ||||
| import {SerializationMetaType} from '../../utils' | |||||
| import {SSAAPlugin} from './SSAAPlugin' | |||||
| export type ProgressivePluginEventTypes = '' | export type ProgressivePluginEventTypes = '' | ||||
| export type ProgressivePluginTarget = WebGLRenderTarget | export type ProgressivePluginTarget = WebGLRenderTarget | ||||
| readonly passId = 'progressive' | readonly passId = 'progressive' | ||||
| public static readonly PluginType = 'ProgressivePlugin' | public static readonly PluginType = 'ProgressivePlugin' | ||||
| public static readonly OldPluginType = 'Progressive' | |||||
| /** | /** | ||||
| * Different targets for different render cameras. | * Different targets for different render cameras. | ||||
| protected _targets = new Map<string, ProgressivePluginTarget>() | protected _targets = new Map<string, ProgressivePluginTarget>() | ||||
| @serialize() @uiInput('Frame count') maxFrameCount: number | @serialize() @uiInput('Frame count') maxFrameCount: number | ||||
| // todo: deserialize jitter | |||||
| // @uiImage('Last Texture' /* {readOnly: true}*/) texture?: Texture | // @uiImage('Last Texture' /* {readOnly: true}*/) texture?: Texture | ||||
| return this._viewer ? this.getTarget(this._viewer.scene.mainCamera)?.texture : undefined | return this._viewer ? this.getTarget(this._viewer.scene.mainCamera)?.texture : undefined | ||||
| } | } | ||||
| /** | |||||
| * Note - this is not used right now | |||||
| */ | |||||
| // @onChange2(ProgressivePlugin.prototype._createTarget) | // @onChange2(ProgressivePlugin.prototype._createTarget) | ||||
| // @uiDropdown('Buffer Type', threeConstMappings.TextureDataType.uiConfig) | // @uiDropdown('Buffer Type', threeConstMappings.TextureDataType.uiConfig) | ||||
| readonly bufferType: TextureDataType // cannot be changed after creation (for now) | readonly bufferType: TextureDataType // cannot be changed after creation (for now) | ||||
| constructor( | constructor( | ||||
| maxFrameCount = 32, | maxFrameCount = 32, | ||||
| bufferType: TextureDataType = UnsignedByteType, | |||||
| bufferType: TextureDataType = UnsignedByteType, // this is not used. todo use halffloat when rgbm = false | |||||
| enabled = true, | enabled = true, | ||||
| ) { | ) { | ||||
| super() | super() | ||||
| }) | }) | ||||
| } | } | ||||
| fromJSON(data: ISerializedConfig&{pass?: any}, meta?: SerializationMetaType): this|null|Promise<this|null> { | |||||
| console.log(data) | |||||
| if (data.jitter !== undefined) { | |||||
| const ssaa = this._viewer?.getPlugin(SSAAPlugin) | |||||
| if (!ssaa) { | |||||
| console.warn('Loading old webgi v0 file, add SSAAPlugin to get anti-aliasing') | |||||
| } else { | |||||
| data = {...data} | |||||
| ssaa.enabled = data.jitter | |||||
| delete data.jitter | |||||
| } | |||||
| } | |||||
| return super.fromJSON(data, meta) | |||||
| } | |||||
| } | } | ||||
| class ProgressiveBlendPass extends AddBlendTexturePass implements IPipelinePass { | class ProgressiveBlendPass extends AddBlendTexturePass implements IPipelinePass { |
| 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 | |||||
| */ | |||||
| @uiFolderContainer('SSAA Plugin') | |||||
| export class SSAAPlugin extends AViewerPluginSync<SSAAPluginEventTypes> { | |||||
| 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 Set<[TCamera, {width: number, height: number}]>() // todo register other cameras and light shadows cameras when added to the scene and changed. | |||||
| dependencies = [ProgressivePlugin] | |||||
| 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}, | |||||
| ] | |||||
| } | |||||