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

Add TonemapPlugin and its example.

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

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

@@ -213,7 +213,7 @@
<div class="sidebar" data-selected-example="GLTF Load">
<button class="hamburger"> &#9776;</button>
<h1><a href="https://github.com/repalash/threepipe">ThreePipe</a> Examples</h1>
<h2 class="category">Import/Export</h2>
<h2 class="category">Import</h2>
<ul>
<li><a href="./fbx-load/">FBX Load </a></li>
<li><a href="./obj-mtl-load/">OBJ MTL Load </a></li>
@@ -222,11 +222,18 @@
<li><a href="./drc-load/">DRACO(DRC) Load </a></li>
<li><a href="./hdr-load/">HDR Load </a></li>
<li><a href="./exr-load/">EXR Load </a></li>
</ul>
<h2 class="category">Export</h2>
<ul>
<li><a href="./image-snapshot-export/">PNG, JPEG, WEBP Export<br/>(Image Snapshot) </a></li>
<li><a href="./render-target-export/">EXR, PNG, JPEG, WEBP Export<br/>(Render Target Export) </a></li>
<li><a href="./glb-export/">GLB Export </a></li>
<li><a href="./pmat-material-export/">PMAT Material export </a></li>
</ul>
<h2 class="category">Post-Processing</h2>
<ul>
<li><a href="./tonemap-plugin/">Tonemap Plugin </a></li>
</ul>
<h2 class="category">Rendering</h2>
<ul>
<li><a href="./depth-buffer-plugin/">Depth Buffer Plugin </a></li>

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

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Tonemap Plugin</title>
<!-- 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>

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

@@ -0,0 +1,22 @@
import {_testFinish, DepthBufferPlugin, IObject3D, ThreeViewer, TonemapPlugin, UnsignedByteType} from 'threepipe'
import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane'

async function init() {

const viewer = new ThreeViewer({
canvas: document.getElementById('mcanvas') as HTMLCanvasElement,
})

// A GBuffer(depth buffer here) is required for the `tonemapBackground` flag in TonemapPlugin to work
viewer.addPluginSync(new DepthBufferPlugin(UnsignedByteType, true))
viewer.addPluginSync(new TonemapPlugin())

await viewer.setEnvironmentMap('https://threejs.org/examples/textures/equirectangular/venice_sunset_1k.hdr')
await viewer.load<IObject3D>('https://threejs.org/examples/models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf')

const ui = viewer.addPluginSync(new TweakpaneUiPlugin(true))
ui.setupPluginUi(TonemapPlugin)

}

init().then(_testFinish)

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

