@openhps/core
Version:
Open Hybrid Positioning System - Core component
1,349 lines (1,212 loc) • 107 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.WebGLRenderer = void 0;
var _constants = require("../constants.js");
var _Color = require("../math/Color.js");
var _Frustum = require("../math/Frustum.js");
var _Matrix = require("../math/Matrix4.js");
var _Vector = require("../math/Vector3.js");
var _Vector2 = require("../math/Vector4.js");
var _WebGLAnimation = require("./webgl/WebGLAnimation.js");
var _WebGLAttributes = require("./webgl/WebGLAttributes.js");
var _WebGLBackground = require("./webgl/WebGLBackground.js");
var _WebGLBindingStates = require("./webgl/WebGLBindingStates.js");
var _WebGLBufferRenderer = require("./webgl/WebGLBufferRenderer.js");
var _WebGLCapabilities = require("./webgl/WebGLCapabilities.js");
var _WebGLClipping = require("./webgl/WebGLClipping.js");
var _WebGLCubeMaps = require("./webgl/WebGLCubeMaps.js");
var _WebGLCubeUVMaps = require("./webgl/WebGLCubeUVMaps.js");
var _WebGLExtensions = require("./webgl/WebGLExtensions.js");
var _WebGLGeometries = require("./webgl/WebGLGeometries.js");
var _WebGLIndexedBufferRenderer = require("./webgl/WebGLIndexedBufferRenderer.js");
var _WebGLInfo = require("./webgl/WebGLInfo.js");
var _WebGLMorphtargets = require("./webgl/WebGLMorphtargets.js");
var _WebGLObjects = require("./webgl/WebGLObjects.js");
var _WebGLPrograms = require("./webgl/WebGLPrograms.js");
var _WebGLProperties = require("./webgl/WebGLProperties.js");
var _WebGLRenderLists = require("./webgl/WebGLRenderLists.js");
var _WebGLRenderStates = require("./webgl/WebGLRenderStates.js");
var _WebGLRenderTarget = require("./WebGLRenderTarget.js");
var _WebGLShadowMap = require("./webgl/WebGLShadowMap.js");
var _WebGLState = require("./webgl/WebGLState.js");
var _WebGLTextures = require("./webgl/WebGLTextures.js");
var _WebGLUniforms = require("./webgl/WebGLUniforms.js");
var _WebGLUtils = require("./webgl/WebGLUtils.js");
var _WebXRManager = require("./webxr/WebXRManager.js");
var _WebGLMaterials = require("./webgl/WebGLMaterials.js");
var _WebGLUniformsGroups = require("./webgl/WebGLUniformsGroups.js");
var _utils = require("../utils.js");
var _ColorManagement = require("../math/ColorManagement.js");
/**
* This renderer uses WebGL 2 to display scenes.
*
* WebGL 1 is not supported since `r163`.
*/
class WebGLRenderer {
/**
* Constructs a new WebGL renderer.
*
* @param {WebGLRenderer~Options} [parameters] - The configuration parameter.
*/
constructor(parameters = {}) {
const {
canvas = (0, _utils.createCanvasElement)(),
context = null,
depth = true,
stencil = false,
alpha = false,
antialias = false,
premultipliedAlpha = true,
preserveDrawingBuffer = false,
powerPreference = 'default',
failIfMajorPerformanceCaveat = false,
reverseDepthBuffer = false
} = parameters;
/**
* This flag can be used for type testing.
*
* @type {boolean}
* @readonly
* @default true
*/
this.isWebGLRenderer = true;
let _alpha;
if (context !== null) {
if (typeof WebGLRenderingContext !== 'undefined' && context instanceof WebGLRenderingContext) {
throw new Error('THREE.WebGLRenderer: WebGL 1 is not supported since r163.');
}
_alpha = context.getContextAttributes().alpha;
} else {
_alpha = alpha;
}
const uintClearColor = new Uint32Array(4);
const intClearColor = new Int32Array(4);
let currentRenderList = null;
let currentRenderState = null;
// render() can be called from within a callback triggered by another render.
// We track this so that the nested render call gets its list and state isolated from the parent render call.
const renderListStack = [];
const renderStateStack = [];
// public properties
/**
* A canvas where the renderer draws its output.This is automatically created by the renderer
* in the constructor (if not provided already); you just need to add it to your page like so:
* ```js
* document.body.appendChild( renderer.domElement );
* ```
*
* @type {DOMElement}
*/
this.domElement = canvas;
/**
* A object with debug configuration settings.
*
* - `checkShaderErrors`: If it is `true`, defines whether material shader programs are
* checked for errors during compilation and linkage process. It may be useful to disable
* this check in production for performance gain. It is strongly recommended to keep these
* checks enabled during development. If the shader does not compile and link - it will not
* work and associated material will not render.
* - `onShaderError(gl, program, glVertexShader,glFragmentShader)`: A callback function that
* can be used for custom error reporting. The callback receives the WebGL context, an instance
* of WebGLProgram as well two instances of WebGLShader representing the vertex and fragment shader.
* Assigning a custom function disables the default error reporting.
*
* @type {Object}
*/
this.debug = {
/**
* Enables error checking and reporting when shader programs are being compiled.
* @type {boolean}
*/
checkShaderErrors: true,
/**
* Callback for custom error reporting.
* @type {?Function}
*/
onShaderError: null
};
// clearing
/**
* Whether the renderer should automatically clear its output before rendering a frame or not.
*
* @type {boolean}
* @default true
*/
this.autoClear = true;
/**
* If {@link WebGLRenderer#autoClear} set to `true`, whether the renderer should clear
* the color buffer or not.
*
* @type {boolean}
* @default true
*/
this.autoClearColor = true;
/**
* If {@link WebGLRenderer#autoClear} set to `true`, whether the renderer should clear
* the depth buffer or not.
*
* @type {boolean}
* @default true
*/
this.autoClearDepth = true;
/**
* If {@link WebGLRenderer#autoClear} set to `true`, whether the renderer should clear
* the stencil buffer or not.
*
* @type {boolean}
* @default true
*/
this.autoClearStencil = true;
// scene graph
/**
* Whether the renderer should sort objects or not.
*
* Note: Sorting is used to attempt to properly render objects that have some
* degree of transparency. By definition, sorting objects may not work in all
* cases. Depending on the needs of application, it may be necessary to turn
* off sorting and use other methods to deal with transparency rendering e.g.
* manually determining each object's rendering order.
*
* @type {boolean}
* @default true
*/
this.sortObjects = true;
// user-defined clipping
/**
* User-defined clipping planes specified in world space. These planes apply globally.
* Points in space whose dot product with the plane is negative are cut away.
*
* @type {Array<Plane>}
*/
this.clippingPlanes = [];
/**
* Whether the renderer respects object-level clipping planes or not.
*
* @type {boolean}
* @default false
*/
this.localClippingEnabled = false;
// tone mapping
/**
* The tone mapping technique of the renderer.
*
* @type {(NoToneMapping|LinearToneMapping|ReinhardToneMapping|CineonToneMapping|ACESFilmicToneMapping|CustomToneMapping|AgXToneMapping|NeutralToneMapping)}
* @default NoToneMapping
*/
this.toneMapping = _constants.NoToneMapping;
/**
* Exposure level of tone mapping.
*
* @type {number}
* @default 1
*/
this.toneMappingExposure = 1.0;
// transmission
/**
* The normalized resolution scale for the transmission render target, measured in percentage
* of viewport dimensions. Lowering this value can result in significant performance improvements
* when using {@link MeshPhysicalMaterial#transmission}.
*
* @type {number}
* @default 1
*/
this.transmissionResolutionScale = 1.0;
// internal properties
const _this = this;
let _isContextLost = false;
// internal state cache
this._outputColorSpace = _constants.SRGBColorSpace;
let _currentActiveCubeFace = 0;
let _currentActiveMipmapLevel = 0;
let _currentRenderTarget = null;
let _currentMaterialId = -1;
let _currentCamera = null;
const _currentViewport = new _Vector2.Vector4();
const _currentScissor = new _Vector2.Vector4();
let _currentScissorTest = null;
const _currentClearColor = new _Color.Color(0x000000);
let _currentClearAlpha = 0;
//
let _width = canvas.width;
let _height = canvas.height;
let _pixelRatio = 1;
let _opaqueSort = null;
let _transparentSort = null;
const _viewport = new _Vector2.Vector4(0, 0, _width, _height);
const _scissor = new _Vector2.Vector4(0, 0, _width, _height);
let _scissorTest = false;
// frustum
const _frustum = new _Frustum.Frustum();
// clipping
let _clippingEnabled = false;
let _localClippingEnabled = false;
// camera matrices cache
const _currentProjectionMatrix = new _Matrix.Matrix4();
const _projScreenMatrix = new _Matrix.Matrix4();
const _vector3 = new _Vector.Vector3();
const _vector4 = new _Vector2.Vector4();
const _emptyScene = {
background: null,
fog: null,
environment: null,
overrideMaterial: null,
isScene: true
};
let _renderBackground = false;
function getTargetPixelRatio() {
return _currentRenderTarget === null ? _pixelRatio : 1;
}
// initialize
let _gl = context;
function getContext(contextName, contextAttributes) {
return canvas.getContext(contextName, contextAttributes);
}
try {
const contextAttributes = {
alpha: true,
depth,
stencil,
antialias,
premultipliedAlpha,
preserveDrawingBuffer,
powerPreference,
failIfMajorPerformanceCaveat
};
// OffscreenCanvas does not have setAttribute, see #22811
if ('setAttribute' in canvas) canvas.setAttribute('data-engine', `three.js r${_constants.REVISION}`);
// event listeners must be registered before WebGL context is created, see #12753
canvas.addEventListener('webglcontextlost', onContextLost, false);
canvas.addEventListener('webglcontextrestored', onContextRestore, false);
canvas.addEventListener('webglcontextcreationerror', onContextCreationError, false);
if (_gl === null) {
const contextName = 'webgl2';
_gl = getContext(contextName, contextAttributes);
if (_gl === null) {
if (getContext(contextName)) {
throw new Error('Error creating WebGL context with your selected attributes.');
} else {
throw new Error('Error creating WebGL context.');
}
}
}
} catch (error) {
console.error('THREE.WebGLRenderer: ' + error.message);
throw error;
}
let extensions, capabilities, state, info;
let properties, textures, cubemaps, cubeuvmaps, attributes, geometries, objects;
let programCache, materials, renderLists, renderStates, clipping, shadowMap;
let background, morphtargets, bufferRenderer, indexedBufferRenderer;
let utils, bindingStates, uniformsGroups;
function initGLContext() {
extensions = new _WebGLExtensions.WebGLExtensions(_gl);
extensions.init();
utils = new _WebGLUtils.WebGLUtils(_gl, extensions);
capabilities = new _WebGLCapabilities.WebGLCapabilities(_gl, extensions, parameters, utils);
state = new _WebGLState.WebGLState(_gl, extensions);
if (capabilities.reverseDepthBuffer && reverseDepthBuffer) {
state.buffers.depth.setReversed(true);
}
info = new _WebGLInfo.WebGLInfo(_gl);
properties = new _WebGLProperties.WebGLProperties();
textures = new _WebGLTextures.WebGLTextures(_gl, extensions, state, properties, capabilities, utils, info);
cubemaps = new _WebGLCubeMaps.WebGLCubeMaps(_this);
cubeuvmaps = new _WebGLCubeUVMaps.WebGLCubeUVMaps(_this);
attributes = new _WebGLAttributes.WebGLAttributes(_gl);
bindingStates = new _WebGLBindingStates.WebGLBindingStates(_gl, attributes);
geometries = new _WebGLGeometries.WebGLGeometries(_gl, attributes, info, bindingStates);
objects = new _WebGLObjects.WebGLObjects(_gl, geometries, attributes, info);
morphtargets = new _WebGLMorphtargets.WebGLMorphtargets(_gl, capabilities, textures);
clipping = new _WebGLClipping.WebGLClipping(properties);
programCache = new _WebGLPrograms.WebGLPrograms(_this, cubemaps, cubeuvmaps, extensions, capabilities, bindingStates, clipping);
materials = new _WebGLMaterials.WebGLMaterials(_this, properties);
renderLists = new _WebGLRenderLists.WebGLRenderLists();
renderStates = new _WebGLRenderStates.WebGLRenderStates(extensions);
background = new _WebGLBackground.WebGLBackground(_this, cubemaps, cubeuvmaps, state, objects, _alpha, premultipliedAlpha);
shadowMap = new _WebGLShadowMap.WebGLShadowMap(_this, objects, capabilities);
uniformsGroups = new _WebGLUniformsGroups.WebGLUniformsGroups(_gl, info, capabilities, state);
bufferRenderer = new _WebGLBufferRenderer.WebGLBufferRenderer(_gl, extensions, info);
indexedBufferRenderer = new _WebGLIndexedBufferRenderer.WebGLIndexedBufferRenderer(_gl, extensions, info);
info.programs = programCache.programs;
/**
* Holds details about the capabilities of the current rendering context.
*
* @name WebGLRenderer#capabilities
* @type {WebGLRenderer~Capabilities}
*/
_this.capabilities = capabilities;
/**
* Provides methods for retrieving and testing WebGL extensions.
*
* - `get(extensionName:string)`: Used to check whether a WebGL extension is supported
* and return the extension object if available.
* - `has(extensionName:string)`: returns `true` if the extension is supported.
*
* @name WebGLRenderer#extensions
* @type {Object}
*/
_this.extensions = extensions;
/**
* Used to track properties of other objects like native WebGL objects.
*
* @name WebGLRenderer#properties
* @type {Object}
*/
_this.properties = properties;
/**
* Manages the render lists of the renderer.
*
* @name WebGLRenderer#renderLists
* @type {Object}
*/
_this.renderLists = renderLists;
/**
* Interface for managing shadows.
*
* @name WebGLRenderer#shadowMap
* @type {WebGLRenderer~ShadowMap}
*/
_this.shadowMap = shadowMap;
/**
* Interface for managing the WebGL state.
*
* @name WebGLRenderer#state
* @type {Object}
*/
_this.state = state;
/**
* Holds a series of statistical information about the GPU memory
* and the rendering process. Useful for debugging and monitoring.
*
* By default these data are reset at each render call but when having
* multiple render passes per frame (e.g. when using post processing) it can
* be preferred to reset with a custom pattern. First, set `autoReset` to
* `false`.
* ```js
* renderer.info.autoReset = false;
* ```
* Call `reset()` whenever you have finished to render a single frame.
* ```js
* renderer.info.reset();
* ```
*
* @name WebGLRenderer#info
* @type {WebGLRenderer~Info}
*/
_this.info = info;
}
initGLContext();
// xr
const xr = new _WebXRManager.WebXRManager(_this, _gl);
/**
* A reference to the XR manager.
*
* @type {WebXRManager}
*/
this.xr = xr;
/**
* Returns the rendering context.
*
* @return {WebGL2RenderingContext} The rendering context.
*/
this.getContext = function () {
return _gl;
};
/**
* Returns the rendering context attributes.
*
* @return {WebGLContextAttributes} The rendering context attributes.
*/
this.getContextAttributes = function () {
return _gl.getContextAttributes();
};
/**
* Simulates a loss of the WebGL context. This requires support for the `WEBGL_lose_context` extension.
*/
this.forceContextLoss = function () {
const extension = extensions.get('WEBGL_lose_context');
if (extension) extension.loseContext();
};
/**
* Simulates a restore of the WebGL context. This requires support for the `WEBGL_lose_context` extension.
*/
this.forceContextRestore = function () {
const extension = extensions.get('WEBGL_lose_context');
if (extension) extension.restoreContext();
};
/**
* Returns the pixel ratio.
*
* @return {number} The pixel ratio.
*/
this.getPixelRatio = function () {
return _pixelRatio;
};
/**
* Sets the given pixel ratio and resizes the canvas if necessary.
*
* @param {number} value - The pixel ratio.
*/
this.setPixelRatio = function (value) {
if (value === undefined) return;
_pixelRatio = value;
this.setSize(_width, _height, false);
};
/**
* Returns the renderer's size in logical pixels. This method does not honor the pixel ratio.
*
* @param {Vector2} target - The method writes the result in this target object.
* @return {Vector2} The renderer's size in logical pixels.
*/
this.getSize = function (target) {
return target.set(_width, _height);
};
/**
* Resizes the output canvas to (width, height) with device pixel ratio taken
* into account, and also sets the viewport to fit that size, starting in (0,
* 0). Setting `updateStyle` to false prevents any style changes to the output canvas.
*
* @param {number} width - The width in logical pixels.
* @param {number} height - The height in logical pixels.
* @param {boolean} [updateStyle=true] - Whether to update the `style` attribute of the canvas or not.
*/
this.setSize = function (width, height, updateStyle = true) {
if (xr.isPresenting) {
console.warn('THREE.WebGLRenderer: Can\'t change size while VR device is presenting.');
return;
}
_width = width;
_height = height;
canvas.width = Math.floor(width * _pixelRatio);
canvas.height = Math.floor(height * _pixelRatio);
if (updateStyle === true) {
canvas.style.width = width + 'px';
canvas.style.height = height + 'px';
}
this.setViewport(0, 0, width, height);
};
/**
* Returns the drawing buffer size in physical pixels. This method honors the pixel ratio.
*
* @param {Vector2} target - The method writes the result in this target object.
* @return {Vector2} The drawing buffer size.
*/
this.getDrawingBufferSize = function (target) {
return target.set(_width * _pixelRatio, _height * _pixelRatio).floor();
};
/**
* This method allows to define the drawing buffer size by specifying
* width, height and pixel ratio all at once. The size of the drawing
* buffer is computed with this formula:
* ```js
* size.x = width * pixelRatio;
* size.y = height * pixelRatio;
* ```
*
* @param {number} width - The width in logical pixels.
* @param {number} height - The height in logical pixels.
* @param {number} pixelRatio - The pixel ratio.
*/
this.setDrawingBufferSize = function (width, height, pixelRatio) {
_width = width;
_height = height;
_pixelRatio = pixelRatio;
canvas.width = Math.floor(width * pixelRatio);
canvas.height = Math.floor(height * pixelRatio);
this.setViewport(0, 0, width, height);
};
/**
* Returns the current viewport definition.
*
* @param {Vector2} target - The method writes the result in this target object.
* @return {Vector2} The current viewport definition.
*/
this.getCurrentViewport = function (target) {
return target.copy(_currentViewport);
};
/**
* Returns the viewport definition.
*
* @param {Vector4} target - The method writes the result in this target object.
* @return {Vector4} The viewport definition.
*/
this.getViewport = function (target) {
return target.copy(_viewport);
};
/**
* Sets the viewport to render from `(x, y)` to `(x + width, y + height)`.
*
* @param {number | Vector4} x - The horizontal coordinate for the lower left corner of the viewport origin in logical pixel unit.
* Or alternatively a four-component vector specifying all the parameters of the viewport.
* @param {number} y - The vertical coordinate for the lower left corner of the viewport origin in logical pixel unit.
* @param {number} width - The width of the viewport in logical pixel unit.
* @param {number} height - The height of the viewport in logical pixel unit.
*/
this.setViewport = function (x, y, width, height) {
if (x.isVector4) {
_viewport.set(x.x, x.y, x.z, x.w);
} else {
_viewport.set(x, y, width, height);
}
state.viewport(_currentViewport.copy(_viewport).multiplyScalar(_pixelRatio).round());
};
/**
* Returns the scissor region.
*
* @param {Vector4} target - The method writes the result in this target object.
* @return {Vector4} The scissor region.
*/
this.getScissor = function (target) {
return target.copy(_scissor);
};
/**
* Sets the scissor region to render from `(x, y)` to `(x + width, y + height)`.
*
* @param {number | Vector4} x - The horizontal coordinate for the lower left corner of the scissor region origin in logical pixel unit.
* Or alternatively a four-component vector specifying all the parameters of the scissor region.
* @param {number} y - The vertical coordinate for the lower left corner of the scissor region origin in logical pixel unit.
* @param {number} width - The width of the scissor region in logical pixel unit.
* @param {number} height - The height of the scissor region in logical pixel unit.
*/
this.setScissor = function (x, y, width, height) {
if (x.isVector4) {
_scissor.set(x.x, x.y, x.z, x.w);
} else {
_scissor.set(x, y, width, height);
}
state.scissor(_currentScissor.copy(_scissor).multiplyScalar(_pixelRatio).round());
};
/**
* Returns `true` if the scissor test is enabled.
*
* @return {boolean} Whether the scissor test is enabled or not.
*/
this.getScissorTest = function () {
return _scissorTest;
};
/**
* Enable or disable the scissor test. When this is enabled, only the pixels
* within the defined scissor area will be affected by further renderer
* actions.
*
* @param {boolean} boolean - Whether the scissor test is enabled or not.
*/
this.setScissorTest = function (boolean) {
state.setScissorTest(_scissorTest = boolean);
};
/**
* Sets a custom opaque sort function for the render lists. Pass `null`
* to use the default `painterSortStable` function.
*
* @param {?Function} method - The opaque sort function.
*/
this.setOpaqueSort = function (method) {
_opaqueSort = method;
};
/**
* Sets a custom transparent sort function for the render lists. Pass `null`
* to use the default `reversePainterSortStable` function.
*
* @param {?Function} method - The opaque sort function.
*/
this.setTransparentSort = function (method) {
_transparentSort = method;
};
// Clearing
/**
* Returns the clear color.
*
* @param {Color} target - The method writes the result in this target object.
* @return {Color} The clear color.
*/
this.getClearColor = function (target) {
return target.copy(background.getClearColor());
};
/**
* Sets the clear color and alpha.
*
* @param {Color} color - The clear color.
* @param {number} [alpha=1] - The clear alpha.
*/
this.setClearColor = function () {
background.setClearColor(...arguments);
};
/**
* Returns the clear alpha. Ranges within `[0,1]`.
*
* @return {number} The clear alpha.
*/
this.getClearAlpha = function () {
return background.getClearAlpha();
};
/**
* Sets the clear alpha.
*
* @param {number} alpha - The clear alpha.
*/
this.setClearAlpha = function () {
background.setClearAlpha(...arguments);
};
/**
* Tells the renderer to clear its color, depth or stencil drawing buffer(s).
* This method initializes the buffers to the current clear color values.
*
* @param {boolean} [color=true] - Whether the color buffer should be cleared or not.
* @param {boolean} [depth=true] - Whether the depth buffer should be cleared or not.
* @param {boolean} [stencil=true] - Whether the stencil buffer should be cleared or not.
*/
this.clear = function (color = true, depth = true, stencil = true) {
let bits = 0;
if (color) {
// check if we're trying to clear an integer target
let isIntegerFormat = false;
if (_currentRenderTarget !== null) {
const targetFormat = _currentRenderTarget.texture.format;
isIntegerFormat = targetFormat === _constants.RGBAIntegerFormat || targetFormat === _constants.RGIntegerFormat || targetFormat === _constants.RedIntegerFormat;
}
// use the appropriate clear functions to clear the target if it's a signed
// or unsigned integer target
if (isIntegerFormat) {
const targetType = _currentRenderTarget.texture.type;
const isUnsignedType = targetType === _constants.UnsignedByteType || targetType === _constants.UnsignedIntType || targetType === _constants.UnsignedShortType || targetType === _constants.UnsignedInt248Type || targetType === _constants.UnsignedShort4444Type || targetType === _constants.UnsignedShort5551Type;
const clearColor = background.getClearColor();
const a = background.getClearAlpha();
const r = clearColor.r;
const g = clearColor.g;
const b = clearColor.b;
if (isUnsignedType) {
uintClearColor[0] = r;
uintClearColor[1] = g;
uintClearColor[2] = b;
uintClearColor[3] = a;
_gl.clearBufferuiv(_gl.COLOR, 0, uintClearColor);
} else {
intClearColor[0] = r;
intClearColor[1] = g;
intClearColor[2] = b;
intClearColor[3] = a;
_gl.clearBufferiv(_gl.COLOR, 0, intClearColor);
}
} else {
bits |= _gl.COLOR_BUFFER_BIT;
}
}
if (depth) {
bits |= _gl.DEPTH_BUFFER_BIT;
}
if (stencil) {
bits |= _gl.STENCIL_BUFFER_BIT;
this.state.buffers.stencil.setMask(0xffffffff);
}
_gl.clear(bits);
};
/**
* Clears the color buffer. Equivalent to calling `renderer.clear( true, false, false )`.
*/
this.clearColor = function () {
this.clear(true, false, false);
};
/**
* Clears the depth buffer. Equivalent to calling `renderer.clear( false, true, false )`.
*/
this.clearDepth = function () {
this.clear(false, true, false);
};
/**
* Clears the stencil buffer. Equivalent to calling `renderer.clear( false, false, true )`.
*/
this.clearStencil = function () {
this.clear(false, false, true);
};
/**
* Frees the GPU-related resources allocated by this instance. Call this
* method whenever this instance is no longer used in your app.
*/
this.dispose = function () {
canvas.removeEventListener('webglcontextlost', onContextLost, false);
canvas.removeEventListener('webglcontextrestored', onContextRestore, false);
canvas.removeEventListener('webglcontextcreationerror', onContextCreationError, false);
background.dispose();
renderLists.dispose();
renderStates.dispose();
properties.dispose();
cubemaps.dispose();
cubeuvmaps.dispose();
objects.dispose();
bindingStates.dispose();
uniformsGroups.dispose();
programCache.dispose();
xr.dispose();
xr.removeEventListener('sessionstart', onXRSessionStart);
xr.removeEventListener('sessionend', onXRSessionEnd);
animation.stop();
};
// Events
function onContextLost(event) {
event.preventDefault();
console.log('THREE.WebGLRenderer: Context Lost.');
_isContextLost = true;
}
function onContextRestore( /* event */
) {
console.log('THREE.WebGLRenderer: Context Restored.');
_isContextLost = false;
const infoAutoReset = info.autoReset;
const shadowMapEnabled = shadowMap.enabled;
const shadowMapAutoUpdate = shadowMap.autoUpdate;
const shadowMapNeedsUpdate = shadowMap.needsUpdate;
const shadowMapType = shadowMap.type;
initGLContext();
info.autoReset = infoAutoReset;
shadowMap.enabled = shadowMapEnabled;
shadowMap.autoUpdate = shadowMapAutoUpdate;
shadowMap.needsUpdate = shadowMapNeedsUpdate;
shadowMap.type = shadowMapType;
}
function onContextCreationError(event) {
console.error('THREE.WebGLRenderer: A WebGL context could not be created. Reason: ', event.statusMessage);
}
function onMaterialDispose(event) {
const material = event.target;
material.removeEventListener('dispose', onMaterialDispose);
deallocateMaterial(material);
}
// Buffer deallocation
function deallocateMaterial(material) {
releaseMaterialProgramReferences(material);
properties.remove(material);
}
function releaseMaterialProgramReferences(material) {
const programs = properties.get(material).programs;
if (programs !== undefined) {
programs.forEach(function (program) {
programCache.releaseProgram(program);
});
if (material.isShaderMaterial) {
programCache.releaseShaderCache(material);
}
}
}
// Buffer rendering
this.renderBufferDirect = function (camera, scene, geometry, material, object, group) {
if (scene === null) scene = _emptyScene; // renderBufferDirect second parameter used to be fog (could be null)
const frontFaceCW = object.isMesh && object.matrixWorld.determinant() < 0;
const program = setProgram(camera, scene, geometry, material, object);
state.setMaterial(material, frontFaceCW);
//
let index = geometry.index;
let rangeFactor = 1;
if (material.wireframe === true) {
index = geometries.getWireframeAttribute(geometry);
if (index === undefined) return;
rangeFactor = 2;
}
//
const drawRange = geometry.drawRange;
const position = geometry.attributes.position;
let drawStart = drawRange.start * rangeFactor;
let drawEnd = (drawRange.start + drawRange.count) * rangeFactor;
if (group !== null) {
drawStart = Math.max(drawStart, group.start * rangeFactor);
drawEnd = Math.min(drawEnd, (group.start + group.count) * rangeFactor);
}
if (index !== null) {
drawStart = Math.max(drawStart, 0);
drawEnd = Math.min(drawEnd, index.count);
} else if (position !== undefined && position !== null) {
drawStart = Math.max(drawStart, 0);
drawEnd = Math.min(drawEnd, position.count);
}
const drawCount = drawEnd - drawStart;
if (drawCount < 0 || drawCount === Infinity) return;
//
bindingStates.setup(object, material, program, geometry, index);
let attribute;
let renderer = bufferRenderer;
if (index !== null) {
attribute = attributes.get(index);
renderer = indexedBufferRenderer;
renderer.setIndex(attribute);
}
//
if (object.isMesh) {
if (material.wireframe === true) {
state.setLineWidth(material.wireframeLinewidth * getTargetPixelRatio());
renderer.setMode(_gl.LINES);
} else {
renderer.setMode(_gl.TRIANGLES);
}
} else if (object.isLine) {
let lineWidth = material.linewidth;
if (lineWidth === undefined) lineWidth = 1; // Not using Line*Material
state.setLineWidth(lineWidth * getTargetPixelRatio());
if (object.isLineSegments) {
renderer.setMode(_gl.LINES);
} else if (object.isLineLoop) {
renderer.setMode(_gl.LINE_LOOP);
} else {
renderer.setMode(_gl.LINE_STRIP);
}
} else if (object.isPoints) {
renderer.setMode(_gl.POINTS);
} else if (object.isSprite) {
renderer.setMode(_gl.TRIANGLES);
}
if (object.isBatchedMesh) {
if (object._multiDrawInstances !== null) {
// @deprecated, r174
(0, _utils.warnOnce)('THREE.WebGLRenderer: renderMultiDrawInstances has been deprecated and will be removed in r184. Append to renderMultiDraw arguments and use indirection.');
renderer.renderMultiDrawInstances(object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount, object._multiDrawInstances);
} else {
if (!extensions.get('WEBGL_multi_draw')) {
const starts = object._multiDrawStarts;
const counts = object._multiDrawCounts;
const drawCount = object._multiDrawCount;
const bytesPerElement = index ? attributes.get(index).bytesPerElement : 1;
const uniforms = properties.get(material).currentProgram.getUniforms();
for (let i = 0; i < drawCount; i++) {
uniforms.setValue(_gl, '_gl_DrawID', i);
renderer.render(starts[i] / bytesPerElement, counts[i]);
}
} else {
renderer.renderMultiDraw(object._multiDrawStarts, object._multiDrawCounts, object._multiDrawCount);
}
}
} else if (object.isInstancedMesh) {
renderer.renderInstances(drawStart, drawCount, object.count);
} else if (geometry.isInstancedBufferGeometry) {
const maxInstanceCount = geometry._maxInstanceCount !== undefined ? geometry._maxInstanceCount : Infinity;
const instanceCount = Math.min(geometry.instanceCount, maxInstanceCount);
renderer.renderInstances(drawStart, drawCount, instanceCount);
} else {
renderer.render(drawStart, drawCount);
}
};
// Compile
function prepareMaterial(material, scene, object) {
if (material.transparent === true && material.side === _constants.DoubleSide && material.forceSinglePass === false) {
material.side = _constants.BackSide;
material.needsUpdate = true;
getProgram(material, scene, object);
material.side = _constants.FrontSide;
material.needsUpdate = true;
getProgram(material, scene, object);
material.side = _constants.DoubleSide;
} else {
getProgram(material, scene, object);
}
}
/**
* Compiles all materials in the scene with the camera. This is useful to precompile shaders
* before the first rendering. If you want to add a 3D object to an existing scene, use the third
* optional parameter for applying the target scene.
*
* Note that the (target) scene's lighting and environment must be configured before calling this method.
*
* @param {Object3D} scene - The scene or another type of 3D object to precompile.
* @param {Camera} camera - The camera.
* @param {?Scene} [targetScene=null] - The target scene.
* @return {?Set} The precompiled materials.
*/
this.compile = function (scene, camera, targetScene = null) {
if (targetScene === null) targetScene = scene;
currentRenderState = renderStates.get(targetScene);
currentRenderState.init(camera);
renderStateStack.push(currentRenderState);
// gather lights from both the target scene and the new object that will be added to the scene.
targetScene.traverseVisible(function (object) {
if (object.isLight && object.layers.test(camera.layers)) {
currentRenderState.pushLight(object);
if (object.castShadow) {
currentRenderState.pushShadow(object);
}
}
});
if (scene !== targetScene) {
scene.traverseVisible(function (object) {
if (object.isLight && object.layers.test(camera.layers)) {
currentRenderState.pushLight(object);
if (object.castShadow) {
currentRenderState.pushShadow(object);
}
}
});
}
currentRenderState.setupLights();
// Only initialize materials in the new scene, not the targetScene.
const materials = new Set();
scene.traverse(function (object) {
if (!(object.isMesh || object.isPoints || object.isLine || object.isSprite)) {
return;
}
const material = object.material;
if (material) {
if (Array.isArray(material)) {
for (let i = 0; i < material.length; i++) {
const material2 = material[i];
prepareMaterial(material2, targetScene, object);
materials.add(material2);
}
} else {
prepareMaterial(material, targetScene, object);
materials.add(material);
}
}
});
currentRenderState = renderStateStack.pop();
return materials;
};
// compileAsync
/**
* Asynchronous version of {@link WebGLRenderer#compile}.
*
* This method makes use of the `KHR_parallel_shader_compile` WebGL extension. Hence,
* it is recommended to use this version of `compile()` whenever possible.
*
* @async
* @param {Object3D} scene - The scene or another type of 3D object to precompile.
* @param {Camera} camera - The camera.
* @param {?Scene} [targetScene=null] - The target scene.
* @return {Promise} A Promise that resolves when the given scene can be rendered without unnecessary stalling due to shader compilation.
*/
this.compileAsync = function (scene, camera, targetScene = null) {
const materials = this.compile(scene, camera, targetScene);
// Wait for all the materials in the new object to indicate that they're
// ready to be used before resolving the promise.
return new Promise(resolve => {
function checkMaterialsReady() {
materials.forEach(function (material) {
const materialProperties = properties.get(material);
const program = materialProperties.currentProgram;
if (program.isReady()) {
// remove any programs that report they're ready to use from the list
materials.delete(material);
}
});
// once the list of compiling materials is empty, call the callback
if (materials.size === 0) {
resolve(scene);
return;
}
// if some materials are still not ready, wait a bit and check again
setTimeout(checkMaterialsReady, 10);
}
if (extensions.get('KHR_parallel_shader_compile') !== null) {
// If we can check the compilation status of the materials without
// blocking then do so right away.
checkMaterialsReady();
} else {
// Otherwise start by waiting a bit to give the materials we just
// initialized a chance to finish.
setTimeout(checkMaterialsReady, 10);
}
});
};
// Animation Loop
let onAnimationFrameCallback = null;
function onAnimationFrame(time) {
if (onAnimationFrameCallback) onAnimationFrameCallback(time);
}
function onXRSessionStart() {
animation.stop();
}
function onXRSessionEnd() {
animation.start();
}
const animation = new _WebGLAnimation.WebGLAnimation();
animation.setAnimationLoop(onAnimationFrame);
if (typeof self !== 'undefined') animation.setContext(self);
this.setAnimationLoop = function (callback) {
onAnimationFrameCallback = callback;
xr.setAnimationLoop(callback);
callback === null ? animation.stop() : animation.start();
};
xr.addEventListener('sessionstart', onXRSessionStart);
xr.addEventListener('sessionend', onXRSessionEnd);
// Rendering
/**
* Renders the given scene (or other type of 3D object) using the given camera.
*
* The render is done to a previously specified render target set by calling {@link WebGLRenderer#setRenderTarget}
* or to the canvas as usual.
*
* By default render buffers are cleared before rendering but you can prevent
* this by setting the property `autoClear` to `false`. If you want to prevent
* only certain buffers being cleared you can `autoClearColor`, `autoClearDepth`
* or `autoClearStencil` to `false`. To force a clear, use {@link WebGLRenderer#clear}.
*
* @param {Object3D} scene - The scene to render.
* @param {Camera} camera - The camera.
*/
this.render = function (scene, camera) {
if (camera !== undefined && camera.isCamera !== true) {
console.error('THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.');
return;
}
if (_isContextLost === true) return;
// update scene graph
if (scene.matrixWorldAutoUpdate === true) scene.updateMatrixWorld();
// update camera matrices and frustum
if (camera.parent === null && camera.matrixWorldAutoUpdate === true) camera.updateMatrixWorld();
if (xr.enabled === true && xr.isPresenting === true) {
if (xr.cameraAutoUpdate === true) xr.updateCamera(camera);
camera = xr.getCamera(); // use XR camera for rendering
}
//
if (scene.isScene === true) scene.onBeforeRender(_this, scene, camera, _currentRenderTarget);
currentRenderState = renderStates.get(scene, renderStateStack.length);
currentRenderState.init(camera);
renderStateStack.push(currentRenderState);
_projScreenMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse);
_frustum.setFromProjectionMatrix(_projScreenMatrix);
_localClippingEnabled = this.localClippingEnabled;
_clippingEnabled = clipping.init(this.clippingPlanes, _localClippingEnabled);
currentRenderList = renderLists.get(scene, renderListStack.length);
currentRenderList.init();
renderListStack.push(currentRenderList);
if (xr.enabled === true && xr.isPresenting === true) {
const depthSensingMesh = _this.xr.getDepthSensingMesh();
if (depthSensingMesh !== null) {
projectObject(depthSensingMesh, camera, -Infinity, _this.sortObjects);
}
}
projectObject(scene, camera, 0, _this.sortObjects);
currentRenderList.finish();
if (_this.sortObjects === true) {
currentRenderList.sort(_opaqueSort, _transparentSort);
}
_renderBackground = xr.enabled === false || xr.isPresenting === false || xr.hasDepthSensing() === false;
if (_renderBackground) {
background.addToRenderList(currentRenderList, scene);
}
//
this.info.render.frame++;
if (_clippingEnabled === true) clipping.beginShadows();
const shadowsArray = currentRenderState.state.shadowsArray;
shadowMap.render(shadowsArray, scene, camera);
if (_clippingEnabled === true) clipping.endShadows();
//
if (this.info.autoReset === true) this.info.reset();
// render scene
const opaqueObjects = currentRenderList.opaque;
const transmissiveObjects = currentRenderList.transmissive;
currentRenderState.setupLights();
if (camera.isArrayCamera) {
const cameras = camera.cameras;
if (transmissiveObjects.length > 0) {
for (let i = 0, l = cameras.length; i < l; i++) {
const camera2 = cameras[i];
renderTransmissionPass(opaqueObjects, transmissiveObjects, scene, camera2);
}
}
if (_renderBackground) background.render(scene);
for (let i = 0, l = cameras.length; i < l; i++) {
const camera2 = cameras[i];
renderScene(currentRenderList, scene, camera2, camera2.viewport);
}
} else {
if (transmissiveObjects.length > 0) renderTransmissionPass(opaqueObjects, transmissiveObjects, scene, camera);
if (_renderBackground) background.render(scene);
renderScene(currentRenderList, scene, camera);
}
//
if (_currentRenderTarget !== null && _currentActiveMipmapLevel === 0) {
// resolve multisample renderbuffers to a single-sample texture if necessary
textures.updateMultisampleRenderTarget(_currentRenderTarget);
// Generate mipmap if we're using any kind of mipmap filtering
textures.updateRenderTargetMipmap(_currentRenderTarget);
}
//
if (scene.isScene === true) scene.onAfterRender(_this, scene, camera);
// _gl.finish();
bindingStates.resetDefaultState();
_currentMaterialId = -1;
_currentCamera = null;
renderStateStack.pop();
if (renderStateStack.length > 0) {
currentRenderState = renderStateStack[renderStateStack.length - 1];
if (_clippingEnabled === true) clipping.setGlobalState(_this.clippingPlanes, currentRenderState.state.camera);
} else {
currentRenderState = null;
}
renderListStack.pop();
if (renderListStack.length > 0) {
currentRenderList = renderListStack[renderListStack.length - 1];
} else {
currentRenderList = null;
}
};
function projectObject(object, camera, groupOrder, sortObjects) {
if (object.visible === false) return;
const visible = object.layers.test(camera.layers);
if (visible) {
if (object.isGroup) {
groupOrder = object.renderOrder;
} else if (object.isLOD) {
if (object.autoUpdate === true) object.update(camera);
} else if (object.isLight) {
currentRenderState.pushLight(object);
if (object.castShadow) {
currentRenderState.pushShadow(object);
}
} else if (object.isSprite) {
if (!object.frustumCulled || _frustum.intersectsSprite(object)) {
if (sortObjects) {
_vector4.setFromMatrixPosition(object.matrixWorld).applyMatrix4(_projScreenMatrix);
}
const geometry = objects.update(object);
const material = object.material;
if (material.visible) {
currentRenderList.push(object, geometry, material, groupOrder, _vector4.z, null);
}
}
} else if (object.isMesh || object.isLine || object.isPoints) {
if (!object.frustumCulled || _frustum.intersectsObject(object)) {
const geometry = objects.update(object);
const material = object.material;
if (sortObjects) {
if (object.boundingSphere !== undefined) {
if (object.boundingSphere === null) object.computeBoundingSphere();
_vector4.copy(object.boundingSphere.center);
} else {
if (geometry.boundingSphere === null) geometry.computeBoundingSphere();
_vector4.copy(geometry.boundingSphere.center);
}
_vector4.applyMatrix4(object.matrixWorld).applyMatrix4(_projScreenMatrix);
}
if (Array.isArray(material)) {
const groups = geometry.groups;
for (let i = 0, l = groups.length; i < l; i++) {
const group = groups[i];
const groupMaterial = material[group.materialIndex];
if (groupMaterial && groupMaterial.visible) {
currentRenderList.push(object, geometry, groupMaterial, groupOrder, _vector4.z, group);
}
}
} else if (material.visible) {
currentRenderList.push(object, geometry, material, groupOrder, _vector4.z, null);
}
}
}
}
const children = object.children;
for (let i = 0, l = children.length; i < l; i++) {
projectObject(children[i], camera, groupOrder, sortObjects);
}
}
function renderScene(currentRenderList, scene, camera, viewport) {
const opaqueObjects = currentRenderList.opaque;
const transmissiveObjects = currentRenderList.transmissive;
const transparentObjects = currentRenderList.transparent;
currentRenderState.setupLightsView(camera);
if (_clippingEnabled === true) clipping.setGlobalState(_this.clippingPlanes, camera);
if (viewport) state.viewport(_currentViewport.copy(viewport));
if (opaqueObjects.length > 0) renderObjects(opaqueObjects, scene, camera);
if (transmissiveObjects.length > 0) renderObjects(transmissiveObjects, scene, camera);
if (transparentObjects.length > 0) renderObjects(transparentObjects, scene, camera);
// Ensure depth buffer writing is enabled so it can be cleared on next render
state.buffers.depth.setTest(true);
state.buffers.depth.setMask(true);
state.bu