playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
483 lines (480 loc) • 22.8 kB
JavaScript
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 };