UNPKG

@needle-tools/engine

Version:

Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in

156 lines (125 loc) 6.4 kB
import type { ToneMappingEffect as _TonemappingEffect, ToneMappingMode } from "postprocessing"; import { ACESFilmicToneMapping, AgXToneMapping, LinearToneMapping, NeutralToneMapping, NoToneMapping, ReinhardToneMapping } from "three"; import { MODULES } from "../../../engine/engine_modules.js"; import { serializable } from "../../../engine/engine_serialization.js"; import { getParam } from "../../../engine/engine_utils.js"; import { EffectProviderResult, PostProcessingEffect } from "../PostProcessingEffect.js"; import { findPostProcessingManager } from "../utils.js"; import { VolumeParameter } from "../VolumeParameter.js"; import { registerCustomEffectType } from "../VolumeProfile.js"; const debug = getParam("debugpost"); enum NEToneMappingMode { None = 0, Neutral = 1, // Neutral tonemapper, close to Reinhard ACES = 2, // ACES Filmic reference tonemapper (custom approximation) AgX = 3, // AgX Filmic tonemapper KhronosNeutral = 4, // PBR Neural tonemapper } type NEToneMappingModeNames = keyof typeof NEToneMappingMode; function toThreeToneMapping(mode: NEToneMappingMode | undefined) { switch (mode) { case NEToneMappingMode.None: return LinearToneMapping; case NEToneMappingMode.Neutral: return ReinhardToneMapping; case NEToneMappingMode.ACES: return ACESFilmicToneMapping; case NEToneMappingMode.AgX: return AgXToneMapping; case NEToneMappingMode.KhronosNeutral: return NeutralToneMapping; default: return NeutralToneMapping; } } function threeToNeToneMapping(mode: number | undefined): NEToneMappingMode { switch (mode) { case LinearToneMapping: return NEToneMappingMode.None; case ACESFilmicToneMapping: return NEToneMappingMode.ACES; case AgXToneMapping: return NEToneMappingMode.AgX; case NeutralToneMapping: return NEToneMappingMode.Neutral; case ReinhardToneMapping: return NEToneMappingMode.Neutral; default: return NEToneMappingMode.None; } } function threeToneMappingToEffectMode(mode: number | undefined): ToneMappingMode { switch (mode) { case LinearToneMapping: return MODULES.POSTPROCESSING.MODULE.ToneMappingMode.LINEAR; case ACESFilmicToneMapping: return MODULES.POSTPROCESSING.MODULE.ToneMappingMode.ACES_FILMIC; case AgXToneMapping: return MODULES.POSTPROCESSING.MODULE.ToneMappingMode.AGX; case NeutralToneMapping: return MODULES.POSTPROCESSING.MODULE.ToneMappingMode.NEUTRAL; case ReinhardToneMapping: return MODULES.POSTPROCESSING.MODULE.ToneMappingMode.REINHARD; default: return MODULES.POSTPROCESSING.MODULE.ToneMappingMode.LINEAR; } } /** * @category Effects * @group Components */ export class ToneMappingEffect extends PostProcessingEffect { get typeName() { return "ToneMapping"; } @serializable(VolumeParameter) readonly mode: VolumeParameter = new VolumeParameter(undefined); @serializable(VolumeParameter) readonly exposure: VolumeParameter = new VolumeParameter(1); /** Set the tonemapping mode to e.g. "agx" */ setMode(mode: NEToneMappingModeNames) { const enumValue = NEToneMappingMode[mode as NEToneMappingModeNames]; if (enumValue === undefined) { console.error("Invalid ToneMapping mode", mode); return this; } this.mode.value = enumValue; return this; } get isToneMapping() { return true; } onEffectEnabled(): void { // Tonemapping works with and without a postprocessing manager. // If there's no manager already in the scene we don't need to create one because tonemapping can also be applied without a postprocessing pass const ppmanager = findPostProcessingManager(this); if (!ppmanager) return; super.onEffectEnabled(ppmanager); } onCreateEffect(): EffectProviderResult | undefined { // TODO: this should be done in the PostProcessingHandler if (this.postprocessingContext) { for (const other of this.postprocessingContext.components) { // If we're the first tonemapping effect it's all good if (other === this) break; // If another tonemapping effect is found, warn the user if (other != this && other instanceof ToneMappingEffect) { console.warn("Multiple tonemapping effects found in the same postprocessing stack: Please check your scene setup.", { activeEffect: other, ignoredEffect: this }); return undefined; } } } // ensure the effect tonemapping value is initialized if (this.mode.isInitialized == false) { const init = threeToNeToneMapping(this.context.renderer.toneMapping); this.mode.initialize(init); } const threeMode = toThreeToneMapping(this.mode.value); const tonemapping = new MODULES.POSTPROCESSING.MODULE.ToneMappingEffect({ mode: threeToneMappingToEffectMode(threeMode), }); this.mode.onValueChanged = (newValue) => { const threeMode = toThreeToneMapping(newValue); tonemapping.mode = threeToneMappingToEffectMode(threeMode); if (debug) console.log("ToneMapping mode changed to", NEToneMappingMode[newValue], threeMode, tonemapping.mode); }; if (debug) console.log("Use ToneMapping", NEToneMappingMode[this.mode.value], threeMode, tonemapping.mode, "renderer.tonemapping: " + this.context.renderer.toneMapping); this.exposure.onValueChanged = (newValue) => { this.context.renderer.toneMappingExposure = newValue; }; return tonemapping; } onBeforeRender(): void { if (this.mode.overrideState) this.context.renderer.toneMapping = toThreeToneMapping(this.mode.value); if (this.exposure.overrideState && this.exposure.value !== undefined) this.context.renderer.toneMappingExposure = this.exposure.value } } registerCustomEffectType("Tonemapping", ToneMappingEffect);