| // @onChange2(DepthBufferPlugin.prototype._createTarget) | // @onChange2(DepthBufferPlugin.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) todo line 139: unregisterMaterialExtensions, maybe because the priority is not set so its added at the end? | |||||
| // @uiToggle() | // @uiToggle() | ||||
| // @onChange2(DepthBufferPlugin.prototype._createTarget) | // @onChange2(DepthBufferPlugin.prototype._createTarget) | ||||
| unpackExtension: MaterialExtension = { | unpackExtension: MaterialExtension = { | ||||
| shaderExtender: (shader)=>{ | shaderExtender: (shader)=>{ | ||||
| const includes = ['depth_buffer_unpack', 'gbuffer_unpack', 'packing'] as const | |||||
| const include = includes.find(i=>shader.fragmentShader.includes(`#include <${i}>`)) | |||||
| shader.fragmentShader = shaderReplaceString(shader.fragmentShader, | shader.fragmentShader = shaderReplaceString(shader.fragmentShader, | ||||
| '#include <packing>', | |||||
| '\n' + DepthBufferUnpack + '\n', {append: true}) | |||||
| `#include <${include}>`, | |||||
| '\n' + DepthBufferUnpack + '\n', {append: include === 'packing'}) | |||||
| }, | }, | ||||
| extraUniforms: { | extraUniforms: { | ||||
| tDepthBuffer: ()=>({value: this.target?.texture}), | tDepthBuffer: ()=>({value: this.target?.texture}), | ||||
| if (this.isPrimaryGBuffer) { | if (this.isPrimaryGBuffer) { | ||||
| this._viewer.renderManager.gbufferTarget = this.target | this._viewer.renderManager.gbufferTarget = this.target | ||||
| this._viewer.renderManager.gbufferUnpackExtension = this.unpackExtension | |||||
| this._viewer.renderManager.screenPass.material.registerMaterialExtensions([this.unpackExtension]) | this._viewer.renderManager.screenPass.material.registerMaterialExtensions([this.unpackExtension]) | ||||
| this._isPrimaryGBufferSet = true | this._isPrimaryGBufferSet = true | ||||
| } | } | ||||
| this.texture = undefined | this.texture = undefined | ||||
| if (this._isPrimaryGBufferSet) { // using a separate flag as when isPrimaryGBuffer is changed, we cannot check it. | if (this._isPrimaryGBufferSet) { // using a separate flag as when isPrimaryGBuffer is changed, we cannot check it. | ||||
| this._viewer.renderManager.gbufferTarget = undefined | this._viewer.renderManager.gbufferTarget = undefined | ||||
| // this._viewer.renderManager.screenPass.material.unregisterMaterialExtensions([this.unpackExtension]) // todo | |||||
| this._viewer.renderManager.gbufferUnpackExtension = undefined | |||||
| // this._viewer.renderManager.screenPass.material.unregisterMaterialExtensions([this.unpackExtension]) // todo this has an issue | |||||
| this._isPrimaryGBufferSet = false | this._isPrimaryGBufferSet = false | ||||
| } | } | ||||
| } | } |
| }) | }) | ||||
| this._pass.fadeTimeState = Math.max(duration, this._pass.fadeTimeState) | this._pass.fadeTimeState = Math.max(duration, this._pass.fadeTimeState) | ||||
| this._pass.fadeTime = this._pass.fadeTimeState | this._pass.fadeTime = this._pass.fadeTimeState | ||||
| this._pass.toSaveFrame = true | |||||
| if (this._pass.fadeTimeState < 500) // only save if very near the end | |||||
| this._pass.toSaveFrame = true | |||||
| // this._pass.passObject.enabled = true | // this._pass.passObject.enabled = true | ||||
| this.setDirty() | this.setDirty() | ||||
| await timeout(duration) | await timeout(duration) |
| unpackExtension: MaterialExtension = { | unpackExtension: MaterialExtension = { | ||||
| shaderExtender: (shader)=>{ | shaderExtender: (shader)=>{ | ||||
| const includes = ['gbuffer_unpack', 'packing'] as const | |||||
| const include = includes.find(i=>shader.fragmentShader.includes(`#include <${i}>`)) | |||||
| shader.fragmentShader = shaderReplaceString(shader.fragmentShader, | shader.fragmentShader = shaderReplaceString(shader.fragmentShader, | ||||
| '#include <packing>', | |||||
| '\n' + GBufferUnpack + '\n', {append: true}) | |||||
| `#include <${include}>`, | |||||
| '\n' + GBufferUnpack + '\n', {append: include === 'packing'}) | |||||
| }, | }, | ||||
| extraUniforms: { | extraUniforms: { | ||||
| tNormalDepth: ()=>({value: this.normalDepthTexture}), | tNormalDepth: ()=>({value: this.normalDepthTexture}), | ||||
| if (this.isPrimaryGBuffer) { | if (this.isPrimaryGBuffer) { | ||||
| this._viewer.renderManager.gbufferTarget = this.target | this._viewer.renderManager.gbufferTarget = this.target | ||||
| this._viewer.renderManager.gbufferUnpackExtension = this.unpackExtension | |||||
| this._viewer.renderManager.screenPass.material.registerMaterialExtensions([this.unpackExtension]) | this._viewer.renderManager.screenPass.material.registerMaterialExtensions([this.unpackExtension]) | ||||
| this._isPrimaryGBufferSet = true | this._isPrimaryGBufferSet = true | ||||
| } | } | ||||
| this.textures = [] | this.textures = [] | ||||
| if (this._isPrimaryGBufferSet) { // using a separate flag as when isPrimaryGBuffer is changed, we cannot check it. | if (this._isPrimaryGBufferSet) { // using a separate flag as when isPrimaryGBuffer is changed, we cannot check it. | ||||
| this._viewer.renderManager.gbufferTarget = undefined | this._viewer.renderManager.gbufferTarget = undefined | ||||
| this._viewer.renderManager.gbufferUnpackExtension = undefined | |||||
| // this._viewer.renderManager.screenPass.material.unregisterMaterialExtensions([this.unpackExtension]) // todo | // this._viewer.renderManager.screenPass.material.unregisterMaterialExtensions([this.unpackExtension]) // todo | ||||
| this._isPrimaryGBufferSet = false | this._isPrimaryGBufferSet = false | ||||
| } | } |
| readonly passId = 'progressive' | readonly passId = 'progressive' | ||||
| public static readonly PluginType = 'ProgressivePlugin' | public static readonly PluginType = 'ProgressivePlugin' | ||||
| /** | |||||
| * Different targets for different render cameras. | |||||
| * Need to save them all here since we need them in the next frame. | |||||
| * @protected | |||||
| */ | |||||
| protected _targets = new Map<string, ProgressivePluginTarget>() | protected _targets = new Map<string, ProgressivePluginTarget>() | ||||
| @serialize() @uiInput('Frame count') maxFrameCount: number | @serialize() @uiInput('Frame count') maxFrameCount: number |
| import {glsl, onChange, serialize} from 'ts-browser-helpers' | import {glsl, onChange, serialize} from 'ts-browser-helpers' | ||||
| import {IMaterial} from '../../core' | import {IMaterial} from '../../core' | ||||
| import {updateBit} from '../../utils' | import {updateBit} from '../../utils' | ||||
| import {matDefine, uniform} from '../../three' | |||||
| import {uniform} from '../../three' | |||||
| import Uncharted2ToneMappingShader from './shaders/Uncharted2ToneMapping.glsl' | import Uncharted2ToneMappingShader from './shaders/Uncharted2ToneMapping.glsl' | ||||
| import TonemapShader from './shaders/TonemapPlugin.pars.glsl' | import TonemapShader from './shaders/TonemapPlugin.pars.glsl' | ||||
| import TonemapShaderPatch from './shaders/TonemapPlugin.patch.glsl' | import TonemapShaderPatch from './shaders/TonemapPlugin.patch.glsl' | ||||
| import {AScreenPassExtensionPlugin} from './AScreenPassExtensionPlugin' | import {AScreenPassExtensionPlugin} from './AScreenPassExtensionPlugin' | ||||
| import {GBufferUpdaterContext} from '../pipeline/GBufferPlugin' | import {GBufferUpdaterContext} from '../pipeline/GBufferPlugin' | ||||
| import {matDefineBool} from '../../three/utils/decorators' | |||||
| // eslint-disable-next-line @typescript-eslint/naming-convention | // eslint-disable-next-line @typescript-eslint/naming-convention | ||||
| export const Uncharted2Tonemapping: ToneMapping = CustomToneMapping | export const Uncharted2Tonemapping: ToneMapping = CustomToneMapping | ||||
| @serialize() toneMapping: ToneMapping = ACESFilmicToneMapping | @serialize() toneMapping: ToneMapping = ACESFilmicToneMapping | ||||
| @uiToggle('Tonemap Background', (t: TonemapPlugin)=>({hidden: ()=>!t._viewer?.renderManager.gbufferTarget})) | @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') | |||||
| @matDefineBool('TONEMAP_BACKGROUND', undefined, true, TonemapPlugin.prototype.setDirty) | |||||
| @serialize() tonemapBackground = true | @serialize() tonemapBackground = true | ||||
| // todo handle legacy deserialize | // todo handle legacy deserialize | ||||
| return super.fromJSON(data, meta) | return super.fromJSON(data, meta) | ||||
| } | } | ||||
| // TODO: add gBufferData or just tonemapEnabled to the scene material UI with an extension | |||||
| // TODO: add gBufferData or just tonemapEnabled to the scene material UI with an extension like bloom | |||||
| updateGBufferFlags(data: Vector4, c: GBufferUpdaterContext): void { | updateGBufferFlags(data: Vector4, c: GBufferUpdaterContext): void { | ||||
| const x = (c.material.userData.gBufferData?.tonemapEnabled ?? c.material?.userData.postTonemap) === false ? 0 : 1 | const x = (c.material.userData.gBufferData?.tonemapEnabled ?? c.material?.userData.postTonemap) === false ? 0 : 1 | ||||
| data.w = updateBit(data.w, 1, x) // 2nd Bit | data.w = updateBit(data.w, 1, x) // 2nd Bit |
| import { | import { | ||||
| BaseEvent, | |||||
| Color, | Color, | ||||
| FloatType, | FloatType, | ||||
| HalfFloatType, | HalfFloatType, | ||||
| @serializable('RenderManager') | @serializable('RenderManager') | ||||
| @uiFolderContainer('Render Manager') | @uiFolderContainer('Render Manager') | ||||
| export class RenderManager extends RenderTargetManager<IRenderManagerEvent, IRenderManagerEventTypes> implements IShaderPropertiesUpdater, IRenderManager { | |||||
| export class RenderManager<TEvent extends BaseEvent = IRenderManagerEvent, TEventTypes extends string = IRenderManagerEventTypes> extends RenderTargetManager<IRenderManagerEvent|TEvent, IRenderManagerEventTypes|TEventTypes> implements IShaderPropertiesUpdater, IRenderManager { | |||||
| private readonly _isWebGL2: boolean | private readonly _isWebGL2: boolean | ||||
| private readonly _composer: EffectComposer2 | private readonly _composer: EffectComposer2 | ||||
| private readonly _context: WebGLRenderingContext | private readonly _context: WebGLRenderingContext |
| } | } | ||||
| /** | /** | ||||
| * | |||||
| * @param customDefines - object for setting define value (like ShaderMaterial.defines), otherwise this.material.defines is taken | |||||
| * Decorator to create a three.js style define in this or this.material and bind to a property. | |||||
| * see also - {@link matDefineBool} | |||||
| * @param key - define name | * @param key - define name | ||||
| * @param customDefines - object for setting define value (like ShaderMaterial.defines), otherwise this.material.defines is taken | |||||
| * @param thisMat - access this.defines instead of this.material.defines | * @param thisMat - access this.defines instead of this.material.defines | ||||
| * @param onChange - function to call when the value changes. The function is called with the following parameters: [key, newVal]. Note: needsUpdate is set to true for this/material if onChange is not provided. | |||||
| * @param processVal - function that processes the value before setting it. | |||||
| * @param invProcessVal - function that processes the value before returning it. | |||||
| */ | */ | ||||
| export function matDefine(key?: string|symbol, customDefines?: any, thisMat = false, onChange?: (...args: any[]) => any, processVal?: (newVal: any)=>any, invProcessVal?: (val:any)=>any): PropertyDecorator { | export function matDefine(key?: string|symbol, customDefines?: any, thisMat = false, onChange?: (...args: any[]) => any, processVal?: (newVal: any)=>any, invProcessVal?: (val:any)=>any): PropertyDecorator { | ||||
| // backing up properties as values are different when called again, no idea why. | // backing up properties as values are different when called again, no idea why. | ||||
| set(newVal: any) { | set(newVal: any) { | ||||
| const {t, p} = getTarget(thisMat ? this : this.material) | const {t, p} = getTarget(thisMat ? this : this.material) | ||||
| if (processVal) newVal = processVal(newVal) | if (processVal) newVal = processVal(newVal) | ||||
| else if (typeof newVal === 'boolean') { // just in case | |||||
| console.error('Boolean values are not supported for defines. Use @matDefineBool instead.') | |||||
| newVal = newVal ? '1' : '0' | |||||
| } | |||||
| safeSetProperty(t, p, newVal, true) | safeSetProperty(t, p, newVal, true) | ||||
| if (newVal === undefined) delete t[p] | if (newVal === undefined) delete t[p] | ||||
| if (onChange && typeof onChange === 'function') { | if (onChange && typeof onChange === 'function') { | ||||
| } | } | ||||
| } | } | ||||
| /** | |||||
| * Same as {@link matDefine} but for boolean values. It sets the value to '1' or '0'/undefined. | |||||
| * @param key - define name | |||||
| * @param customDefines - object for setting define value (like ShaderMaterial.defines), otherwise this.material.defines is taken | |||||
| * @param thisMat - access this.defines instead of this.material.defines | |||||
| * @param onChange - function to call when the value changes. If a string, it is used as a property name in `this` and called. If a function, it is called. The function is called with the following parameters: key, newVal | |||||
| * @param deleteOnFalse - sets to undefined instead of '0' when false | |||||
| */ | |||||
| export function matDefineBool(key?: string|symbol, customDefines?: any, thisMat = false, onChange?: (...args: any[]) => any, deleteOnFalse = false): PropertyDecorator { | |||||
| return matDefine(key, customDefines, thisMat, onChange, (v: any)=>v ? '1' : deleteOnFalse ? undefined : '0', (v: any)=>v && v !== '0') | |||||
| } | |||||
| /** | /** | ||||
| * Binds a property to a value in an object. If the object is a string, it is used as a property name in `this`. | * Binds a property to a value in an object. If the object is a string, it is used as a property name in `this`. | ||||
| * @param obj - object to bind to. If a string, it is used as a property name in `this`. If a function, it is called and the result is used as the object/string. | * @param obj - object to bind to. If a string, it is used as a property name in `this`. If a function, it is called and the result is used as the object/string. |
| import {IRenderTarget, RenderManager} from '../rendering' | import {IRenderTarget, RenderManager} from '../rendering' | ||||
| import {HalfFloatType, LinearMipMapLinearFilter, NoColorSpace, RGBM16ColorSpace, UnsignedByteType} from 'three' | import {HalfFloatType, LinearMipMapLinearFilter, NoColorSpace, RGBM16ColorSpace, UnsignedByteType} from 'three' | ||||
| import {IRenderManagerOptions} from '../core' | |||||
| import {IRenderManagerEvent, IRenderManagerOptions} from '../core' | |||||
| import {ExtendedRenderPass, ScreenPass, TViewerScreenShader} from '../postprocessing' | import {ExtendedRenderPass, ScreenPass, TViewerScreenShader} from '../postprocessing' | ||||
| import {uiFolderContainer} from 'uiconfig.js' | import {uiFolderContainer} from 'uiconfig.js' | ||||
| import {MaterialExtension} from '../materials' | |||||
| import {onChange3} from 'ts-browser-helpers' | |||||
| export interface ViewerRenderManagerOptions extends IRenderManagerOptions { | export interface ViewerRenderManagerOptions extends IRenderManagerOptions { | ||||
| rgbm?: boolean, | rgbm?: boolean, | ||||
| } | } | ||||
| @uiFolderContainer('Render Manager') | @uiFolderContainer('Render Manager') | ||||
| export class ViewerRenderManager extends RenderManager { | |||||
| export class ViewerRenderManager extends RenderManager<IRenderManagerEvent, 'gbufferUnpackExtensionChanged'> { | |||||
| readonly rgbm: boolean | readonly rgbm: boolean | ||||
| readonly msaa: boolean | number | readonly msaa: boolean | number | ||||
| readonly depthBuffer: boolean | readonly depthBuffer: boolean | ||||
| * Reference to the gbuffer target, if it exists. This can be set by plugins like {@link DepthBufferPlugin}, {@link GBufferPlugin} | * Reference to the gbuffer target, if it exists. This can be set by plugins like {@link DepthBufferPlugin}, {@link GBufferPlugin} | ||||
| */ | */ | ||||
| gbufferTarget: IRenderTarget | undefined | gbufferTarget: IRenderTarget | undefined | ||||
| /** | |||||
| * The extension that can be used to upload and unpack the values in gbuffer target(s), if it exists. This can be set by plugins like {@link DepthBufferPlugin}, {@link GBufferPlugin} | |||||
| * Note: this should not be changed after set by some plugin. | |||||
| */ | |||||
| @onChange3(ViewerRenderManager.prototype._gbufferUnpackExtensionChanged) | |||||
| gbufferUnpackExtension: MaterialExtension | undefined | |||||
| private _gbufferUnpackExtensionChanged(params: any) { | |||||
| this.dispatchEvent({type: 'gbufferUnpackExtensionChanged', ...params}) | |||||
| } | |||||
| } | } |