UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

483 lines (480 loc) 22.8 kB
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 { FramePassCameraFrame, CameraFrameOptions } from './frame-pass-camera-frame.js'; /** * @import { AppBase } from '../../framework/app-base.js' * @import { CameraComponent } from '../../framework/components/camera/component.js' * @import { Texture } from '../../platform/graphics/texture.js' */ /** * @typedef {Object} Rendering * Properties related to scene rendering, encompassing settings that control the rendering resolution, * pixel format, multi-sampling for anti-aliasing, tone-mapping and similar. * @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 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 * 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. Can be: * * - {@link TONEMAP_LINEAR} * - {@link TONEMAP_FILMIC} * - {@link TONEMAP_HEJL} * - {@link TONEMAP_ACES} * - {@link TONEMAP_ACES2} * - {@link TONEMAP_NEUTRAL} * * Defaults to {@link TONEMAP_LINEAR}. * @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. */ /** * @typedef {Object} Ssao * 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. * @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 {boolean} randomize - Whether the SSAO sampling is randomized. Useful when used instead * of blur effect together with TAA. Defaults to false. * @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 * 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. * @property {number} intensity - The intensity of the bloom effect, 0-0.1 range. Defaults to 0, * making it disabled. * @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. */ /** * @typedef {Object} Grading * 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. * @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} ColorLUT * Properties related to the color lookup table (LUT) effect, a postprocessing technique used to * apply a color transformation to the image. * @property {Texture|null} texture - The LUT texture. This must be a 2D "horizontal strip" texture * representing an unwrapped 3D LUT (the same format used by Unreal Engine). For an N×N×N 3D LUT, * the texture dimensions are N² × N pixels (width × height). For example, a 16×16×16 LUT uses a * 256×16 texture, and a 32×32×32 LUT uses a 1024×32 texture. The texture contains N horizontal * slices representing the blue channel, with each slice mapping red to the X-axis and green to * the Y-axis. Note that HALD LUTs (e.g. from ImageMagick) and Unity LUTs use different layouts * and are not compatible. Defaults to null. * @property {number} intensity - The intensity of the color LUT effect. Defaults to 1. */ /** * @typedef {Object} Vignette * 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. * @property {number} intensity - The intensity of the vignette effect, 0-1 range. Defaults to 0, * making it disabled. * @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. * @property {Color} color - The color of the vignette effect. Defaults to black. */ /** * @typedef {Object} Fringing * 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. * @property {number} intensity - The intensity of the fringing effect, 0-100 range. Defaults to 0, * making it disabled. */ /** * @typedef {Object} ColorEnhance * Properties related to the color enhancement effect, a postprocessing technique that provides * HDR-aware adjustments for shadows, highlights, vibrance, and dehaze. Shadows and highlights allow * selective adjustment of dark and bright areas of the image, vibrance is a smart saturation * that boosts less-saturated colors more than already-saturated ones, and dehaze removes atmospheric * haze to increase clarity and contrast. * @property {boolean} enabled - Whether color enhancement is enabled. Defaults to false. * @property {number} shadows - The shadow adjustment, -3 to 3 range. Uses an exponential curve where * -3 gives 0.125x, 0 gives 1x, and +3 gives 8x brightness on dark areas. Defaults to 0. * @property {number} highlights - The highlight adjustment, -3 to 3 range. Uses an exponential curve * where -3 gives 0.125x, 0 gives 1x, and +3 gives 8x brightness on bright areas. Defaults to 0. * @property {number} vibrance - The vibrance (smart saturation), -1 to 1 range. Positive values boost * saturation of less-saturated colors more than already-saturated ones. Negative values desaturate. * Defaults to 0. * @property {number} midtones - The midtone adjustment, -1 to 1 range. Positive values brighten * midtones, negative values darken midtones, with shadows and highlights more strongly preserved * than by a linear exposure change. Defaults to 0. * @property {number} dehaze - The dehaze adjustment, -1 to 1 range. Positive values remove atmospheric * haze, increasing clarity and contrast. Negative values add a haze effect. Defaults to 0. */ /** * @typedef {Object} Taa * 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. * @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, * 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. */ /** * @typedef {Object} Dof * Properties related to Depth of Field (DOF), a technique used to simulate the optical effect where * objects at certain distances appear sharp while others are blurred, enhancing the perception of * focus and depth in the rendered scene. * @property {boolean} enabled - Whether DoF is enabled. Defaults to false. * @property {boolean} nearBlur - Whether the near blur is enabled. Defaults to false. * @property {number} focusDistance - The distance at which the focus is set. Defaults to 100. * @property {number} focusRange - The range around the focus distance where the focus is sharp. * Defaults to 10. * @property {number} blurRadius - The radius of the blur effect, typically 2-10 range. Defaults to 3. * @property {number} blurRings - The number of rings in the blur effect, typically 3-8 range. Defaults * to 4. * @property {number} blurRingPoints - The number of points in each ring of the blur effect, typically * 3-8 range. Defaults to 5. * @property {boolean} highQuality - Whether the high quality implementation is used. This will have * a higher performance cost, but will produce better quality results. Defaults to true. */ /** * Implementation of a simple to use camera rendering pass, which supports SSAO, Bloom and * other rendering effects. * * Overriding compose shader chunks: * The final compose pass registers its shader chunks in a way that does not override any chunks * that were already provided. To customize the compose pass output, set your shader chunks on the * {@link ShaderChunks} map before creating the `CameraFrame`. Those chunks will be picked up by * the compose pass and preserved. * * Example (GLSL): * * @example * // Provide custom compose chunk(s) before constructing CameraFrame * ShaderChunks.get(graphicsDevice, SHADERLANGUAGE_GLSL).set('composeVignettePS', ` * #ifdef VIGNETTE * vec3 applyVignette(vec3 color, vec2 uv) { * return color * uv.u; * } * #endif * `); * * // For WebGPU, use SHADERLANGUAGE_WGSL instead. * * @category Graphics */ class CameraFrame { /** * Destroys the camera frame, removing all render passes. */ destroy() { this.disable(); this.cameraLayersChanged.off(); } enable() { this.renderPassCamera = this.createRenderPass(); this.cameraComponent.framePasses = [ this.renderPassCamera ]; } disable() { const cameraComponent = this.cameraComponent; cameraComponent.framePasses?.forEach((renderPass)=>{ renderPass.destroy(); }); cameraComponent.framePasses = []; cameraComponent.rendering = null; cameraComponent.jitter = 0; // disable SSAO included in the lighting pass cameraComponent.shaderParams.ssaoEnabled = false; this.renderPassCamera = null; } /** * Creates a frame pass for the camera frame. Override this method to utilize a custom frame * pass, typically one that extends {@link FramePassCameraFrame}. * * @returns {FramePassCameraFrame} - The frame pass. */ createRenderPass() { return new FramePassCameraFrame(this.app, this, this.cameraComponent, this.options); } /** * Sets the enabled state of the camera frame. Passing false will release associated resources. * * @type {boolean} */ set enabled(value) { if (this._enabled !== value) { if (value) { this.enable(); } else { this.disable(); } this._enabled = value; } } /** * Gets the enabled state of the camera frame. * * @type {boolean} */ get enabled() { return this._enabled; } 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(); options.dofEnabled = this.dof.enabled; options.dofNearBlur = this.dof.nearBlur; options.dofHighQuality = this.dof.highQuality; } /** * 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, colorEnhance, 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, dofPass } = 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.blurLevel = bloom.blurLevel; } if (options.dofEnabled) { dofPass.focusDistance = this.dof.focusDistance; dofPass.focusRange = this.dof.focusRange; dofPass.blurRadius = this.dof.blurRadius; dofPass.blurRings = this.dof.blurRings; dofPass.blurRingPoints = this.dof.blurRingPoints; } 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; ssaoPass.randomize = ssao.randomize; } composePass.gradingEnabled = grading.enabled; if (grading.enabled) { composePass.gradingSaturation = grading.saturation; composePass.gradingBrightness = grading.brightness; composePass.gradingContrast = grading.contrast; composePass.gradingTint = grading.tint; } composePass.colorLUT = this.colorLUT.texture; composePass.colorLUTIntensity = this.colorLUT.intensity; 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.vignetteColor.copy(vignette.color); } composePass.fringingEnabled = fringing.intensity > 0; if (composePass.fringingEnabled) { composePass.fringingIntensity = fringing.intensity; } composePass.colorEnhanceEnabled = colorEnhance.enabled; if (colorEnhance.enabled) { composePass.colorEnhanceShadows = colorEnhance.shadows; composePass.colorEnhanceHighlights = colorEnhance.highlights; composePass.colorEnhanceVibrance = colorEnhance.vibrance; composePass.colorEnhanceMidtones = colorEnhance.midtones; composePass.colorEnhanceDehaze = colorEnhance.dehaze; } // enable camera jitter if taa is enabled cameraComponent.jitter = taa.enabled ? taa.jitter : 0; // debug rendering composePass.debug = this.debug; if (composePass.debug === 'ssao' && options.ssaoType === SSAOTYPE_NONE) composePass.debug = null; if (composePass.debug === 'vignette' && !composePass.vignetteEnabled) composePass.debug = null; } /** * Creates a new CameraFrame instance. * * @param {AppBase} app - The application. * @param {CameraComponent} cameraComponent - The camera component. */ constructor(app, cameraComponent){ /** @private */ this._enabled = true; /** * Rendering settings. * * @type {Rendering} */ this.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} */ this.ssao = { type: SSAOTYPE_NONE, blurEnabled: true, randomize: false, intensity: 0.5, radius: 30, samples: 12, power: 6, minAngle: 10, scale: 1 }; /** * Bloom settings. * * @type {Bloom} */ this.bloom = { intensity: 0, blurLevel: 16 }; /** * Grading settings. * * @type {Grading} */ this.grading = { enabled: false, brightness: 1, contrast: 1, saturation: 1, tint: new Color(1, 1, 1, 1) }; /** * Color LUT settings. * * @type {ColorLUT} */ this.colorLUT = { texture: null, intensity: 1 }; /** * Vignette settings. * * @type {Vignette} */ this.vignette = { intensity: 0, inner: 0.5, outer: 1, curvature: 0.5, color: new Color(0, 0, 0) }; /** * Taa settings. * * @type {Taa} */ this.taa = { enabled: false, jitter: 1 }; /** * Fringing settings. * * @type {Fringing} */ this.fringing = { intensity: 0 }; /** * Color enhancement settings. * * @type {ColorEnhance} */ this.colorEnhance = { enabled: false, shadows: 0, highlights: 0, vibrance: 0, midtones: 0, dehaze: 0 }; /** * DoF settings. * * @type {Dof} */ this.dof = { enabled: false, nearBlur: false, focusDistance: 100, focusRange: 10, blurRadius: 3, blurRings: 4, blurRingPoints: 5, highQuality: true }; /** * Debug rendering. Set to null to disable. * * @type {null|'scene'|'ssao'|'bloom'|'vignette'|'dofcoc'|'dofblur'} */ this.debug = null; this.options = new CameraFrameOptions(); /** * @type {FramePassCameraFrame|null} * @private */ this.renderPassCamera = null; this.app = app; this.cameraComponent = cameraComponent; Debug.assert(cameraComponent, 'CameraFrame: cameraComponent must be defined'); this.updateOptions(); this.enable(); // handle layer changes on the camera - render passes need to be update to reflect the changes this.cameraLayersChanged = cameraComponent.on('set:layers', ()=>{ if (this.renderPassCamera) this.renderPassCamera.layersDirty = true; }); } } export { CameraFrame };