UNPKG

@xeokit/xeokit-sdk

Version:

3D BIM IFC Viewer SDK for AEC engineering applications. Open Source JavaScript Toolkit based on pure WebGL for top performance, real-world coordinates and full double precision

544 lines (513 loc) 14.6 kB
import {Component} from '../Component.js'; import {WEBGL_INFO} from "../webglInfo.js"; /** * @desc Configures Scalable Ambient Obscurance (SAO) for a {@link Scene}. * * <a href="https://xeokit.github.io/xeokit-sdk/examples/index.html#postEffects_SAO_OTCConferenceCenter"><img src="http://xeokit.io/img/docs/SAO/saoEnabledDisabled.gif"></a> * * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/viewer/#sao_ConferenceCenter)] * * ## Overview * * SAO approximates [Ambient Occlusion](https://en.wikipedia.org/wiki/Ambient_occlusion) in realtime. It darkens creases, cavities and surfaces * that are close to each other, which tend to be occluded from ambient light and appear darker. * * The animated GIF above shows the effect as we repeatedly enable and disable SAO. When SAO is enabled, we can see darkening * in regions such as the corners, and the crevices between stairs. This increases the amount of detail we can see when ambient * light is high, or when objects have uniform colors across their surfaces. Run the example to experiment with the various * SAO configurations. * * xeokit's implementation of SAO is based on the paper [Scalable Ambient Obscurance](https://research.nvidia.com/sites/default/files/pubs/2012-06_Scalable-Ambient-Obscurance/McGuire12SAO.pdf). * * ## Caveats * * Currently, SAO only works with perspective and orthographic projections. Therefore, to use SAO, make sure {@link Camera#projection} is * either "perspective" or "ortho". * * {@link SAO#scale} and {@link SAO#intensity} must be tuned to the distance * between {@link Perspective#near} and {@link Perspective#far}, or the distance * between {@link Ortho#near} and {@link Ortho#far}, depending on which of those two projections the {@link Camera} is currently * using. Use the [live example](https://xeokit.github.io/xeokit-sdk/examples/viewer/#sao_ConferenceCenter) to get a * feel for that. * * ## Usage * * In the example below, we'll start by logging a warning message to the console if SAO is not supported by the * system. * *Then we'll enable and configure SAO, position the camera, and configure the near and far perspective and orthographic * clipping planes. Finally, we'll use {@link XKTLoaderPlugin} to load the OTC Conference Center model. * * ````javascript * import {Viewer, XKTLoaderPlugin} from "xeokit-sdk.es.js"; * * const viewer = new Viewer({ * canvasId: "myCanvas", * transparent: true * }); * * const sao = viewer.scene.sao; * * if (!sao.supported) { * sao.warn("SAO is not supported on this system - ignoring SAO configs") * } * * sao.enabled = true; // Enable SAO - only works if supported (see above) * sao.intensity = 0.15; * sao.bias = 0.5; * sao.scale = 1.0; * sao.minResolution = 0.0; * sao.numSamples = 10; * sao.kernelRadius = 100; * sao.blendCutoff = 0.1; * * const camera = viewer.scene.camera; * * camera.eye = [3.69, 5.83, -23.98]; * camera.look = [84.31, -29.88, -116.21]; * camera.up = [0.18, 0.96, -0.21]; * * camera.perspective.near = 0.1; * camera.perspective.far = 2000.0; * * camera.ortho.near = 0.1; * camera.ortho.far = 2000.0; * camera.projection = "perspective"; * * const xktLoader = new XKTLoaderPlugin(viewer); * * const model = xktLoader.load({ * id: "myModel", * src: "./models/xkt/OTCConferenceCenter.xkt" * edges: true * }); * ```` * * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/viewer/#sao_ConferenceCenter)] * * ## Efficiency * * SAO can incur some rendering overhead, especially on objects that are viewed close to the camera. For this reason, * it's recommended to use a low value for {@link SAO#kernelRadius}. A low radius will sample pixels that are close * to the source pixel, which will allow the GPU to efficiently cache those pixels. When {@link Camera#projection} is "perspective", * objects near to the viewpoint will use larger radii than farther pixels. Therefore, computing SAO for close objects * is more expensive than for objects far away, that occupy fewer pixels on the canvas. * * ## Selectively enabling SAO for models * * When loading multiple models into a Scene, we sometimes only want SAO on the models that are actually going to * show it, such as the architecture or structure, and not show SAO on models that won't show it well, such as the * electrical wiring, or plumbing. * * To illustrate, lets load some of the models for the West Riverside Hospital. We'll enable SAO on the structure model, * but disable it on the electrical and plumbing. * * This will only apply SAO to those models if {@link SAO#supported} and {@link SAO#enabled} are both true. * * Note, by the way, how we load the models in sequence. Since XKTLoaderPlugin uses scratch memory as part of its loading * process, this allows the plugin to reuse that same memory across multiple loads, instead of having to create multiple * pools of scratch memory. * * ````javascript * const structure = xktLoader.load({ * id: "structure", * src: "./models/xkt/WestRiverSideHospital/structure.xkt" * edges: true, * saoEnabled: true * }); * * structure.on("loaded", () => { * * const electrical = xktLoader.load({ * id: "electrical", * src: "./models/xkt/WestRiverSideHospital/electrical.xkt", * edges: true * }); * * electrical.on("loaded", () => { * * const plumbing = xktLoader.load({ * id: "plumbing", * src: "./models/xkt/WestRiverSideHospital/plumbing.xkt", * edges: true * }); * }); * }); * ```` * * ## Disabling SAO while camera is moving * * For smoother interaction with large models on low-power hardware, we can disable SAO while the {@link Camera} is moving: * * ````javascript * const timeoutDuration = 150; // Milliseconds * var timer = timeoutDuration; * var saoDisabled = false; * * const onCameraMatrix = scene.camera.on("matrix", () => { * timer = timeoutDuration; * if (!saoDisabled) { * scene.sao.enabled = false; * saoDisabled = true; * } * }); * * const onSceneTick = scene.on("tick", (tickEvent) => { * if (!saoDisabled) { * return; * } * timer -= tickEvent.deltaTime; // Milliseconds * if (timer <= 0) { * if (saoDisabled) { * scene.sao.enabled = true; * saoDisabled = false; * } * } * }); * ```` * * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/index.html#techniques_nonInteractiveQuality)] */ class SAO extends Component { /** @private */ constructor(owner, cfg = {}) { super(owner, cfg); this._supported = WEBGL_INFO.SUPPORTED_EXTENSIONS["OES_standard_derivatives"]; // For computing normals in SAO fragment shader this.enabled = cfg.enabled; this.kernelRadius = cfg.kernelRadius; this.intensity = cfg.intensity; this.bias = cfg.bias; this.scale = cfg.scale; this.minResolution = cfg.minResolution; this.numSamples = cfg.numSamples; this.blur = cfg.blur; this.blendCutoff = cfg.blendCutoff; this.blendFactor = cfg.blendFactor; } /** * Gets whether or not SAO is supported by this browser and GPU. * * Even when enabled, SAO will only work if supported. * * @type {Boolean} */ get supported() { return this._supported; } /** * Sets whether SAO is enabled for the {@link Scene}. * * Even when enabled, SAO will only work if supported. * * Default value is ````false````. * * @type {Boolean} */ set enabled(value) { value = !!value; if (this._enabled === value) { return; } this._enabled = value; this.glRedraw(); } /** * Gets whether SAO is enabled for the {@link Scene}. * * Even when enabled, SAO will only apply if supported. * * Default value is ````false````. * * @type {Boolean} */ get enabled() { return this._enabled; } /** * Returns true if SAO is currently possible, where it is supported, enabled, and the current scene state is compatible. * Called internally by renderer logic. * @private * @returns {Boolean} */ get possible() { if (!this._supported) { return false; } if (!this._enabled) { return false; } const projection = this.scene.camera.projection; if (projection === "customProjection") { return false; } if (projection === "frustum") { return false; } return true; } /** * @private * @returns {boolean|*} */ get active() { return this._active; } /** * Sets the maximum area that SAO takes into account when checking for possible occlusion for each fragment. * * Default value is ````100.0````. * * @type {Number} */ set kernelRadius(value) { if (value === undefined || value === null) { value = 100.0; } if (this._kernelRadius === value) { return; } this._kernelRadius = value; this.glRedraw(); } /** * Gets the maximum area that SAO takes into account when checking for possible occlusion for each fragment. * * Default value is ````100.0````. * * @type {Number} */ get kernelRadius() { return this._kernelRadius; } /** * Sets the degree of darkening (ambient obscurance) produced by the SAO effect. * * Default value is ````0.15````. * * @type {Number} */ set intensity(value) { if (value === undefined || value === null) { value = 0.15; } if (this._intensity === value) { return; } this._intensity = value; this.glRedraw(); } /** * Gets the degree of darkening (ambient obscurance) produced by the SAO effect. * * Default value is ````0.15````. * * @type {Number} */ get intensity() { return this._intensity; } /** * Sets the SAO bias. * * Default value is ````0.5````. * * @type {Number} */ set bias(value) { if (value === undefined || value === null) { value = 0.5; } if (this._bias === value) { return; } this._bias = value; this.glRedraw(); } /** * Gets the SAO bias. * * Default value is ````0.5````. * * @type {Number} */ get bias() { return this._bias; } /** * Sets the SAO occlusion scale. * * Default value is ````1.0````. * * @type {Number} */ set scale(value) { if (value === undefined || value === null) { value = 1.0; } if (this._scale === value) { return; } this._scale = value; this.glRedraw(); } /** * Gets the SAO occlusion scale. * * Default value is ````1.0````. * * @type {Number} */ get scale() { return this._scale; } /** * Sets the SAO minimum resolution. * * Default value is ````0.0````. * * @type {Number} */ set minResolution(value) { if (value === undefined || value === null) { value = 0.0; } if (this._minResolution === value) { return; } this._minResolution = value; this.glRedraw(); } /** * Gets the SAO minimum resolution. * * Default value is ````0.0````. * * @type {Number} */ get minResolution() { return this._minResolution; } /** * Sets the number of SAO samples. * * Default value is ````10````. * * Update this sparingly, since it causes a shader recompile. * * @type {Number} */ set numSamples(value) { if (value === undefined || value === null) { value = 10; } if (this._numSamples === value) { return; } this._numSamples = value; this.glRedraw(); } /** * Gets the number of SAO samples. * * Default value is ````10````. * * @type {Number} */ get numSamples() { return this._numSamples; } /** * Sets whether Guassian blur is enabled. * * Default value is ````true````. * * @type {Boolean} */ set blur(value) { value = (value !== false); if (this._blur === value) { return; } this._blur = value; this.glRedraw(); } /** * Gets whether Guassian blur is enabled. * * Default value is ````true````. * * @type {Boolean} */ get blur() { return this._blur; } /** * Sets the SAO blend cutoff. * * Default value is ````0.3````. * * Normally you don't need to alter this. * * @type {Number} */ set blendCutoff(value) { if (value === undefined || value === null) { value = 0.3; } if (this._blendCutoff === value) { return; } this._blendCutoff = value; this.glRedraw(); } /** * Gets the SAO blend cutoff. * * Default value is ````0.3````. * * Normally you don't need to alter this. * * @type {Number} */ get blendCutoff() { return this._blendCutoff; } /** * Sets the SAO blend factor. * * Default value is ````1.0````. * * Normally you don't need to alter this. * * @type {Number} */ set blendFactor(value) { if (value === undefined || value === null) { value = 1.0; } if (this._blendFactor === value) { return; } this._blendFactor = value; this.glRedraw(); } /** * Gets the SAO blend scale. * * Default value is ````1.0````. * * Normally you don't need to alter this. * * @type {Number} */ get blendFactor() { return this._blendFactor; } /** * Destroys this component. */ destroy() { super.destroy(); } } export {SAO};