@@ -1,6 +1,5 @@
import {
_testFinish,
AViewerPluginSync,
DepthBufferPlugin,
DropzonePlugin,
FullScreenPlugin,
@@ -8,27 +7,20 @@ import {
IObject3D,
NormalBufferPlugin,
RenderTargetPreviewPlugin,
SceneUiConfigPlugin,
ThreeViewer,
UnsignedByteType,
TonemapPlugin,
ViewerUiConfigPlugin,
} from 'threepipe'
import {TweakpaneUiPlugin} from '@threepipe/plugin-tweakpane'
import {TweakpaneEditorPlugin} from '@threepipe/plugin-tweakpane-editor'

class ViewerUiConfig extends AViewerPluginSync<''> {
static readonly PluginType = 'ViewerUiConfig'
enabled = true
toJSON: any = undefined
constructor(viewer: ThreeViewer) {
super()
this._viewer = viewer
this.uiConfig = viewer.uiConfig
}
}
async function init() {

const viewer = new ThreeViewer({
canvas: document.getElementById('mcanvas') as HTMLCanvasElement,
msaa: false,
msaa: true,
rgbm: true,
dropzone: {
addOptions: {
clearSceneObjects: false,
@@ -40,10 +32,12 @@ async function init() {
const editor = viewer.addPluginSync(new TweakpaneEditorPlugin())

await viewer.addPlugins([
new ViewerUiConfig(viewer),
new DepthBufferPlugin(UnsignedByteType, false, false),
new ViewerUiConfigPlugin(),
// new SceneUiConfigPlugin(),
new DepthBufferPlugin(HalfFloatType, true, true),
new NormalBufferPlugin(HalfFloatType, false),
new RenderTargetPreviewPlugin(false),
new TonemapPlugin(),
])

const rt = viewer.getOrAddPluginSync(RenderTargetPreviewPlugin)
@@ -51,8 +45,9 @@ async function init() {
rt.addTarget(viewer.getPlugin(NormalBufferPlugin)?.target, 'normal', false, true, false)

editor.loadPlugins({
['Viewer']: [ViewerUiConfig, DropzonePlugin, FullScreenPlugin],
['Viewer']: [ViewerUiConfigPlugin, SceneUiConfigPlugin, DropzonePlugin, FullScreenPlugin],
['GBuffer']: [DepthBufferPlugin, NormalBufferPlugin],
['Post-processing']: [TonemapPlugin],
['Debug']: [RenderTargetPreviewPlugin],
})

@@ -71,3 +66,4 @@ async function init() {
}

init().then(_testFinish)


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

@@ -0,0 +1,197 @@
import {AViewerPluginSync, ThreeViewer} from '../../viewer'
import {MaterialExtension} from '../../materials'
import {uiDropdown, uiFolderContainer, uiSlider, uiToggle} from 'uiconfig.js'
import {
ACESFilmicToneMapping,
CineonToneMapping,
CustomToneMapping,
LinearToneMapping,
Object3D,
ReinhardToneMapping,
Shader,
ShaderChunk,
SRGBColorSpace,
ToneMapping,
Vector4,
WebGLRenderer,
} from 'three'
import {glsl, onChange, serialize} from 'ts-browser-helpers'
import {IMaterial} from '../../core'
import {shaderReplaceString, updateBit} from '../../utils'
import {matDefine, uniform} from '../../three'
import Uncharted2ToneMapping from './shaders/Uncharted2ToneMapping.glsl'
import TonemapShader from './shaders/TonemapPlugin.pars.glsl'
import TonemapShaderPatch from './shaders/TonemapPlugin.patch.glsl'

// todo move
export interface GBufferUpdater {
updateGBufferFlags: (material: IMaterial, data: Vector4) => void
}

// eslint-disable-next-line @typescript-eslint/naming-convention
export const Uncharted2Tonemapping: ToneMapping = CustomToneMapping

@uiFolderContainer('Tonemapping')
export class TonemapPlugin extends AViewerPluginSync<''> implements MaterialExtension, GBufferUpdater {
static readonly PluginType = 'Tonemap'

@serialize() @uiToggle('Enabled') enabled = true

@uiDropdown('Mode', ([
['Linear', LinearToneMapping],
['Reinhard', ReinhardToneMapping],
['Cineon', CineonToneMapping],
['ACESFilmic', ACESFilmicToneMapping],
['Uncharted2', Uncharted2Tonemapping],
] as [string, ToneMapping][]).map(value => ({
label: value[0],
value: value[1],
})))

@onChange(TonemapPlugin.prototype.setDirty)
@serialize() toneMapping: ToneMapping = ACESFilmicToneMapping

@uiToggle('Tonemap Background', (t: TonemapPlugin)=>({hidden: ()=>!t._viewer?.renderManager.gbufferTarget}))
@matDefine('TONEMAP_BACKGROUND', undefined, true, TonemapPlugin.prototype.setDirty, (v)=>v ? '1' : '0', (v) => v !== '0')
@serialize() tonemapBackground = true

// todo handle legacy deserialize
// @onChange(TonemapPlugin.prototype.setDirty)
// @uiToggle('Clip Background')
// @serialize() clipBackground = false

@onChange(TonemapPlugin.prototype.setDirty)
@uiSlider('Exposure', [0, 2 * Math.PI], 0.01)
@serialize() exposure = 1

@uiSlider('Saturation', [0, 2], 0.01)
@uniform({propKey: 'toneMappingSaturation'})
@serialize() saturation: number

@uiSlider('Contrast', [0, 2], 0.01)
@uniform({propKey: 'toneMappingContrast'})
@serialize() contrast: number

readonly extraUniforms = {
toneMappingContrast: {value: 1},
toneMappingSaturation: {value: 1},
} as const

set uniformsNeedUpdate(v: boolean) { // for @uniform decorator
if (v) this.setDirty()
}

parsFragmentSnippet: any = (_: WebGLRenderer, _1: IMaterial) => {
if (!this.enabled) return ''

return glsl`
uniform float toneMappingContrast;
uniform float toneMappingSaturation;
${TonemapShader}
`
}

constructor() {
super()
this.setDirty = this.setDirty.bind(this)
}

/**
* The priority of the material extension when applied to the material in ScreenPass
* set to very low priority, so applied at the end
*/
readonly priority = -100

shaderExtender(shader: Shader, _: IMaterial, _1: WebGLRenderer): void {
if (!this.enabled) return

shader.fragmentShader = shaderReplaceString(
shader.fragmentShader,
'#glMarker', '\n' + TonemapShaderPatch + '\n',
{prepend: true}
)
}

readonly extraDefines = {
['TONEMAP_BACKGROUND']: '1',
} as const

private _rendererState: any = {}

onObjectRender(_: Object3D, material: IMaterial, renderer: WebGLRenderer): void {
if (!this.enabled) return
const {toneMapping, toneMappingExposure, outputColorSpace} = renderer
this._rendererState.toneMapping = toneMapping
this._rendererState.toneMappingExposure = toneMappingExposure
this._rendererState.outputColorSpace = outputColorSpace

renderer.toneMapping = this.toneMapping
renderer.toneMappingExposure = this.exposure
renderer.outputColorSpace = SRGBColorSpace
material.toneMapped = true
material.needsUpdate = true
}

onAfterRender(_: Object3D, _1: IMaterial, renderer: WebGLRenderer): void {
renderer.toneMapping = this._rendererState.toneMapping
renderer.toneMappingExposure = this._rendererState.toneMappingExposure
renderer.outputColorSpace = this._rendererState.outputColorSpace
}

getUiConfig(): any {
return this.uiConfig
}

computeCacheKey = (_: IMaterial) => this.enabled ? '1' : '0'

isCompatible(_: IMaterial): boolean {
return true // (material as MeshStandardMaterial2).isMeshStandardMaterial2
}

setDirty() {
this.__setDirty?.() // this will update version which will set needsUpdate on material
this._viewer?.renderManager.screenPass.setDirty()
}

fromJSON(data: any, meta?: any): this|null|Promise<this|null> {
// really pld legacy
if (data.pass) {
data = {...data}
data.extension = {...data.pass}
delete data.extension.enabled
delete data.pass
}
if (data.extension) {
console.error('TODO: old file')
return null
}
return super.fromJSON(data, meta)
}

onAdded(viewer: ThreeViewer) {
super.onAdded(viewer)
// viewer.getPlugin(GBufferPlugin)?.registerGBufferUpdater(this.updateGBufferFlags) // todo
viewer.renderManager.screenPass.material.registerMaterialExtensions([this])
}

onRemove(viewer: ThreeViewer) {
// viewer.getPlugin(GBufferPlugin)?.unregisterGBufferUpdater(this.updateGBufferFlags)
viewer.renderManager.screenPass.material.unregisterMaterialExtensions([this])
super.onRemove(viewer)
}

updateGBufferFlags(material: IMaterial, data: Vector4): void {
const x = material?.userData.postTonemap === false ? 0 : 1
data.w = updateBit(data.w, 1, x) // 2nd Bit
}

static {
// Add support for Uncharted2 tone mapping
ShaderChunk.tonemapping_pars_fragment = ShaderChunk.tonemapping_pars_fragment.replace('vec3 CustomToneMapping( vec3 color ) { return color; }', Uncharted2ToneMapping)
}

// for typescript
// eslint-disable-next-line @typescript-eslint/naming-convention
__setDirty?: () => void

}

+ 27
- 0
src/plugins/postprocessing/shaders/TonemapPlugin.pars.glsl Просмотреть файл

@@ -0,0 +1,27 @@
int getToneMapBit(in int number) {
return (number/2) % 2; // 2nd bit
}

vec3 TonemappingSaturation(vec3 rgb) {
const vec3 W = vec3(0.2125, 0.7154, 0.0721);
vec3 intensity = vec3(dot(rgb, W));
return mix(intensity, rgb, toneMappingSaturation);
}

vec3 TonemappingContrast(vec3 color){
return (color - vec3(0.5)) * toneMappingContrast + vec3(0.5);
}

vec4 ToneMapping(in vec4 color) {
vec4 outColor = color;

#if defined( TONE_MAPPING )

outColor.rgb = toneMapping(outColor.rgb);
outColor.rgb = TonemappingContrast(outColor.rgb);
outColor.rgb = TonemappingSaturation(outColor.rgb);

#endif

return outColor;
}

+ 8
- 0
src/plugins/postprocessing/shaders/TonemapPlugin.patch.glsl Просмотреть файл

@@ -0,0 +1,8 @@
bool doTonemap = true;
#ifdef HAS_GBUFFER
// doTonemap = getToneMapBit(getGBufferFlags(vUv).a) > 0; // todo
#if TONEMAP_BACKGROUND < 1
if(isBackground) doTonemap = false;
#endif
#endif
if(doTonemap) diffuseColor = ToneMapping(diffuseColor);

+ 8
- 0
src/plugins/postprocessing/shaders/Uncharted2ToneMapping.glsl Просмотреть файл

@@ -0,0 +1,8 @@
// source: http://filmicworlds.com/blog/filmic-tonemapping-operators/
#define Uncharted2Helper( x ) max( ( ( x * ( 0.15 * x + 0.10 * 0.50 ) + 0.20 * 0.02 ) / ( x * ( 0.15 * x + 0.50 ) + 0.20 * 0.30 ) ) - 0.02 / 0.30, vec3( 0.0 ) )
vec3 Uncharted2ToneMapping( vec3 color ) {
// John Hable's filmic operator from Uncharted 2 video game
color *= toneMappingExposure;
return saturate( Uncharted2Helper( color ) / Uncharted2Helper( vec3( 1.0 ) ) );
}
vec3 CustomToneMapping( vec3 color ) { return Uncharted2ToneMapping( color ); }

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