UNPKG

@babylonjs/core

Version:

Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.

809 lines (808 loc) 31.8 kB
import { PrePassRenderTarget } from "../Materials/Textures/prePassRenderTarget.js"; import { _WarnImport } from "../Misc/devTools.js"; import { Color4 } from "../Maths/math.color.js"; import { Material } from "../Materials/material.js"; import { GeometryBufferRenderer } from "../Rendering/geometryBufferRenderer.js"; import "../Engines/Extensions/engine.multiRender.js"; /** * Renders a pre pass of the scene * This means every mesh in the scene will be rendered to a render target texture * And then this texture will be composited to the rendering canvas with post processes * It is necessary for effects like subsurface scattering or deferred shading */ export class PrePassRenderer { /** * Indicates if the prepass renderer is generating normals in world space or camera space (default: camera space) */ get generateNormalsInWorldSpace() { return this._generateNormalsInWorldSpace; } set generateNormalsInWorldSpace(value) { if (this._generateNormalsInWorldSpace === value) { return; } this._generateNormalsInWorldSpace = value; this._markAllMaterialsAsPrePassDirty(); } /** * Returns the index of a texture in the multi render target texture array. * @param type Texture type * @returns The index */ getIndex(type) { return this._textureIndices[type]; } /** * How many samples are used for MSAA of the scene render target */ get samples() { return this.defaultRT.samples; } set samples(n) { this.defaultRT.samples = n; } /** * If set to true (default: false), the depth texture will be cleared with the depth value corresponding to the far plane (1 in normal mode, 0 in reverse depth buffer mode) * If set to false, the depth texture is always cleared with 0. */ get useSpecificClearForDepthTexture() { return this._useSpecificClearForDepthTexture; } set useSpecificClearForDepthTexture(value) { if (this._useSpecificClearForDepthTexture === value) { return; } this._useSpecificClearForDepthTexture = value; this._isDirty = true; } /** * @returns the prepass render target for the rendering pass. * If we are currently rendering a render target, it returns the PrePassRenderTarget * associated with that render target. Otherwise, it returns the scene default PrePassRenderTarget */ getRenderTarget() { return this._currentTarget; } /** * @internal * Managed by the scene component * @param prePassRenderTarget */ _setRenderTarget(prePassRenderTarget) { if (prePassRenderTarget) { this._currentTarget = prePassRenderTarget; } else { this._currentTarget = this.defaultRT; this._engine.currentRenderPassId = this._scene.activeCamera?.renderPassId ?? this._currentTarget.renderPassId; } } /** * Returns true if the currently rendered prePassRenderTarget is the one * associated with the scene. */ get currentRTisSceneRT() { return this._currentTarget === this.defaultRT; } _refreshGeometryBufferRendererLink() { if (!this.doNotUseGeometryRendererFallback) { this._geometryBuffer = this._scene.enableGeometryBufferRenderer(); if (!this._geometryBuffer) { // Not supported this.doNotUseGeometryRendererFallback = true; return; } this._geometryBuffer._linkPrePassRenderer(this); } else { if (this._geometryBuffer) { this._geometryBuffer._unlinkPrePassRenderer(); } this._geometryBuffer = null; this._scene.disableGeometryBufferRenderer(); } } /** * Indicates if the prepass is enabled */ get enabled() { return this._enabled; } /** * Instantiates a prepass renderer * @param scene The scene */ constructor(scene) { /** * To save performance, we can excluded skinned meshes from the prepass */ this.excludedSkinnedMesh = []; /** * Force material to be excluded from the prepass * Can be useful when `useGeometryBufferFallback` is set to `true` * and you don't want a material to show in the effect. */ this.excludedMaterials = []; /** * Number of textures in the multi render target texture where the scene is directly rendered */ this.mrtCount = 0; this._mrtTypes = []; this._mrtFormats = []; this._mrtLayout = []; this._mrtNames = []; this._textureIndices = []; this._generateNormalsInWorldSpace = false; this._useSpecificClearForDepthTexture = false; this._isDirty = true; /** * Configuration for prepass effects */ this._effectConfigurations = []; /** * Prevents the PrePassRenderer from using the GeometryBufferRenderer as a fallback */ this.doNotUseGeometryRendererFallback = true; /** * All the render targets generated by prepass */ this.renderTargets = []; this._clearColor = new Color4(0, 0, 0, 0); this._clearDepthColor = new Color4(1e8, 0, 0, 1); // "infinity" value - depth in the depth texture is view.z, not a 0..1 value! this._enabled = false; this._needsCompositionForThisPass = false; /** * Set to true to disable gamma transform in PrePass. * Can be useful in case you already proceed to gamma transform on a material level * and your post processes don't need to be in linear color space. */ this.disableGammaTransform = false; this._scene = scene; this._engine = scene.getEngine(); let type = 0; if (this._engine._caps.textureFloat && this._engine._caps.textureFloatLinearFiltering) { type = 1; } else if (this._engine._caps.textureHalfFloat && this._engine._caps.textureHalfFloatLinearFiltering) { type = 2; } for (let i = 0; i < PrePassRenderer.TextureFormats.length; ++i) { const format = PrePassRenderer.TextureFormats[i].format; if (PrePassRenderer.TextureFormats[i].type === 1) { PrePassRenderer.TextureFormats[i].type = type; if (type === 1 && (format === 6 || format === 7 || format === 5) && !this._engine._caps.supportFloatTexturesResolve) { // We don't know in advance if the texture will be used as a resolve target, so we revert to half_float if the extension to resolve full float textures is not supported PrePassRenderer.TextureFormats[i].type = 2; } } } PrePassRenderer._SceneComponentInitialization(this._scene); this.defaultRT = this._createRenderTarget("sceneprePassRT", null); this._currentTarget = this.defaultRT; } /** * Creates a new PrePassRenderTarget * This should be the only way to instantiate a `PrePassRenderTarget` * @param name Name of the `PrePassRenderTarget` * @param renderTargetTexture RenderTarget the `PrePassRenderTarget` will be attached to. * Can be `null` if the created `PrePassRenderTarget` is attached to the scene (default framebuffer). * @internal */ _createRenderTarget(name, renderTargetTexture) { const rt = new PrePassRenderTarget(name, renderTargetTexture, { width: this._engine.getRenderWidth(), height: this._engine.getRenderHeight() }, 0, this._scene, { generateMipMaps: false, generateStencilBuffer: this._engine.isStencilEnable, defaultType: 0, types: [], drawOnlyOnFirstAttachmentByDefault: true, }); this.renderTargets.push(rt); if (this._enabled) { // The pre-pass renderer is already enabled, so make sure we create the render target with the correct number of textures this._update(); } return rt; } /** * Indicates if rendering a prepass is supported */ get isSupported() { return this._scene.getEngine().getCaps().drawBuffersExtension; } /** * Sets the proper output textures to draw in the engine. * @param effect The effect that is drawn. It can be or not be compatible with drawing to several output textures. * @param subMesh Submesh on which the effect is applied */ bindAttachmentsForEffect(effect, subMesh) { const material = subMesh.getMaterial(); const isPrePassCapable = material && material.isPrePassCapable; const excluded = material && this.excludedMaterials.indexOf(material) !== -1; if (this.enabled && this._currentTarget.enabled) { if (effect._multiTarget && isPrePassCapable && !excluded) { this._engine.bindAttachments(this._multiRenderAttachments); } else { if (this._engine._currentRenderTarget) { this._engine.bindAttachments(this._defaultAttachments); } else { this._engine.restoreSingleAttachment(); } if (this._geometryBuffer && this.currentRTisSceneRT && !excluded) { this._geometryBuffer.renderList.push(subMesh.getRenderingMesh()); } } } } _reinitializeAttachments() { const multiRenderLayout = []; const clearLayout = [false]; const clearDepthLayout = [false]; const defaultLayout = [true]; for (let i = 0; i < this.mrtCount; i++) { multiRenderLayout.push(true); if (i > 0) { if (this._useSpecificClearForDepthTexture && this._mrtLayout[i] === 5) { clearLayout.push(false); clearDepthLayout.push(true); } else { clearLayout.push(true); clearDepthLayout.push(false); } defaultLayout.push(false); } } this._multiRenderAttachments = this._engine.buildTextureLayout(multiRenderLayout); this._clearAttachments = this._engine.buildTextureLayout(clearLayout); this._clearDepthAttachments = this._engine.buildTextureLayout(clearDepthLayout); this._defaultAttachments = this._engine.buildTextureLayout(defaultLayout); } _resetLayout() { for (let i = 0; i < PrePassRenderer.TextureFormats.length; i++) { this._textureIndices[PrePassRenderer.TextureFormats[i].purpose] = -1; } this._textureIndices[4] = 0; this._mrtLayout = [4]; this._mrtTypes = [PrePassRenderer.TextureFormats[4].type]; this._mrtFormats = [PrePassRenderer.TextureFormats[4].format]; this._mrtNames = [PrePassRenderer.TextureFormats[4].name]; this.mrtCount = 1; } _updateGeometryBufferLayout() { this._refreshGeometryBufferRendererLink(); if (this._geometryBuffer) { this._geometryBuffer._resetLayout(); const texturesActivated = []; for (let i = 0; i < this._mrtLayout.length; i++) { texturesActivated.push(false); } this._geometryBuffer._linkInternalTexture(this.defaultRT.getInternalTexture()); const matches = [ { prePassConstant: 5, geometryBufferConstant: GeometryBufferRenderer.DEPTH_TEXTURE_TYPE, }, { prePassConstant: 6, geometryBufferConstant: GeometryBufferRenderer.NORMAL_TEXTURE_TYPE, }, { prePassConstant: 1, geometryBufferConstant: GeometryBufferRenderer.POSITION_TEXTURE_TYPE, }, { prePassConstant: 3, geometryBufferConstant: GeometryBufferRenderer.REFLECTIVITY_TEXTURE_TYPE, }, { prePassConstant: 2, geometryBufferConstant: GeometryBufferRenderer.VELOCITY_TEXTURE_TYPE, }, ]; // replace textures in the geometryBuffer RT for (let i = 0; i < matches.length; i++) { const index = this._mrtLayout.indexOf(matches[i].prePassConstant); if (index !== -1) { this._geometryBuffer._forceTextureType(matches[i].geometryBufferConstant, index); texturesActivated[index] = true; } } this._geometryBuffer._setAttachments(this._engine.buildTextureLayout(texturesActivated)); } } /** * Restores attachments for single texture draw. */ restoreAttachments() { if (this.enabled && this._currentTarget.enabled && this._defaultAttachments) { if (this._engine._currentRenderTarget) { this._engine.bindAttachments(this._defaultAttachments); } else { this._engine.restoreSingleAttachment(); } } } /** * @internal */ // eslint-disable-next-line @typescript-eslint/no-unused-vars _beforeDraw(camera, faceIndex, layer) { // const previousEnabled = this._enabled && this._currentTarget.enabled; if (this._isDirty) { this._update(); } if (!this._enabled || !this._currentTarget.enabled) { return; } if (this._geometryBuffer) { this._geometryBuffer.renderList = []; } this._setupOutputForThisPass(this._currentTarget, camera); } _prepareFrame(prePassRenderTarget, faceIndex, layer) { if (prePassRenderTarget.renderTargetTexture) { prePassRenderTarget.renderTargetTexture._prepareFrame(this._scene, faceIndex, layer, prePassRenderTarget.renderTargetTexture.useCameraPostProcesses); } else if (this._postProcessesSourceForThisPass.length) { this._scene.postProcessManager._prepareFrame(); } else { this._engine.restoreDefaultFramebuffer(); } } /** * Sets an intermediary texture between prepass and postprocesses. This texture * will be used as input for post processes * @param rt The render target texture to use * @returns true if there are postprocesses that will use this texture, * false if there is no postprocesses - and the function has no effect */ setCustomOutput(rt) { const firstPP = this._postProcessesSourceForThisPass[0]; if (!firstPP) { return false; } firstPP.inputTexture = rt.renderTarget; return true; } _renderPostProcesses(prePassRenderTarget, faceIndex) { const firstPP = this._postProcessesSourceForThisPass[0]; const outputTexture = firstPP ? firstPP.inputTexture : prePassRenderTarget.renderTargetTexture ? prePassRenderTarget.renderTargetTexture.renderTarget : null; // Build post process chain for this prepass post draw let postProcessChain = this._currentTarget._beforeCompositionPostProcesses; if (this._needsCompositionForThisPass) { postProcessChain = postProcessChain.concat([this._currentTarget.imageProcessingPostProcess]); } // Activates and renders the chain if (postProcessChain.length) { this._scene.postProcessManager._prepareFrame(this._currentTarget.renderTarget?.texture, postProcessChain); this._scene.postProcessManager.directRender(postProcessChain, outputTexture, false, faceIndex); } } /** * @internal */ _afterDraw(faceIndex, layer) { if (this._enabled && this._currentTarget.enabled) { this._prepareFrame(this._currentTarget, faceIndex, layer); this._renderPostProcesses(this._currentTarget, faceIndex); } } /** * Clears the current prepass render target (in the sense of settings pixels to the scene clear color value) * @internal */ _clear() { if (this._isDirty) { this._update(); } if (this._enabled && this._currentTarget.enabled) { this._bindFrameBuffer(); // Clearing other attachment with 0 on all other attachments this._engine.bindAttachments(this._clearAttachments); this._engine.clear(this._clearColor, true, false, false); if (this._useSpecificClearForDepthTexture) { this._engine.bindAttachments(this._clearDepthAttachments); this._engine.clear(this._clearDepthColor, true, false, false); } // Regular clear color with the scene clear color of the 1st attachment this._engine.bindAttachments(this._defaultAttachments); } } // eslint-disable-next-line @typescript-eslint/no-unused-vars _bindFrameBuffer() { if (this._enabled && this._currentTarget.enabled) { this._currentTarget._checkSize(); const internalTexture = this._currentTarget.renderTarget; if (internalTexture) { this._engine.bindFramebuffer(internalTexture); } } } _setEnabled(enabled) { this._enabled = enabled; } _setRenderTargetEnabled(prePassRenderTarget, enabled) { prePassRenderTarget.enabled = enabled; if (!enabled) { this._unlinkInternalTexture(prePassRenderTarget); } } /** * Adds an effect configuration to the prepass render target. * If an effect has already been added, it won't add it twice and will return the configuration * already present. * @param cfg the effect configuration * @returns the effect configuration now used by the prepass */ addEffectConfiguration(cfg) { // Do not add twice for (let i = 0; i < this._effectConfigurations.length; i++) { if (this._effectConfigurations[i].name === cfg.name) { return this._effectConfigurations[i]; } } this._effectConfigurations.push(cfg); return cfg; } /** * Retrieves an effect configuration by name * @param name the name of the effect configuration * @returns the effect configuration, or null if not present */ getEffectConfiguration(name) { for (let i = 0; i < this._effectConfigurations.length; i++) { if (this._effectConfigurations[i].name === name) { return this._effectConfigurations[i]; } } return null; } _enable() { const previousMrtCount = this.mrtCount; for (let i = 0; i < this._effectConfigurations.length; i++) { if (this._effectConfigurations[i].enabled) { this._enableTextures(this._effectConfigurations[i].texturesRequired); } } for (let i = 0; i < this.renderTargets.length; i++) { if (this.mrtCount !== previousMrtCount || this.renderTargets[i].count !== this.mrtCount) { this.renderTargets[i].updateCount(this.mrtCount, { types: this._mrtTypes, formats: this._mrtFormats }, this._mrtNames.concat("prePass_DepthBuffer")); } this.renderTargets[i]._resetPostProcessChain(); for (let j = 0; j < this._effectConfigurations.length; j++) { if (this._effectConfigurations[j].enabled) { // TODO : subsurface scattering has 1 scene-wide effect configuration // solution : do not stock postProcess on effectConfiguration, but in the prepassRenderTarget (hashmap configuration => postProcess) // And call createPostProcess whenever the post process does not exist in the RT if (!this._effectConfigurations[j].postProcess && this._effectConfigurations[j].createPostProcess) { this._effectConfigurations[j].createPostProcess(); } if (this._effectConfigurations[j].postProcess) { this.renderTargets[i]._beforeCompositionPostProcesses.push(this._effectConfigurations[j].postProcess); } } } } this._reinitializeAttachments(); this._setEnabled(true); this._updateGeometryBufferLayout(); } _disable() { this._setEnabled(false); for (let i = 0; i < this.renderTargets.length; i++) { this._setRenderTargetEnabled(this.renderTargets[i], false); } this._resetLayout(); for (let i = 0; i < this._effectConfigurations.length; i++) { this._effectConfigurations[i].enabled = false; } } _getPostProcessesSource(prePassRenderTarget, camera) { if (camera) { return camera._postProcesses; } else if (prePassRenderTarget.renderTargetTexture) { if (prePassRenderTarget.renderTargetTexture.useCameraPostProcesses) { const camera = prePassRenderTarget.renderTargetTexture.activeCamera ? prePassRenderTarget.renderTargetTexture.activeCamera : this._scene.activeCamera; return camera ? camera._postProcesses : []; } else if (prePassRenderTarget.renderTargetTexture.postProcesses) { return prePassRenderTarget.renderTargetTexture.postProcesses; } else { return []; } } else { return this._scene.activeCamera ? this._scene.activeCamera._postProcesses : []; } } _setupOutputForThisPass(prePassRenderTarget, camera) { // Order is : draw ===> prePassRenderTarget._postProcesses ==> ipp ==> camera._postProcesses const secondaryCamera = camera && this._scene.activeCameras && !!this._scene.activeCameras.length && this._scene.activeCameras.indexOf(camera) !== 0; this._postProcessesSourceForThisPass = this._getPostProcessesSource(prePassRenderTarget, camera); this._postProcessesSourceForThisPass = this._postProcessesSourceForThisPass.filter((pp) => { return pp != null; }); this._scene.autoClear = true; const cameraHasImageProcessing = this._hasImageProcessing(this._postProcessesSourceForThisPass); this._needsCompositionForThisPass = !cameraHasImageProcessing && !this.disableGammaTransform && this._needsImageProcessing() && !secondaryCamera; const firstCameraPP = this._getFirstPostProcess(this._postProcessesSourceForThisPass); const firstPrePassPP = prePassRenderTarget._beforeCompositionPostProcesses && prePassRenderTarget._beforeCompositionPostProcesses[0]; let firstPP = null; // Setting the scene-wide post process configuration this._scene.imageProcessingConfiguration.applyByPostProcess = this._needsCompositionForThisPass || cameraHasImageProcessing; // Create composition effect if needed if (this._needsCompositionForThisPass && !prePassRenderTarget.imageProcessingPostProcess) { prePassRenderTarget._createCompositionEffect(); } // Setting the prePassRenderTarget as input texture of the first PP if (firstPrePassPP) { firstPP = firstPrePassPP; } else if (this._needsCompositionForThisPass) { firstPP = prePassRenderTarget.imageProcessingPostProcess; } else if (firstCameraPP) { firstPP = firstCameraPP; } this._bindFrameBuffer(); this._linkInternalTexture(prePassRenderTarget, firstPP); } _linkInternalTexture(prePassRenderTarget, postProcess) { if (postProcess) { postProcess.autoClear = false; postProcess.inputTexture = prePassRenderTarget.renderTarget; } if (prePassRenderTarget._outputPostProcess !== postProcess) { if (prePassRenderTarget._outputPostProcess) { this._unlinkInternalTexture(prePassRenderTarget); } prePassRenderTarget._outputPostProcess = postProcess; } if (prePassRenderTarget._internalTextureDirty) { this._updateGeometryBufferLayout(); prePassRenderTarget._internalTextureDirty = false; } } /** * @internal */ _unlinkInternalTexture(prePassRenderTarget) { if (prePassRenderTarget._outputPostProcess) { prePassRenderTarget._outputPostProcess.autoClear = true; prePassRenderTarget._outputPostProcess.restoreDefaultInputTexture(); prePassRenderTarget._outputPostProcess = null; } } _needsImageProcessing() { for (let i = 0; i < this._effectConfigurations.length; i++) { if (this._effectConfigurations[i].enabled && this._effectConfigurations[i].needsImageProcessing) { return true; } } return false; } _hasImageProcessing(postProcesses) { let isIPPAlreadyPresent = false; if (postProcesses) { for (let i = 0; i < postProcesses.length; i++) { if (postProcesses[i]?.getClassName() === "ImageProcessingPostProcess") { isIPPAlreadyPresent = true; break; } } } return isIPPAlreadyPresent; } /** * Internal, gets the first post proces. * @param postProcesses * @returns the first post process to be run on this camera. */ _getFirstPostProcess(postProcesses) { for (let ppIndex = 0; ppIndex < postProcesses.length; ppIndex++) { if (postProcesses[ppIndex] !== null) { return postProcesses[ppIndex]; } } return null; } /** * Marks the prepass renderer as dirty, triggering a check if the prepass is necessary for the next rendering. */ markAsDirty() { this._isDirty = true; } /** * Enables a texture on the MultiRenderTarget for prepass * @param types */ _enableTextures(types) { // For velocity : enable storage of previous matrices for instances this._scene.needsPreviousWorldMatrices = false; for (let i = 0; i < types.length; i++) { const type = types[i]; if (this._textureIndices[type] === -1) { this._textureIndices[type] = this._mrtLayout.length; this._mrtLayout.push(type); this._mrtTypes.push(PrePassRenderer.TextureFormats[type].type); this._mrtFormats.push(PrePassRenderer.TextureFormats[type].format); this._mrtNames.push(PrePassRenderer.TextureFormats[type].name); this.mrtCount++; } if (type === 2 || type === 11) { this._scene.needsPreviousWorldMatrices = true; } } } /** * Makes sure that the prepass renderer is up to date if it has been dirtified. */ update() { if (this._isDirty) { this._update(); } } _update() { this._disable(); let enablePrePass = false; this._scene.imageProcessingConfiguration.applyByPostProcess = false; if (this._scene._depthPeelingRenderer && this._scene.useOrderIndependentTransparency) { this._scene._depthPeelingRenderer.setPrePassRenderer(this); enablePrePass = true; } for (let i = 0; i < this._scene.materials.length; i++) { if (this._scene.materials[i].setPrePassRenderer(this)) { enablePrePass = true; } } if (enablePrePass) { this._setRenderTargetEnabled(this.defaultRT, true); } let postProcesses; for (let i = 0; i < this.renderTargets.length; i++) { if (this.renderTargets[i].renderTargetTexture) { postProcesses = this._getPostProcessesSource(this.renderTargets[i]); } else { const camera = this._scene.activeCamera; if (!camera) { continue; } postProcesses = camera._postProcesses; } if (!postProcesses) { continue; } postProcesses = postProcesses.filter((pp) => { return pp != null; }); if (postProcesses) { for (let j = 0; j < postProcesses.length; j++) { if (postProcesses[j].setPrePassRenderer(this)) { this._setRenderTargetEnabled(this.renderTargets[i], true); enablePrePass = true; } } if (this._hasImageProcessing(postProcesses)) { this._scene.imageProcessingConfiguration.applyByPostProcess = true; } } } this._markAllMaterialsAsPrePassDirty(); this._isDirty = false; if (enablePrePass) { this._enable(); } } _markAllMaterialsAsPrePassDirty() { const materials = this._scene.materials; for (let i = 0; i < materials.length; i++) { materials[i].markAsDirty(Material.PrePassDirtyFlag); } } /** * Disposes the prepass renderer. */ dispose() { for (let i = this.renderTargets.length - 1; i >= 0; i--) { this.renderTargets[i].dispose(); } for (let i = 0; i < this._effectConfigurations.length; i++) { if (this._effectConfigurations[i].dispose) { this._effectConfigurations[i].dispose(); } } } } /** * @internal */ PrePassRenderer._SceneComponentInitialization = (_) => { throw _WarnImport("PrePassRendererSceneComponent"); }; /** * Describes the types and formats of the textures used by the pre-pass renderer */ PrePassRenderer.TextureFormats = [ { purpose: 0, type: 2, format: 5, name: "prePass_Irradiance", }, { purpose: 1, type: 2, format: 5, name: "prePass_Position", }, { purpose: 2, type: 0, format: 5, name: "prePass_Velocity", }, { purpose: 3, type: 0, format: 5, name: "prePass_Reflectivity", }, { purpose: 4, type: 2, format: 5, name: "prePass_Color", }, { purpose: 5, type: 1, format: 6, name: "prePass_Depth", }, { purpose: 6, type: 2, format: 5, name: "prePass_Normal", }, { purpose: 7, type: 0, format: 5, name: "prePass_Albedo", }, { purpose: 8, type: 0, format: 5, name: "prePass_WorldNormal", }, { purpose: 9, type: 2, format: 5, name: "prePass_LocalPosition", }, { purpose: 10, type: 1, format: 6, name: "prePass_ScreenDepth", }, { purpose: 11, type: 2, format: 5, name: "prePass_VelocityLinear", }, ]; //# sourceMappingURL=prePassRenderer.js.map