From 9f49aa757e666bc38cf18ebdebbd2cc3e1b275fa Mon Sep 17 00:00:00 2001 From: Martin Valigursky Date: Thu, 21 Nov 2024 14:40:35 +0000 Subject: [PATCH 1/9] CameraFrame API in the engine --- .../src/examples/animation/events.example.mjs | 9 +- .../examples/camera/first-person.example.mjs | 11 +- .../graphics/ambient-occlusion.example.mjs | 8 +- .../clustered-area-lights.example.mjs | 7 +- .../dithered-transparency.example.mjs | 8 +- .../src/examples/graphics/portal.example.mjs | 9 +- .../graphics/post-processing.example.mjs | 19 +- .../src/examples/graphics/taa.example.mjs | 10 +- .../misc => scripts/utils}/camera-frame.mjs | 145 ++++---- src/extras/index.js | 3 +- src/extras/render-passes/camera-frame.js | 331 ++++++++++++++++++ src/extras/render-passes/constants.js | 20 ++ .../render-passes/render-pass-camera-frame.js | 9 +- 13 files changed, 451 insertions(+), 138 deletions(-) rename {examples/assets/scripts/misc => scripts/utils}/camera-frame.mjs (57%) create mode 100644 src/extras/render-passes/camera-frame.js create mode 100644 src/extras/render-passes/constants.js diff --git a/examples/src/examples/animation/events.example.mjs b/examples/src/examples/animation/events.example.mjs index 74f14bcb017..8d3c856d2ed 100644 --- a/examples/src/examples/animation/events.example.mjs +++ b/examples/src/examples/animation/events.example.mjs @@ -1,6 +1,5 @@ -import { deviceType, rootPath, fileImport } from 'examples/utils'; +import { deviceType, rootPath } from 'examples/utils'; import * as pc from 'playcanvas'; -const { CameraFrame } = await fileImport(`${rootPath}/static/assets/scripts/misc/camera-frame.mjs`); const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); window.focus(); @@ -34,13 +33,11 @@ createOptions.componentSystems = [ pc.RenderComponentSystem, pc.CameraComponentSystem, pc.LightComponentSystem, - pc.ScriptComponentSystem, pc.AnimComponentSystem ]; createOptions.resourceHandlers = [ pc.TextureHandler, pc.ContainerHandler, - pc.ScriptHandler, pc.AnimClipHandler, pc.AnimStateGraphHandler ]; @@ -76,12 +73,12 @@ assetListLoader.load(() => { // ------ Custom render passes set up ------ - cameraEntity.addComponent('script'); - const cameraFrame = cameraEntity.script.create(CameraFrame); + const cameraFrame = new pc.CameraFrame(app, cameraEntity.camera); cameraFrame.rendering.toneMapping = pc.TONEMAP_NEUTRAL; cameraFrame.rendering.samples = 4; cameraFrame.bloom.enabled = true; cameraFrame.bloom.intensity = 0.01; + cameraFrame.update(); // ------------------------------------------ diff --git a/examples/src/examples/camera/first-person.example.mjs b/examples/src/examples/camera/first-person.example.mjs index 8dd6c65fde2..e8e7eee742f 100644 --- a/examples/src/examples/camera/first-person.example.mjs +++ b/examples/src/examples/camera/first-person.example.mjs @@ -1,9 +1,7 @@ // @config DESCRIPTION
(WASD) Move
(Space) Jump
(Mouse) Look
-import { deviceType, rootPath, fileImport } from 'examples/utils'; +import { deviceType, rootPath } from 'examples/utils'; import * as pc from 'playcanvas'; -const { CameraFrame } = await fileImport(`${rootPath}/static/assets/scripts/misc/camera-frame.mjs`); - const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); window.focus(); @@ -148,15 +146,12 @@ cameraEntity.setLocalPosition(0, 0.5, 0); // ------ Custom render passes set up ------ -cameraEntity.addComponent('script'); -/** @type { CameraFrame } */ -const cameraFrame = cameraEntity.script.create(CameraFrame); - +const cameraFrame = new pc.CameraFrame(app, cameraEntity.camera); cameraFrame.rendering.samples = 4; cameraFrame.rendering.toneMapping = pc.TONEMAP_ACES2; - cameraFrame.bloom.enabled = true; cameraFrame.bloom.intensity = 0.01; +cameraFrame.update(); // ------------------------------------------ diff --git a/examples/src/examples/graphics/ambient-occlusion.example.mjs b/examples/src/examples/graphics/ambient-occlusion.example.mjs index b01146cb31e..a3e59ca99ef 100644 --- a/examples/src/examples/graphics/ambient-occlusion.example.mjs +++ b/examples/src/examples/graphics/ambient-occlusion.example.mjs @@ -1,7 +1,6 @@ import { data } from 'examples/observer'; -import { deviceType, rootPath, fileImport } from 'examples/utils'; +import { deviceType, rootPath } from 'examples/utils'; import * as pc from 'playcanvas'; -const { CameraFrame } = await fileImport(`${rootPath}/static/assets/scripts/misc/camera-frame.mjs`); const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); window.focus(); @@ -168,8 +167,7 @@ assetListLoader.load(() => { // ------ Custom render passes set up ------ - /** @type { CameraFrame } */ - const cameraFrame = cameraEntity.script.create(CameraFrame); + const cameraFrame = new pc.CameraFrame(app, cameraEntity.camera); cameraFrame.rendering.samples = 4; cameraFrame.rendering.toneMapping = pc.TONEMAP_NEUTRAL; @@ -183,6 +181,8 @@ assetListLoader.load(() => { cameraFrame.ssao.samples = data.get('data.ssao.samples'); cameraFrame.ssao.minAngle = data.get('data.ssao.minAngle'); cameraFrame.ssao.scale = data.get('data.ssao.scale'); + + cameraFrame.update(); }; // apply UI changes diff --git a/examples/src/examples/graphics/clustered-area-lights.example.mjs b/examples/src/examples/graphics/clustered-area-lights.example.mjs index 5c1ab2dd92d..f1d97bde4b6 100644 --- a/examples/src/examples/graphics/clustered-area-lights.example.mjs +++ b/examples/src/examples/graphics/clustered-area-lights.example.mjs @@ -1,7 +1,6 @@ import { data } from 'examples/observer'; -import { deviceType, rootPath, fileImport } from 'examples/utils'; +import { deviceType, rootPath } from 'examples/utils'; import * as pc from 'playcanvas'; -const { CameraFrame } = await fileImport(`${rootPath}/static/assets/scripts/misc/camera-frame.mjs`); const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); window.focus(); @@ -228,11 +227,11 @@ assetListLoader.load(() => { app.root.addChild(camera); // custom render passes - const cameraFrame = camera.script.create(CameraFrame); + const cameraFrame = new pc.CameraFrame(app, camera.camera); cameraFrame.rendering.samples = 4; - cameraFrame.bloom.enabled = true; cameraFrame.bloom.intensity = 0.01; cameraFrame.bloom.lastMipLevel = 4; + cameraFrame.update(); // if the device renders in HDR mode, disable tone mapping to output HDR values without any processing cameraFrame.rendering.toneMapping = device.isHdr ? pc.TONEMAP_NONE : pc.TONEMAP_NEUTRAL; diff --git a/examples/src/examples/graphics/dithered-transparency.example.mjs b/examples/src/examples/graphics/dithered-transparency.example.mjs index ae3077b0d6a..3997f706c1c 100644 --- a/examples/src/examples/graphics/dithered-transparency.example.mjs +++ b/examples/src/examples/graphics/dithered-transparency.example.mjs @@ -1,7 +1,6 @@ import { data } from 'examples/observer'; -import { deviceType, rootPath, fileImport } from 'examples/utils'; +import { deviceType, rootPath } from 'examples/utils'; import * as pc from 'playcanvas'; -const { CameraFrame } = await fileImport(`${rootPath}/static/assets/scripts/misc/camera-frame.mjs`); const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); window.focus(); @@ -159,15 +158,16 @@ assetListLoader.load(() => { // ------ Custom render passes set up ------ - /** @type { CameraFrame } */ - const cameraFrame = cameraEntity.script.create(CameraFrame); + const cameraFrame = new pc.CameraFrame(app, cameraEntity.camera); cameraFrame.rendering.toneMapping = pc.TONEMAP_ACES; cameraFrame.rendering.sceneColorMap = true; cameraFrame.taa.jitter = 1; + cameraFrame.update(); const applySettings = () => { cameraFrame.taa.enabled = data.get('data.taa'); cameraFrame.rendering.sharpness = cameraFrame.taa.enabled ? 1 : 0; + cameraFrame.update(); }; // ------ diff --git a/examples/src/examples/graphics/portal.example.mjs b/examples/src/examples/graphics/portal.example.mjs index 3b4256164c3..95802f84850 100644 --- a/examples/src/examples/graphics/portal.example.mjs +++ b/examples/src/examples/graphics/portal.example.mjs @@ -1,6 +1,5 @@ -import { deviceType, rootPath, fileImport } from 'examples/utils'; +import { deviceType, rootPath } from 'examples/utils'; import * as pc from 'playcanvas'; -const { CameraFrame } = await fileImport(`${rootPath}/static/assets/scripts/misc/camera-frame.mjs`); const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); window.focus(); @@ -159,13 +158,11 @@ assetListLoader.load(() => { // ------ Custom render passes set up ------ - camera.addComponent('script'); - /** @type { CameraFrame } */ - const cameraFrame = camera.script.create(CameraFrame); - + const cameraFrame = new pc.CameraFrame(app, camera.camera); cameraFrame.rendering.stencil = true; cameraFrame.rendering.samples = 4; cameraFrame.rendering.toneMapping = pc.TONEMAP_ACES2; + cameraFrame.update(); // ------------------------------------------ diff --git a/examples/src/examples/graphics/post-processing.example.mjs b/examples/src/examples/graphics/post-processing.example.mjs index 9b73ca371c4..38e2233c8f3 100644 --- a/examples/src/examples/graphics/post-processing.example.mjs +++ b/examples/src/examples/graphics/post-processing.example.mjs @@ -1,7 +1,6 @@ import { data } from 'examples/observer'; -import { deviceType, rootPath, fileImport } from 'examples/utils'; +import { deviceType, rootPath } from 'examples/utils'; import * as pc from 'playcanvas'; -const { CameraFrame } = await fileImport(`${rootPath}/static/assets/scripts/misc/camera-frame.mjs`); const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); window.focus(); @@ -216,9 +215,9 @@ assetListLoader.load(() => { // ------ Custom render passes set up ------ - /** @type { CameraFrame } */ - const cameraFrame = cameraEntity.script.create(CameraFrame); + const cameraFrame = new pc.CameraFrame(app, cameraEntity.camera); cameraFrame.rendering.sceneColorMap = true; + cameraFrame.update(); const applySettings = () => { @@ -247,8 +246,7 @@ assetListLoader.load(() => { cameraFrame.taa.jitter = data.get('data.taa.jitter'); // Bloom - cameraFrame.bloom.enabled = data.get('data.bloom.enabled'); - cameraFrame.bloom.intensity = pc.math.lerp(0, 0.1, data.get('data.bloom.intensity') / 100); + cameraFrame.bloom.intensity = data.get('data.bloom.enabled') ? pc.math.lerp(0, 0.1, data.get('data.bloom.intensity') / 100) : 0; cameraFrame.bloom.lastMipLevel = data.get('data.bloom.lastMipLevel'); // grading @@ -258,15 +256,16 @@ assetListLoader.load(() => { cameraFrame.grading.contrast = data.get('data.grading.contrast'); // vignette - cameraFrame.vignette.enabled = data.get('data.vignette.enabled'); cameraFrame.vignette.inner = data.get('data.vignette.inner'); cameraFrame.vignette.outer = data.get('data.vignette.outer'); cameraFrame.vignette.curvature = data.get('data.vignette.curvature'); - cameraFrame.vignette.intensity = data.get('data.vignette.intensity'); + cameraFrame.vignette.intensity = data.get('data.vignette.enabled') ? data.get('data.vignette.intensity') : 0; // fringing - cameraFrame.fringing.enabled = data.get('data.fringing.enabled'); - cameraFrame.fringing.intensity = data.get('data.fringing.intensity'); + cameraFrame.fringing.intensity = data.get('data.fringing.enabled') ? data.get('data.fringing.intensity') : 0; + + // apply all settings + cameraFrame.update(); }; // apply UI changes diff --git a/examples/src/examples/graphics/taa.example.mjs b/examples/src/examples/graphics/taa.example.mjs index df8c9e35c11..a794b78f6b0 100644 --- a/examples/src/examples/graphics/taa.example.mjs +++ b/examples/src/examples/graphics/taa.example.mjs @@ -1,7 +1,6 @@ import { data } from 'examples/observer'; -import { deviceType, rootPath, fileImport } from 'examples/utils'; +import { deviceType, rootPath } from 'examples/utils'; import * as pc from 'playcanvas'; -const { CameraFrame } = await fileImport(`${rootPath}/static/assets/scripts/misc/camera-frame.mjs`); const canvas = /** @type {HTMLCanvasElement} */ (document.getElementById('application-canvas')); window.focus(); @@ -118,20 +117,21 @@ assetListLoader.load(() => { // ------ Custom render passes set up ------ - /** @type { CameraFrame } */ - const cameraFrame = cameraEntity.script.create(CameraFrame); + const cameraFrame = new pc.CameraFrame(app, cameraEntity.camera); cameraFrame.rendering.toneMapping = pc.TONEMAP_ACES; cameraFrame.bloom.intensity = 0.02; + cameraFrame.update(); // ------ const applySettings = () => { - cameraFrame.bloom.enabled = data.get('data.scene.bloom'); + cameraFrame.bloom.intensity = data.get('data.scene.bloom') ? 0.02 : 0; cameraFrame.taa.enabled = data.get('data.taa.enabled'); cameraFrame.taa.jitter = data.get('data.taa.jitter'); cameraFrame.rendering.renderTargetScale = data.get('data.scene.scale'); cameraFrame.rendering.sharpness = data.get('data.scene.sharpness'); + cameraFrame.update(); }; // apply UI changes diff --git a/examples/assets/scripts/misc/camera-frame.mjs b/scripts/utils/camera-frame.mjs similarity index 57% rename from examples/assets/scripts/misc/camera-frame.mjs rename to scripts/utils/camera-frame.mjs index 622a1828e69..0efbd1204b7 100644 --- a/examples/assets/scripts/misc/camera-frame.mjs +++ b/scripts/utils/camera-frame.mjs @@ -1,11 +1,5 @@ -import { - Script, - Color, - math, - CameraFrameOptions, - RenderPassCameraFrame, - SSAOTYPE_NONE -} from 'playcanvas'; +import { Script, Color } from 'playcanvas'; +import { CameraFrame as EngineCameraFrame} from 'playcanvas'; /** @enum {number} */ const ToneMapping = { @@ -292,110 +286,91 @@ class CameraFrame extends Script { */ fringing = new Fringing(); - options = new CameraFrameOptions(); + engineCameraFrame = new EngineCameraFrame(this.app, this.entity.camera); initialize() { - this.updateOptions(); - this.createRenderPass(); - this.on('enable', () => { - this.createRenderPass(); + this.engineCameraFrame.enabled = true; }); this.on('disable', () => { - this.destroyRenderPass(); + this.engineCameraFrame.enabled = false; }); this.on('destroy', () => { - this.destroyRenderPass(); + this.engineCameraFrame.destroy(); }); } - createRenderPass() { - const cameraComponent = this.entity.camera; - this.renderPassCamera = new RenderPassCameraFrame(this.app, cameraComponent, this.options); - cameraComponent.renderPasses = [this.renderPassCamera]; - } - - destroyRenderPass() { - const cameraComponent = this.entity.camera; - cameraComponent.renderPasses?.forEach((renderPass) => { - renderPass.destroy(); - }); - cameraComponent.renderPasses = []; - cameraComponent.rendering = null; - - cameraComponent.jitter = 0; - } - - updateOptions() { - - const { options, rendering, bloom, taa, ssao } = this; - options.stencil = rendering.stencil; - options.samples = rendering.samples; - options.sceneColorMap = rendering.sceneColorMap; - options.prepassEnabled = rendering.sceneDepthMap; - options.bloomEnabled = bloom.enabled; - options.taaEnabled = taa.enabled; - options.ssaoType = ssao.type; - options.ssaoBlurEnabled = ssao.blurEnabled; - options.formats = [rendering.renderFormat, rendering.renderFormatFallback0, rendering.renderFormatFallback1]; - } - postUpdate(dt) { - const cameraComponent = this.entity.camera; - const { options, renderPassCamera, rendering, bloom, grading, vignette, fringing, taa, ssao } = this; - - // options that can cause the passes to be re-created - this.updateOptions(); - renderPassCamera.update(options); - - // update parameters of individual render passes - const { composePass, bloomPass, ssaoPass } = renderPassCamera; - - renderPassCamera.renderTargetScale = math.clamp(rendering.renderTargetScale, 0.1, 1); - composePass.toneMapping = rendering.toneMapping; - composePass.sharpness = rendering.sharpness; - - if (options.bloomEnabled && bloomPass) { - composePass.bloomIntensity = bloom.intensity; - bloomPass.lastMipLevel = bloom.lastMipLevel; + const cf = this.engineCameraFrame + const { rendering, bloom, grading, vignette, fringing, taa, ssao } = this; + + const dstRendering = cf.rendering; + dstRendering.renderFormats.length = 0; + dstRendering.renderFormats.push(rendering.renderFormat); + dstRendering.renderFormats.push(rendering.renderFormatFallback0); + dstRendering.renderFormats.push(rendering.renderFormatFallback1); + dstRendering.stencil = rendering.stencil, + dstRendering.renderTargetScale = rendering.renderTargetScale, + dstRendering.samples = rendering.samples, + dstRendering.sceneColorMap = rendering.sceneColorMap, + dstRendering.sceneDepthMap = rendering.sceneDepthMap, + dstRendering.toneMapping = rendering.toneMapping, + dstRendering.sharpness = rendering.sharpness; + + // ssao + const dstSsao = cf.ssao; + dstSsao.type = ssao.type; + if (ssao.type !== SsaoType.NONE) { + dstSsao.intensity = ssao.intensity; + dstSsao.radius = ssao.radius; + dstSsao.samples = ssao.samples; + dstSsao.power = ssao.power; + dstSsao.minAngle = ssao.minAngle; + dstSsao.scale = ssao.scale; } - if (options.ssaoType !== SSAOTYPE_NONE) { - ssaoPass.intensity = ssao.intensity; - ssaoPass.power = ssao.power; - ssaoPass.radius = ssao.radius; - ssaoPass.sampleCount = ssao.samples; - ssaoPass.minAngle = ssao.minAngle; - ssaoPass.scale = ssao.scale; + // bloom + const dstBloom = cf.bloom; + dstBloom.intensity = bloom.enabled ? bloom.intensity : 0; + if (bloom.enabled) { + dstBloom.lastMipLevel = bloom.lastMipLevel; } - composePass.gradingEnabled = grading.enabled; + // grading + const dstGrading = cf.grading; + dstGrading.enabled = grading.enabled; if (grading.enabled) { - composePass.gradingSaturation = grading.saturation; - composePass.gradingBrightness = grading.brightness; - composePass.gradingContrast = grading.contrast; - composePass.gradingTint = grading.tint; + dstGrading.brightness = grading.brightness; + dstGrading.contrast = grading.contrast; + dstGrading.saturation = grading.saturation; + dstGrading.tint.copy(grading.tint); } - composePass.vignetteEnabled = vignette.enabled; + // vignette + const dstVignette = cf.vignette; + dstVignette.intensity = vignette.enabled ? vignette.intensity : 0; if (vignette.enabled) { - composePass.vignetteInner = vignette.inner; - composePass.vignetteOuter = vignette.outer; - composePass.vignetteCurvature = vignette.curvature; - composePass.vignetteIntensity = vignette.intensity; + dstVignette.inner = vignette.inner; + dstVignette.outer = vignette.outer; + dstVignette.curvature = vignette.curvature; } - composePass.fringingEnabled = fringing.enabled; - if (fringing.enabled) { - composePass.fringingIntensity = fringing.intensity; + // taa + const dstTaa = cf.taa; + dstTaa.enabled = taa.enabled; + if (taa.enabled) { + dstTaa.jitter = taa.jitter; } - // enable camera jitter if taa is enabled - cameraComponent.jitter = taa.enabled ? taa.jitter : 0; + // fringing + const dstFringing = cf.fringing; + dstFringing.intensity = fringing.enabled ? fringing.intensity : 0; + + cf.update(); } } diff --git a/src/extras/index.js b/src/extras/index.js index c6df3f3165f..09b9c17594d 100644 --- a/src/extras/index.js +++ b/src/extras/index.js @@ -14,7 +14,7 @@ export { UsdzExporter } from './exporters/usdz-exporter.js'; export { GltfExporter } from './exporters/gltf-exporter.js'; // RENDER PASSES -export { SSAOTYPE_NONE, SSAOTYPE_LIGHTING, SSAOTYPE_COMBINE } from './render-passes/render-pass-camera-frame.js'; +export { SSAOTYPE_NONE, SSAOTYPE_LIGHTING, SSAOTYPE_COMBINE } from './render-passes/constants.js'; export { RenderPassCameraFrame, CameraFrameOptions } from './render-passes/render-pass-camera-frame.js'; export { RenderPassCompose } from './render-passes/render-pass-compose.js'; export { RenderPassDepthAwareBlur } from './render-passes/render-pass-depth-aware-blur.js'; @@ -23,6 +23,7 @@ export { RenderPassUpsample } from './render-passes/render-pass-upsample.js'; export { RenderPassBloom } from './render-passes/render-pass-bloom.js'; export { RenderPassSsao } from './render-passes/render-pass-ssao.js'; export { RenderPassTAA } from './render-passes/render-pass-taa.js'; +export { CameraFrame } from './render-passes/camera-frame.js'; // GIZMOS export { diff --git a/src/extras/render-passes/camera-frame.js b/src/extras/render-passes/camera-frame.js new file mode 100644 index 00000000000..8cba280bdf9 --- /dev/null +++ b/src/extras/render-passes/camera-frame.js @@ -0,0 +1,331 @@ +import { Debug } from '../../core/debug.js'; +import { Color } from '../../core/math/color.js'; +import { math } from '../../core/math/math.js'; +import { PIXELFORMAT_111110F, PIXELFORMAT_RGBA16F, PIXELFORMAT_RGBA32F } from '../../platform/graphics/constants.js'; +import { SSAOTYPE_NONE } from './constants.js'; +import { CameraFrameOptions, RenderPassCameraFrame } from './render-pass-camera-frame.js'; + +/** + * @import { AppBase } from '../../framework/app-base.js' + * @import { CameraComponent } from '../../framework/components/camera/component.js' + */ +/** + * @typedef {Object} Rendering + * @property {number[]} renderFormats - The render formats of the frame buffer, in order of + * preference. Defaults to [{@link PIXELFORMAT_111110F}, {@link PIXELFORMAT_RGBA16F}, + * {@link PIXELFORMAT_RGBA32F}]. + * @property {boolean} stencil - Whether the render buffer has a stencil buffer. Defaults to false. + * @property {number} renderTargetScale - The scale of the frame buffer, 0.1-1 range. Defaults to 1. + * @property {number} samples - The number of samples of the render buffer, 1-4 range. Defaults to 1. + * @property {boolean} sceneColorMap - Whether rendering generates a scene color map. Defaults to false. + * @property {boolean} sceneDepthMap - Whether rendering generates a scene depth map. Defaults to false. + * @property {number} toneMapping - The tone mapping. Defaults to {@link ToneMapping.LINEAR}. Can be: + * + * - {@link TONEMAP_LINEAR} + * - {@link TONEMAP_FILMIC} + * - {@link TONEMAP_HEJL} + * - {@link TONEMAP_ACES} + * - {@link TONEMAP_ACES2} + * - {@link TONEMAP_NEUTRAL} + * + * @property {number} sharpness - The sharpness of the frame buffer, 0-1 range. Defaults to 0. + */ + +/** + * @typedef {Object} Ssao + * @property {string} type - The type of the SSAO determines how it is applied in the rendering + * process. Defaults to {@link SSAOTYPE_NONE}. Can be: + * + * - {@link SSAOTYPE_NONE} + * - {@link SSAOTYPE_LIGHTING} + * - {@link SSAOTYPE_COMBINE} + * + * @property {boolean} blurEnabled - Whether the SSAO effect is blurred. Defaults to true. + * @property {number} intensity - The intensity of the SSAO effect, 0-1 range. Defaults to 0.5. + * @property {number} radius - The radius of the SSAO effect, 0-100 range. Defaults to 30. + * @property {number} samples - The number of samples of the SSAO effect, 1-64 range. Defaults to 12. + * @property {number} power - The power of the SSAO effect, 0.1-10 range. Defaults to 6. + * @property {number} minAngle - The minimum angle of the SSAO effect, 1-90 range. Defaults to 10. + * @property {number} scale - The scale of the SSAO effect, 0.5-1 range. Defaults to 1. + */ + +/** + * @typedef {Object} Bloom + * @property {number} intensity - The intensity of the bloom effect, 0-0.1 range. Defaults to 0, + * making it disabled. + * @property {number} lastMipLevel - The last mip level of the bloom effect, 0-12 range. Defaults to 1. + */ + +/** + * @typedef {Object} Grading + * @property {boolean} enabled - Whether grading is enabled. Defaults to false. + * @property {number} brightness - The brightness of the grading effect, 0-3 range. Defaults to 1. + * @property {number} contrast - The contrast of the grading effect, 0.5-1.5 range. Defaults to 1. + * @property {number} saturation - The saturation of the grading effect, 0-2 range. Defaults to 1. + * @property {Color} tint - The tint color of the grading effect. Defaults to white. + */ + +/** + * @typedef {Object} Vignette + * @property {number} intensity - The intensity of the vignette effect, 0-1 range. Defaults to 0, + * making it disabled. + * @property {number} inner - The inner radius of the vignette effect, 0-3 range. Defaults to 0.5. + * @property {number} outer - The outer radius of the vignette effect, 0-3 range. Defaults to 1. + * @property {number} curvature - The curvature of the vignette effect, 0.01-10 range. Defaults to 0.5. + */ + +/** + * @typedef {Object} Fringing + * @property {number} intensity - The intensity of the fringing effect, 0-100 range. Defaults to 0, + * making it disabled. + */ + +/** + * @typedef {Object} Taa + * @property {boolean} enabled - Whether Taa is enabled. Defaults to false. + * @property {number} jitter - The intensity of the camera jitter, 0-1 range. Defaults to 1. + */ + +/** + * Implementation of a simple to use camera rendering pass, which supports SSAO, Bloom and + * other rendering effects. + * + * @category Render Pass + */ +class CameraFrame { + /** + * Rendering settings. + * + * @type {Rendering} + */ + rendering = { + renderFormats: [PIXELFORMAT_111110F, PIXELFORMAT_RGBA16F, PIXELFORMAT_RGBA32F], + stencil: false, + renderTargetScale: 1.0, + samples: 1, + sceneColorMap: false, + sceneDepthMap: false, + toneMapping: 0, + sharpness: 0.0 + }; + + /** + * SSAO settings. + * + * @type {Ssao} + */ + ssao = { + type: SSAOTYPE_NONE, + blurEnabled: true, + intensity: 0.5, + radius: 30, + samples: 12, + power: 6, + minAngle: 10, + scale: 1 + }; + + /** + * Bloom settings. + * + * @type {Bloom} + */ + bloom = { + intensity: 0, + lastMipLevel: 1 + }; + + /** + * Grading settings. + * + * @type {Grading} + */ + grading = { + enabled: false, + brightness: 1, + contrast: 1, + saturation: 1, + tint: new Color(1, 1, 1, 1) + }; + + /** + * Vignette settings. + * + * @type {Vignette} + */ + vignette = { + intensity: 0, + inner: 0.5, + outer: 1, + curvature: 0.5 + }; + + /** + * Taa settings. + * + * @type {Taa} + */ + taa = { + enabled: false, + jitter: 1 + }; + + /** + * Fringing settings. + * + * @type {Fringing} + */ + fringing = { + intensity: 0 + }; + + options = new CameraFrameOptions(); + + /** + * @type {RenderPassCameraFrame|null} + * @private + */ + renderPassCamera = null; + + /** + * Creates a new CameraFrame instance. + * + * @param {AppBase} app - The application. + * @param {CameraComponent} cameraComponent - The camera component. + */ + constructor(app, cameraComponent) { + this.app = app; + this.cameraComponent = cameraComponent; + Debug.assert(cameraComponent, 'CameraFrame: cameraComponent must be defined'); + + this.updateOptions(); + this.enabled = true; + } + + /** + * Destroys the camera frame, removing all render passes. + */ + destroy() { + this.disable(); + } + + enable() { + if (!this.renderPassCamera) { + const cameraComponent = this.cameraComponent; + this.renderPassCamera = new RenderPassCameraFrame(this.app, cameraComponent, this.options); + cameraComponent.renderPasses = [this.renderPassCamera]; + } + } + + disable() { + if (this.renderPassCamera) { + const cameraComponent = this.cameraComponent; + cameraComponent.renderPasses?.forEach((renderPass) => { + renderPass.destroy(); + }); + cameraComponent.renderPasses = []; + cameraComponent.rendering = null; + + cameraComponent.jitter = 0; + } + } + + /** + * Sets the enabled state of the camera frame. This disabled the render passes, and releases + * any resources. + * + * @type {boolean} + */ + set enabled(value) { + if (value) { + this.enable(); + } else { + this.disable(); + } + } + + /** + * Gets the enabled state of the camera frame. + * + * @type {boolean} + */ + get enabled() { + return this.renderPassCamera !== null; + } + + updateOptions() { + + const { options, rendering, bloom, taa, ssao } = this; + options.stencil = rendering.stencil; + options.samples = rendering.samples; + options.sceneColorMap = rendering.sceneColorMap; + options.prepassEnabled = rendering.sceneDepthMap; + options.bloomEnabled = bloom.intensity > 0; + options.taaEnabled = taa.enabled; + options.ssaoType = ssao.type; + options.ssaoBlurEnabled = ssao.blurEnabled; + options.formats = rendering.renderFormats.slice(); + } + + /** + * Applies any changes made to the properties of this instance. + */ + update() { + + if (!this.enabled) return; + + const cameraComponent = this.cameraComponent; + const { options, renderPassCamera, rendering, bloom, grading, vignette, fringing, taa, ssao } = this; + + // options that can cause the passes to be re-created + this.updateOptions(); + renderPassCamera.update(options); + + // update parameters of individual render passes + const { composePass, bloomPass, ssaoPass } = renderPassCamera; + + renderPassCamera.renderTargetScale = math.clamp(rendering.renderTargetScale, 0.1, 1); + composePass.toneMapping = rendering.toneMapping; + composePass.sharpness = rendering.sharpness; + + if (options.bloomEnabled && bloomPass) { + composePass.bloomIntensity = bloom.intensity; + bloomPass.lastMipLevel = bloom.lastMipLevel; + } + + if (options.ssaoType !== SSAOTYPE_NONE) { + ssaoPass.intensity = ssao.intensity; + ssaoPass.power = ssao.power; + ssaoPass.radius = ssao.radius; + ssaoPass.sampleCount = ssao.samples; + ssaoPass.minAngle = ssao.minAngle; + ssaoPass.scale = ssao.scale; + } + + composePass.gradingEnabled = grading.enabled; + if (grading.enabled) { + composePass.gradingSaturation = grading.saturation; + composePass.gradingBrightness = grading.brightness; + composePass.gradingContrast = grading.contrast; + composePass.gradingTint = grading.tint; + } + + composePass.vignetteEnabled = vignette.intensity > 0; + if (composePass.vignetteEnabled) { + composePass.vignetteInner = vignette.inner; + composePass.vignetteOuter = vignette.outer; + composePass.vignetteCurvature = vignette.curvature; + composePass.vignetteIntensity = vignette.intensity; + } + + composePass.fringingEnabled = fringing.intensity > 0; + if (composePass.fringingEnabled) { + composePass.fringingIntensity = fringing.intensity; + } + + // enable camera jitter if taa is enabled + cameraComponent.jitter = taa.enabled ? taa.jitter : 0; + } +} + +export { CameraFrame }; diff --git a/src/extras/render-passes/constants.js b/src/extras/render-passes/constants.js new file mode 100644 index 00000000000..4058f1c5148 --- /dev/null +++ b/src/extras/render-passes/constants.js @@ -0,0 +1,20 @@ +/** + * SSAO is disabled. + * + * @type {string} + */ +export const SSAOTYPE_NONE = 'none'; + +/** + * SSAO is applied during forward rendering. + * + * @type {string} + */ +export const SSAOTYPE_LIGHTING = 'lighting'; + +/** + * SSAO is applied during postprocessing. + * + * @type {string} + */ +export const SSAOTYPE_COMBINE = 'combine'; diff --git a/src/extras/render-passes/render-pass-camera-frame.js b/src/extras/render-passes/render-pass-camera-frame.js index 34ea9a27eac..7e539c4c860 100644 --- a/src/extras/render-passes/render-pass-camera-frame.js +++ b/src/extras/render-passes/render-pass-camera-frame.js @@ -11,10 +11,8 @@ import { RenderPassCompose } from './render-pass-compose.js'; import { RenderPassTAA } from './render-pass-taa.js'; import { RenderPassPrepass } from './render-pass-prepass.js'; import { RenderPassSsao } from './render-pass-ssao.js'; - -export const SSAOTYPE_NONE = 'none'; -export const SSAOTYPE_LIGHTING = 'lighting'; -export const SSAOTYPE_COMBINE = 'combine'; +import { SSAOTYPE_COMBINE, SSAOTYPE_LIGHTING, SSAOTYPE_NONE } from './constants.js'; +import { Debug } from '../../core/debug.js'; class CameraFrameOptions { formats; @@ -82,6 +80,7 @@ class RenderPassCameraFrame extends RenderPass { rt = null; constructor(app, cameraComponent, options = {}) { + Debug.assert(app); super(app.graphicsDevice); this.app = app; this.cameraComponent = cameraComponent; @@ -211,8 +210,8 @@ class RenderPassCameraFrame extends RenderPass { this.rt = new RenderTarget({ colorBuffer: this.sceneTexture, - // depthBuffer: this.sceneDepth, depth: true, + stencil: options.stencil, samples: options.samples, flipY: !!targetRenderTarget?.flipY // flipY is inherited from the target renderTarget }); From 99ac308907be8da0a468635976a46346390d3b52 Mon Sep 17 00:00:00 2001 From: Martin Valigursky Date: Thu, 21 Nov 2024 15:08:51 +0000 Subject: [PATCH 2/9] lint --- eslint.config.mjs | 15 ++++++++++++++- scripts/utils/camera-frame.mjs | 17 ++++++++--------- src/extras/render-passes/camera-frame.js | 8 ++++---- 3 files changed, 26 insertions(+), 14 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 42b1f2f2e92..b68415bc8f8 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -2,6 +2,12 @@ import playcanvasConfig from '@playcanvas/eslint-config'; import babelParser from '@babel/eslint-parser'; import globals from 'globals'; +// Extract or preserve existing JSDoc tags +const jsdocRule = playcanvasConfig.find( + config => config.rules && config.rules['jsdoc/check-tag-names'] +); +const existingTags = jsdocRule?.rules['jsdoc/check-tag-names'][1]?.definedTags || []; + export default [ ...playcanvasConfig, { @@ -27,7 +33,14 @@ export default [ } }, rules: { - 'import/order': 'off' + 'import/order': 'off', + 'jsdoc/check-tag-names': [ + 'error', + { + // custom mjs script tags to not error on, add them to those from parent config + definedTags: [...new Set([...existingTags, 'range', 'step', 'precision'])] + } + ] } }, { diff --git a/scripts/utils/camera-frame.mjs b/scripts/utils/camera-frame.mjs index 0efbd1204b7..10aed518149 100644 --- a/scripts/utils/camera-frame.mjs +++ b/scripts/utils/camera-frame.mjs @@ -1,5 +1,4 @@ -import { Script, Color } from 'playcanvas'; -import { CameraFrame as EngineCameraFrame} from 'playcanvas'; +import { CameraFrame as EngineCameraFrame, Script, Color } from 'playcanvas'; /** @enum {number} */ const ToneMapping = { @@ -305,7 +304,7 @@ class CameraFrame extends Script { postUpdate(dt) { - const cf = this.engineCameraFrame + const cf = this.engineCameraFrame; const { rendering, bloom, grading, vignette, fringing, taa, ssao } = this; const dstRendering = cf.rendering; @@ -313,12 +312,12 @@ class CameraFrame extends Script { dstRendering.renderFormats.push(rendering.renderFormat); dstRendering.renderFormats.push(rendering.renderFormatFallback0); dstRendering.renderFormats.push(rendering.renderFormatFallback1); - dstRendering.stencil = rendering.stencil, - dstRendering.renderTargetScale = rendering.renderTargetScale, - dstRendering.samples = rendering.samples, - dstRendering.sceneColorMap = rendering.sceneColorMap, - dstRendering.sceneDepthMap = rendering.sceneDepthMap, - dstRendering.toneMapping = rendering.toneMapping, + dstRendering.stencil = rendering.stencil; + dstRendering.renderTargetScale = rendering.renderTargetScale; + dstRendering.samples = rendering.samples; + dstRendering.sceneColorMap = rendering.sceneColorMap; + dstRendering.sceneDepthMap = rendering.sceneDepthMap; + dstRendering.toneMapping = rendering.toneMapping; dstRendering.sharpness = rendering.sharpness; // ssao diff --git a/src/extras/render-passes/camera-frame.js b/src/extras/render-passes/camera-frame.js index 8cba280bdf9..a96ccf6c550 100644 --- a/src/extras/render-passes/camera-frame.js +++ b/src/extras/render-passes/camera-frame.js @@ -12,7 +12,7 @@ import { CameraFrameOptions, RenderPassCameraFrame } from './render-pass-camera- /** * @typedef {Object} Rendering * @property {number[]} renderFormats - The render formats of the frame buffer, in order of - * preference. Defaults to [{@link PIXELFORMAT_111110F}, {@link PIXELFORMAT_RGBA16F}, + * preference. Defaults to [{@link PIXELFORMAT_111110F}, {@link PIXELFORMAT_RGBA16F}, * {@link PIXELFORMAT_RGBA32F}]. * @property {boolean} stencil - Whether the render buffer has a stencil buffer. Defaults to false. * @property {number} renderTargetScale - The scale of the frame buffer, 0.1-1 range. Defaults to 1. @@ -39,7 +39,7 @@ import { CameraFrameOptions, RenderPassCameraFrame } from './render-pass-camera- * - {@link SSAOTYPE_NONE} * - {@link SSAOTYPE_LIGHTING} * - {@link SSAOTYPE_COMBINE} - * + * * @property {boolean} blurEnabled - Whether the SSAO effect is blurred. Defaults to true. * @property {number} intensity - The intensity of the SSAO effect, 0-1 range. Defaults to 0.5. * @property {number} radius - The radius of the SSAO effect, 0-100 range. Defaults to 30. @@ -67,7 +67,7 @@ import { CameraFrameOptions, RenderPassCameraFrame } from './render-pass-camera- /** * @typedef {Object} Vignette - * @property {number} intensity - The intensity of the vignette effect, 0-1 range. Defaults to 0, + * @property {number} intensity - The intensity of the vignette effect, 0-1 range. Defaults to 0, * making it disabled. * @property {number} inner - The inner radius of the vignette effect, 0-3 range. Defaults to 0.5. * @property {number} outer - The outer radius of the vignette effect, 0-3 range. Defaults to 1. @@ -252,7 +252,7 @@ class CameraFrame { get enabled() { return this.renderPassCamera !== null; } - + updateOptions() { const { options, rendering, bloom, taa, ssao } = this; From 1cfea014088ff330e9d589669817c543e1cfd168 Mon Sep 17 00:00:00 2001 From: Martin Valigursky Date: Fri, 22 Nov 2024 15:53:14 +0000 Subject: [PATCH 3/9] Vignette docs --- src/extras/render-passes/camera-frame.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/extras/render-passes/camera-frame.js b/src/extras/render-passes/camera-frame.js index a96ccf6c550..f9a374f8f23 100644 --- a/src/extras/render-passes/camera-frame.js +++ b/src/extras/render-passes/camera-frame.js @@ -69,9 +69,18 @@ import { CameraFrameOptions, RenderPassCameraFrame } from './render-pass-camera- * @typedef {Object} Vignette * @property {number} intensity - The intensity of the vignette effect, 0-1 range. Defaults to 0, * making it disabled. - * @property {number} inner - The inner radius of the vignette effect, 0-3 range. Defaults to 0.5. - * @property {number} outer - The outer radius of the vignette effect, 0-3 range. Defaults to 1. - * @property {number} curvature - The curvature of the vignette effect, 0.01-10 range. Defaults to 0.5. + * @property {number} inner - The inner distance of the vignette effect measured from the center of + * the screen, 0-3 range. This is where the vignette effect starts. Value larger than 1 represents + * the value off screen, which allows more control. Defaults to 0.5, representing half the distance + * from center. + * @property {number} outer - The outer distance of the vignette effect measured from the center of + * the screen, 0-3 range. This is where the vignette reaches full intensity. Value larger than 1 + * represents the value off screen, which allows more control. Defaults to 1, representing the full + * screen. + * @property {number} curvature - The curvature of the vignette effect, 0.01-10 range. The vignette + * is rendered using a rectangle with rounded corners, and this parameter controls the curvature of + * the corners. Value of 1 represents a circle. Smaller values make the corners more square, while + * larger values make them more rounded. Defaults to 0.5. */ /** From 102f6d941289b142bcc0694c536c8c375a243ac7 Mon Sep 17 00:00:00 2001 From: Martin Valigursky Date: Fri, 22 Nov 2024 16:11:29 +0000 Subject: [PATCH 4/9] jitter / sharpness docs improvement --- src/extras/render-passes/camera-frame.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/extras/render-passes/camera-frame.js b/src/extras/render-passes/camera-frame.js index f9a374f8f23..38b8a5e21a4 100644 --- a/src/extras/render-passes/camera-frame.js +++ b/src/extras/render-passes/camera-frame.js @@ -28,7 +28,10 @@ import { CameraFrameOptions, RenderPassCameraFrame } from './render-pass-camera- * - {@link TONEMAP_ACES2} * - {@link TONEMAP_NEUTRAL} * - * @property {number} sharpness - The sharpness of the frame buffer, 0-1 range. Defaults to 0. + * @property {number} sharpness - The sharpening intensity, 0-1 range. This can be used to increase + * the sharpness of the rendered image. Often used to counteract the blurriness of the TAA effect, + * but also blurriness caused by rendering to a lower resolution render target by using + * rendering.renderTargetScale property. Defaults to 0. */ /** @@ -92,7 +95,10 @@ import { CameraFrameOptions, RenderPassCameraFrame } from './render-pass-camera- /** * @typedef {Object} Taa * @property {boolean} enabled - Whether Taa is enabled. Defaults to false. - * @property {number} jitter - The intensity of the camera jitter, 0-1 range. Defaults to 1. + * @property {number} jitter - The intensity of the camera jitter, 0-1 range. The larger the value, + * the more jitter is applied to the camera, making the anti-aliasing effect more pronounced. This + * also makes the image more blurry, and rendering.sharpness parameter can be used to counteract. + * Defaults to 1. */ /** From 770c944ff1b4af3bc12b7a8d60dd506f821d4d1c Mon Sep 17 00:00:00 2001 From: Martin Valigursky Date: Fri, 22 Nov 2024 16:23:41 +0000 Subject: [PATCH 5/9] render formats / samples docs --- src/extras/render-passes/camera-frame.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/extras/render-passes/camera-frame.js b/src/extras/render-passes/camera-frame.js index 38b8a5e21a4..1c2d637ecf7 100644 --- a/src/extras/render-passes/camera-frame.js +++ b/src/extras/render-passes/camera-frame.js @@ -11,12 +11,20 @@ import { CameraFrameOptions, RenderPassCameraFrame } from './render-pass-camera- */ /** * @typedef {Object} Rendering - * @property {number[]} renderFormats - The render formats of the frame buffer, in order of - * preference. Defaults to [{@link PIXELFORMAT_111110F}, {@link PIXELFORMAT_RGBA16F}, - * {@link PIXELFORMAT_RGBA32F}]. + * @property {number[]} renderFormats - The preferred render formats of the frame buffer, in order of + * preference. First format from this list that is supported by the hardware is used. When none of + * the formats are supported, {@link PIXELFORMAT_RGBA8} is used, but this automatically disables + * bloom effect, which requires HDR format. The list can contain the following formats: + * {@link PIXELFORMAT_111110F}, {@link PIXELFORMAT_RGBA16F}, {@link PIXELFORMAT_RGBA32F} and {@link + * PIXELFORMAT_RGBA8}. Typically the default option should be used, which prefers the faster formats, + * but if higher dynamic range is needed, the list can be adjusted to prefer higher precision formats. + * Defaults to [{@link PIXELFORMAT_111110F}, {@link PIXELFORMAT_RGBA16F}, {@link PIXELFORMAT_RGBA32F}]. * @property {boolean} stencil - Whether the render buffer has a stencil buffer. Defaults to false. * @property {number} renderTargetScale - The scale of the frame buffer, 0.1-1 range. Defaults to 1. - * @property {number} samples - The number of samples of the render buffer, 1-4 range. Defaults to 1. + * @property {number} samples - The number of samples of the {@link RenderTarget} used for the scene + * rendering, in 1-4 range. Value of 1 disables multisample anti-aliasing, other values enable + * anti-aliasing, Typically set to 1 when TAA is used, even though both anti-aliasing options can be + * used together at a higher cost. Defaults to 1. * @property {boolean} sceneColorMap - Whether rendering generates a scene color map. Defaults to false. * @property {boolean} sceneDepthMap - Whether rendering generates a scene depth map. Defaults to false. * @property {number} toneMapping - The tone mapping. Defaults to {@link ToneMapping.LINEAR}. Can be: From 1232aa31dd5b3bd3533920c50c468ac299eb60e9 Mon Sep 17 00:00:00 2001 From: Martin Valigursky Date: Fri, 22 Nov 2024 16:42:21 +0000 Subject: [PATCH 6/9] renderTargetScale --- src/extras/render-passes/camera-frame.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/extras/render-passes/camera-frame.js b/src/extras/render-passes/camera-frame.js index 1c2d637ecf7..d9faaa35ee2 100644 --- a/src/extras/render-passes/camera-frame.js +++ b/src/extras/render-passes/camera-frame.js @@ -20,7 +20,11 @@ import { CameraFrameOptions, RenderPassCameraFrame } from './render-pass-camera- * but if higher dynamic range is needed, the list can be adjusted to prefer higher precision formats. * Defaults to [{@link PIXELFORMAT_111110F}, {@link PIXELFORMAT_RGBA16F}, {@link PIXELFORMAT_RGBA32F}]. * @property {boolean} stencil - Whether the render buffer has a stencil buffer. Defaults to false. - * @property {number} renderTargetScale - The scale of the frame buffer, 0.1-1 range. Defaults to 1. + * @property {number} renderTargetScale - The scale of the render target, 0.1-1 range. This allows the + * scene to be rendered to a lower resolution render target as an optimization. The post-processing + * is also applied at this lower resolution. The image is then up-scaled to the full resolution and + * any UI rendering that follows is applied at the full resolution. Defaults to 1 which represents + * full resolution rendering. * @property {number} samples - The number of samples of the {@link RenderTarget} used for the scene * rendering, in 1-4 range. Value of 1 disables multisample anti-aliasing, other values enable * anti-aliasing, Typically set to 1 when TAA is used, even though both anti-aliasing options can be From 312c42c96949941403cdedbb5414e6b093b5d072 Mon Sep 17 00:00:00 2001 From: Martin Valigursky Date: Fri, 22 Nov 2024 16:59:35 +0000 Subject: [PATCH 7/9] description for settings types --- src/extras/render-passes/camera-frame.js | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/extras/render-passes/camera-frame.js b/src/extras/render-passes/camera-frame.js index d9faaa35ee2..88858df2fc5 100644 --- a/src/extras/render-passes/camera-frame.js +++ b/src/extras/render-passes/camera-frame.js @@ -9,7 +9,11 @@ import { CameraFrameOptions, RenderPassCameraFrame } from './render-pass-camera- * @import { AppBase } from '../../framework/app-base.js' * @import { CameraComponent } from '../../framework/components/camera/component.js' */ + /** + * Properties related to scene rendering, encompassing settings that control the rendering resolution, + * pixel format, multi-sampling for anti-aliasing, tone-mapping and similar. + * * @typedef {Object} Rendering * @property {number[]} renderFormats - The preferred render formats of the frame buffer, in order of * preference. First format from this list that is supported by the hardware is used. When none of @@ -47,6 +51,11 @@ import { CameraFrameOptions, RenderPassCameraFrame } from './render-pass-camera- */ /** + * Properties related to the Screen Space Ambient Occlusion (SSAO) effect, a postprocessing technique + * that approximates ambient occlusion by calculating how exposed each point in the screen space is + * to ambient light, enhancing depth perception and adding subtle shadowing in crevices and between + * objects. + * * @typedef {Object} Ssao * @property {string} type - The type of the SSAO determines how it is applied in the rendering * process. Defaults to {@link SSAOTYPE_NONE}. Can be: @@ -65,6 +74,10 @@ import { CameraFrameOptions, RenderPassCameraFrame } from './render-pass-camera- */ /** + * Properties related to the HDR bloom effect, a postprocessing technique that simulates the natural + * glow of bright light sources by spreading their intensity beyond their boundaries, creating a soft + * and realistic blooming effect. + * * @typedef {Object} Bloom * @property {number} intensity - The intensity of the bloom effect, 0-0.1 range. Defaults to 0, * making it disabled. @@ -72,6 +85,10 @@ import { CameraFrameOptions, RenderPassCameraFrame } from './render-pass-camera- */ /** + * Properties related to the color grading effect, a postprocessing technique used to adjust and the + * visual tone of an image. This effect modifies brightness, contrast, saturation, and overall color + * balance to achieve a specific aesthetic or mood. + * * @typedef {Object} Grading * @property {boolean} enabled - Whether grading is enabled. Defaults to false. * @property {number} brightness - The brightness of the grading effect, 0-3 range. Defaults to 1. @@ -81,6 +98,11 @@ import { CameraFrameOptions, RenderPassCameraFrame } from './render-pass-camera- */ /** + * Properties related to the vignette effect, a postprocessing technique that darkens the image + * edges, creating a gradual falloff in brightness from the center outward. The effect can be also + * reversed, making the center of the image darker than the edges, by specifying the outer distance + * smaller than the inner distance. + * * @typedef {Object} Vignette * @property {number} intensity - The intensity of the vignette effect, 0-1 range. Defaults to 0, * making it disabled. @@ -99,12 +121,18 @@ import { CameraFrameOptions, RenderPassCameraFrame } from './render-pass-camera- */ /** + * Properties related to the fringing effect, a chromatic aberration phenomenon where the red, green, + * and blue color channels diverge increasingly with greater distance from the center of the screen. + * * @typedef {Object} Fringing * @property {number} intensity - The intensity of the fringing effect, 0-100 range. Defaults to 0, * making it disabled. */ /** + * Properties related to temporal anti-aliasing (TAA), which is a technique used to reduce aliasing + * in the rendered image by blending multiple frames together over time. + * * @typedef {Object} Taa * @property {boolean} enabled - Whether Taa is enabled. Defaults to false. * @property {number} jitter - The intensity of the camera jitter, 0-1 range. The larger the value, From 52a62195360b00e384a26cb20d3ae60f0b9e38f3 Mon Sep 17 00:00:00 2001 From: Martin Valigursky Date: Mon, 25 Nov 2024 11:29:37 +0000 Subject: [PATCH 8/9] renamed lastMipLevel -> blurLevel --- .../examples/graphics/clustered-area-lights.example.mjs | 2 +- .../src/examples/graphics/post-processing.controls.mjs | 6 +++--- .../src/examples/graphics/post-processing.example.mjs | 4 ++-- scripts/utils/camera-frame.mjs | 6 +++--- src/extras/render-passes/camera-frame.js | 8 +++++--- src/extras/render-passes/render-pass-bloom.js | 7 ++++--- 6 files changed, 18 insertions(+), 15 deletions(-) diff --git a/examples/src/examples/graphics/clustered-area-lights.example.mjs b/examples/src/examples/graphics/clustered-area-lights.example.mjs index f1d97bde4b6..42af210d94a 100644 --- a/examples/src/examples/graphics/clustered-area-lights.example.mjs +++ b/examples/src/examples/graphics/clustered-area-lights.example.mjs @@ -230,7 +230,7 @@ assetListLoader.load(() => { const cameraFrame = new pc.CameraFrame(app, camera.camera); cameraFrame.rendering.samples = 4; cameraFrame.bloom.intensity = 0.01; - cameraFrame.bloom.lastMipLevel = 4; + cameraFrame.bloom.blurLevel = 4; cameraFrame.update(); // if the device renders in HDR mode, disable tone mapping to output HDR values without any processing diff --git a/examples/src/examples/graphics/post-processing.controls.mjs b/examples/src/examples/graphics/post-processing.controls.mjs index 4bac61b5ae5..da87e2cc68e 100644 --- a/examples/src/examples/graphics/post-processing.controls.mjs +++ b/examples/src/examples/graphics/post-processing.controls.mjs @@ -86,12 +86,12 @@ export const controls = ({ observer, ReactPCUI, React, jsx, fragment }) => { ), jsx( LabelGroup, - { text: 'last mip level' }, + { text: 'blur level' }, jsx(SliderInput, { binding: new BindingTwoWay(), - link: { observer, path: 'data.bloom.lastMipLevel' }, + link: { observer, path: 'data.bloom.blurLevel' }, min: 1, - max: 10, + max: 16, precision: 0 }) ) diff --git a/examples/src/examples/graphics/post-processing.example.mjs b/examples/src/examples/graphics/post-processing.example.mjs index 38e2233c8f3..fe0872eeaf6 100644 --- a/examples/src/examples/graphics/post-processing.example.mjs +++ b/examples/src/examples/graphics/post-processing.example.mjs @@ -247,7 +247,7 @@ assetListLoader.load(() => { // Bloom cameraFrame.bloom.intensity = data.get('data.bloom.enabled') ? pc.math.lerp(0, 0.1, data.get('data.bloom.intensity') / 100) : 0; - cameraFrame.bloom.lastMipLevel = data.get('data.bloom.lastMipLevel'); + cameraFrame.bloom.blurLevel = data.get('data.bloom.blurLevel'); // grading cameraFrame.grading.enabled = data.get('data.grading.enabled'); @@ -284,7 +284,7 @@ assetListLoader.load(() => { bloom: { enabled: true, intensity: 5, - lastMipLevel: 1 + blurLevel: 16 }, grading: { enabled: false, diff --git a/scripts/utils/camera-frame.mjs b/scripts/utils/camera-frame.mjs index 10aed518149..24497a1767e 100644 --- a/scripts/utils/camera-frame.mjs +++ b/scripts/utils/camera-frame.mjs @@ -147,11 +147,11 @@ class Bloom { /** * @attribute - * @range [0, 12] + * @range [0, 16] * @precision 0 * @step 0 */ - lastMipLevel = 1; + blurLevel = 1; } /** @interface */ @@ -336,7 +336,7 @@ class CameraFrame extends Script { const dstBloom = cf.bloom; dstBloom.intensity = bloom.enabled ? bloom.intensity : 0; if (bloom.enabled) { - dstBloom.lastMipLevel = bloom.lastMipLevel; + dstBloom.blurLevel = bloom.blurLevel; } // grading diff --git a/src/extras/render-passes/camera-frame.js b/src/extras/render-passes/camera-frame.js index 88858df2fc5..eca69368f9f 100644 --- a/src/extras/render-passes/camera-frame.js +++ b/src/extras/render-passes/camera-frame.js @@ -81,7 +81,9 @@ import { CameraFrameOptions, RenderPassCameraFrame } from './render-pass-camera- * @typedef {Object} Bloom * @property {number} intensity - The intensity of the bloom effect, 0-0.1 range. Defaults to 0, * making it disabled. - * @property {number} lastMipLevel - The last mip level of the bloom effect, 0-12 range. Defaults to 1. + * @property {number} blurLevel - The number of iterations for blurring the bloom effect, with each + * level doubling the blur size. Once the blur size matches the dimensions of the render target, + * further blur passes are skipped. The default value is 16. */ /** @@ -187,7 +189,7 @@ class CameraFrame { */ bloom = { intensity: 0, - lastMipLevel: 1 + blurLevel: 16 }; /** @@ -345,7 +347,7 @@ class CameraFrame { if (options.bloomEnabled && bloomPass) { composePass.bloomIntensity = bloom.intensity; - bloomPass.lastMipLevel = bloom.lastMipLevel; + bloomPass.blurLevel = bloom.blurLevel; } if (options.ssaoType !== SSAOTYPE_NONE) { diff --git a/src/extras/render-passes/render-pass-bloom.js b/src/extras/render-passes/render-pass-bloom.js index 6f85693b7b3..ae91d86682b 100644 --- a/src/extras/render-passes/render-pass-bloom.js +++ b/src/extras/render-passes/render-pass-bloom.js @@ -7,6 +7,7 @@ import { FILTER_LINEAR, ADDRESS_CLAMP_TO_EDGE } from '../../platform/graphics/co import { RenderPassDownsample } from './render-pass-downsample.js'; import { RenderPassUpsample } from './render-pass-upsample.js'; +import { math } from '../../core/math/math.js'; // based on https://learnopengl.com/Guest-Articles/2022/Phys.-Based-Bloom /** @@ -18,7 +19,7 @@ import { RenderPassUpsample } from './render-pass-upsample.js'; class RenderPassBloom extends RenderPass { bloomTexture; - lastMipLevel = 1; + blurLevel = 16; bloomRenderTarget; @@ -151,8 +152,8 @@ class RenderPassBloom extends RenderPass { super.frameUpdate(); // create an appropriate amount of render passes - let numPasses = this.calcMipLevels(this._sourceTexture.width, this._sourceTexture.height, 2 ** this.lastMipLevel); - numPasses = Math.max(1, numPasses); + const maxNumPasses = this.calcMipLevels(this._sourceTexture.width, this._sourceTexture.height, 1); + const numPasses = math.clamp(maxNumPasses, 1, this.blurLevel); if (this.renderTargets.length !== numPasses) { From f6a3da13c7148c92c644ebf40d782586f8808baa Mon Sep 17 00:00:00 2001 From: Martin Valigursky Date: Mon, 25 Nov 2024 11:48:36 +0000 Subject: [PATCH 9/9] ssao constants docs --- src/extras/render-passes/constants.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/extras/render-passes/constants.js b/src/extras/render-passes/constants.js index 4058f1c5148..8626ddbe007 100644 --- a/src/extras/render-passes/constants.js +++ b/src/extras/render-passes/constants.js @@ -6,14 +6,18 @@ export const SSAOTYPE_NONE = 'none'; /** - * SSAO is applied during forward rendering. + * SSAO is applied during the lighting calculation stage, allowing it to blend seamlessly with scene + * lighting. This results in ambient occlusion being more pronounced in areas where direct light is + * obstructed, enhancing realism. * * @type {string} */ export const SSAOTYPE_LIGHTING = 'lighting'; /** - * SSAO is applied during postprocessing. + * SSAO is applied as a standalone effect after the scene is rendered. This method uniformly + * overlays ambient occlusion across the image, disregarding direct lighting interactions. While + * this may sacrifice some realism, it can be advantageous for achieving specific artistic styles. * * @type {string} */