Просмотр исходного кода

Add SSAAPlugin and example.

master
Palash Bansal 2 лет назад
Родитель
Сommit
a1ebb7ee1c
Аккаунт пользователя с таким Email не найден

+ 24
- 0
README.md Просмотреть файл

@@ -92,6 +92,7 @@ To make changes and run the example, click on the CodePen button on the top righ
- [TonemapPlugin](#tonemapplugin) - Add tonemap to the final screen pass
- [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
- [SSAAPlugin](#ssaaplugin) - Add Super Sample Anti-Aliasing by applying jitter to the camera.
- [DepthBufferPlugin](#depthbufferplugin) - Pre-rendering of depth buffer
- [NormalBufferPlugin](#normalbufferplugin) - Pre-rendering of normal buffer
- [GBufferPlugin](#gbufferplugin) - Pre-rendering of depth-normal and flags buffers in a single pass
@@ -2198,6 +2199,29 @@ Progressive Plugin adds a post-render pass to blend the last frame with the curr

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

[//]: # (todo: image)

+ 1
- 0
examples/index.html Просмотреть файл

@@ -323,6 +323,7 @@
<ul>
<li><a href="./progressive-plugin/">Progressive Plugin </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="./normal-buffer-plugin/">Normal Buffer Plugin </a></li>
<li><a href="./gbuffer-plugin/">GBuffer Plugin <br/>(NormalDepth+Flags) </a></li>

+ 9
- 1
examples/progressive-hdr-shadows-exp/script.ts Просмотреть файл

@@ -10,6 +10,7 @@ import {
ProgressivePlugin,
ShaderChunk,
shaderReplaceString,
SSAAPlugin,
ThreeViewer,
Vector3,
} from 'threepipe'
@@ -39,7 +40,14 @@ async function init() {
canvas: document.getElementById('mcanvas') as HTMLCanvasElement,
msaa: 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)

+ 10
- 2
examples/progressive-plugin/script.ts Просмотреть файл

@@ -9,17 +9,26 @@ import {
PlaneGeometry,
ProgressivePlugin,
RenderTargetPreviewPlugin,
SSAAPlugin,
ThreeViewer,
Vector3,
} from 'threepipe'
import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane'

async function init() {

const viewer = new ThreeViewer({
canvas: document.getElementById('mcanvas') as HTMLCanvasElement,
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))
const result = await viewer.load<IObject3D>('https://threejs.org/examples/models/fbx/Samba Dancing.fbx', {
@@ -60,7 +69,6 @@ async function init() {
const rt = viewer.addPluginSync(RenderTargetPreviewPlugin)
rt.addTarget(()=>directionalLight.shadow.map || undefined, 'shadow', true, true, true)

viewer.addPluginSync(new ProgressivePlugin((window as any).TESTING ? 20 : 200))
viewer.addEventListener('postFrame', ()=>{
if (viewer.renderManager.frameCount < 1) return


+ 36
- 0
examples/ssaa-plugin/index.html Просмотреть файл

@@ -0,0 +1,36 @@
<!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>

+ 58
- 0
examples/ssaa-plugin/script.ts Просмотреть файл

@@ -0,0 +1,58 @@
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)

+ 2
- 9
examples/ssao-plugin/script.ts Просмотреть файл

@@ -1,17 +1,10 @@
import {
_testFinish,
ProgressivePlugin,
RenderTargetPreviewPlugin,
SSAOPlugin,
ThreeViewer,
UnsignedByteType,
} from 'threepipe'
import {_testFinish, RenderTargetPreviewPlugin, SSAAPlugin, SSAOPlugin, ThreeViewer, UnsignedByteType} from 'threepipe'
import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane'

const viewer = new ThreeViewer({
canvas: document.getElementById('mcanvas') as HTMLCanvasElement,
msaa: true,
plugins: [ProgressivePlugin],
plugins: [SSAAPlugin],
tonemap: false,
})


+ 3
- 1
examples/tweakpane-editor/script.ts Просмотреть файл

@@ -34,6 +34,7 @@ import {
RenderTargetPreviewPlugin,
Rhino3dmLoadPlugin,
SceneUiConfigPlugin,
SSAAPlugin,
SSAOPlugin,
STLLoadPlugin,
ThreeFirstPersonControlsPlugin,
@@ -74,6 +75,7 @@ async function init() {

await viewer.addPlugins([
new ProgressivePlugin(),
new SSAAPlugin(),
GLTFAnimationPlugin,
PickingPlugin,
new TransformControlsPlugin(false),
@@ -126,7 +128,7 @@ async function init() {

editor.loadPlugins({
['Viewer']: [ViewerUiConfigPlugin, SceneUiConfigPlugin, DropzonePlugin, FullScreenPlugin, TweakpaneUiPlugin],
['Scene']: [ContactShadowGroundPlugin],
['Scene']: [SSAAPlugin, ContactShadowGroundPlugin],
['Interaction']: [HierarchyUiPlugin, TransformControlsPlugin, PickingPlugin, Object3DGeneratorPlugin, GeometryGeneratorPlugin, EditorViewWidgetPlugin, Object3DWidgetsPlugin, MeshOptSimplifyModifierPlugin],
['GBuffer']: [GBufferPlugin, DepthBufferPlugin, NormalBufferPlugin],
['Post-processing']: [TonemapPlugin, ProgressivePlugin, SSAOPlugin, FrameFadePlugin, VignettePlugin, ChromaticAberrationPlugin, FilmicGrainPlugin],

+ 2
- 1
examples/virtual-cameras-plugin/script.ts Просмотреть файл

@@ -5,6 +5,7 @@ import {
PerspectiveCamera2,
ProgressivePlugin,
RenderTargetPreviewPlugin,
SSAAPlugin,
ThreeViewer,
Vector3,
VirtualCamerasPlugin,
@@ -16,7 +17,7 @@ async function init() {
const viewer = new ThreeViewer({
canvas: document.getElementById('mcanvas') as HTMLCanvasElement,
debug: true,
plugins: [new ProgressivePlugin(16)],
plugins: [new ProgressivePlugin(16), SSAAPlugin],
})
const virtualCameras = viewer.addPluginSync(VirtualCamerasPlugin)


+ 1
- 0
src/plugins/index.ts Просмотреть файл

@@ -14,6 +14,7 @@ export type {ProgressivePluginEventTypes, ProgressivePluginTarget} from './pipel
export type {GBufferPluginEventTypes, GBufferPluginPass, GBufferUpdater, GBufferUpdaterContext} from './pipeline/GBufferPlugin'
export type {DepthBufferPluginEventTypes, DepthBufferPluginPass, DepthBufferPluginTarget} from './pipeline/DepthBufferPlugin'
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'

// ui

+ 23
- 3
src/plugins/pipeline/ProgressivePlugin.ts Просмотреть файл

@@ -1,12 +1,14 @@
import {IUniform, Texture, TextureDataType, UnsignedByteType, WebGLRenderTarget} from 'three'
import {IPassID, IPipelinePass} from '../../postprocessing'
import {ThreeViewer} from '../../viewer'
import {ISerializedConfig, ThreeViewer} from '../../viewer'
import {PipelinePassPlugin} from '../base/PipelinePassPlugin'
import {uiFolderContainer, uiImage, uiInput} from 'uiconfig.js'
import {ICamera, IRenderManager, IScene, IWebGLRenderer} from '../../core'
import {AddBlendTexturePass} from '../../postprocessing/AddBlendTexturePass'
import {getOrCall, serialize, ValOrFunc} from 'ts-browser-helpers'
import {IShaderPropertiesUpdater} from '../../materials'
import {SerializationMetaType} from '../../utils'
import {SSAAPlugin} from './SSAAPlugin'

export type ProgressivePluginEventTypes = ''
export type ProgressivePluginTarget = WebGLRenderTarget
@@ -24,6 +26,7 @@ export class ProgressivePlugin

readonly passId = 'progressive'
public static readonly PluginType = 'ProgressivePlugin'
public static readonly OldPluginType = 'Progressive'

/**
* Different targets for different render cameras.
@@ -33,7 +36,6 @@ export class ProgressivePlugin
protected _targets = new Map<string, ProgressivePluginTarget>()

@serialize() @uiInput('Frame count') maxFrameCount: number
// todo: deserialize jitter

// @uiImage('Last Texture' /* {readOnly: true}*/) texture?: Texture

@@ -58,13 +60,16 @@ export class ProgressivePlugin
return this._viewer ? this.getTarget(this._viewer.scene.mainCamera)?.texture : undefined
}

/**
* Note - this is not used right now
*/
// @onChange2(ProgressivePlugin.prototype._createTarget)
// @uiDropdown('Buffer Type', threeConstMappings.TextureDataType.uiConfig)
readonly bufferType: TextureDataType // cannot be changed after creation (for now)

constructor(
maxFrameCount = 32,
bufferType: TextureDataType = UnsignedByteType,
bufferType: TextureDataType = UnsignedByteType, // this is not used. todo use halffloat when rgbm = false
enabled = true,
) {
super()
@@ -154,6 +159,21 @@ export class ProgressivePlugin
})
}

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 {

+ 155
- 0
src/plugins/pipeline/SSAAPlugin.ts Просмотреть файл

@@ -0,0 +1,155 @@
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},
]
}



Загрузка…
Отмена
Сохранить