UNPKG

gpu-curtains

Version:

gpu-curtains is a 3D WebGPU rendering engine. It can be used as a standalone 3D engine, but also includes extra classes focused on mapping 3d objects to DOM elements; It allows users to synchronize values such as position, sizing, or scale between them.

624 lines (621 loc) 30.8 kB
import { isRenderer } from '../renderers/utils.mjs'; import { Object3D } from '../objects3D/Object3D.mjs'; import { Vec3 } from '../../math/Vec3.mjs'; import { throwWarning } from '../../utils/utils.mjs'; var __typeError = (msg) => { throw TypeError(msg); }; var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg); var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value); var _shouldLoadColors, _shouldLoadDepth; const camPosA = new Vec3(); const camPosB = new Vec3(); const posA = new Vec3(); const posB = new Vec3(); class Scene extends Object3D { /** * Scene constructor * @param renderer - {@link Renderer} object or {@link GPUCurtains} class object used to create this {@link Scene}. */ constructor({ renderer }) { super(); /** * Keep track of whether the next render pass should set its `loadOp` to `'load'` when rendering. * @private */ __privateAdd(this, _shouldLoadColors); /** * Keep track of whether the next render pass should set its `depthLoadOp` to `'load'` when rendering. * @private */ __privateAdd(this, _shouldLoadDepth); renderer = isRenderer(renderer, "Scene"); this.renderer = renderer; __privateSet(this, _shouldLoadColors, false); __privateSet(this, _shouldLoadDepth, false); this.computePassEntries = []; this.renderPassEntries = { /** Array of {@link RenderPassEntry} that will handle {@link PingPongPlane}. Each {@link PingPongPlane} will be added as a distinct {@link RenderPassEntry} here. */ pingPong: [], /** Array of {@link RenderPassEntry} that will render to a specific {@link RenderTarget}. Each {@link RenderTarget} will be added as a distinct {@link RenderPassEntry} here. */ renderTarget: [], /** Array of {@link RenderPassEntry} containing {@link ShaderPass} that will render directly to the screen before rendering any other pass to the screen. Useful to perform "blit" pass before actually rendering the usual scene content. */ prePass: [], /** Array of {@link RenderPassEntry} that will render directly to the screen. Our first and default entry will contain all the Meshes that do not have any {@link RenderTarget} assigned. You can create following entries for custom scene rendering management process. */ screen: [], /**Array of {@link RenderPassEntry} containing post processing {@link ShaderPass} that will render directly to the screen after everything has been drawn. */ postProPass: [] }; } /** * Create a new {@link RenderPassEntry} in the {@link renderPassEntries} `screen` array. * @param label - Optional label to use for this {@link RenderPassEntry}. * @param order - Optional order into which insert this {@link renderPassEntries} `screen` array. A positive number means at the end of the array, a negative number means at the beginning. Default to `1`. * @returns - The new {@link RenderPassEntry}. */ createScreenPassEntry(label = "", order = 1) { const screenPassEntry = { label, renderPass: this.renderer.renderPass, renderTexture: null, onBeforeRenderPass: null, onAfterRenderPass: null, useCustomRenderPass: null, element: null, // explicitly set to null stack: { unProjected: { opaque: [], transparent: [] }, projected: { opaque: [], transparent: [] } } }; if (order >= 0) { this.renderPassEntries.screen.push(screenPassEntry); } else { this.renderPassEntries.screen.unshift(screenPassEntry); } return screenPassEntry; } /** * Set the main {@link Renderer} render pass entry. */ setMainRenderPassEntry() { this.createScreenPassEntry("Main scene screen render pass"); } /** * Get the number of meshes a {@link RenderPassEntry | render pass entry} should draw. * @param renderPassEntry - The {@link RenderPassEntry | render pass entry} to test. */ getRenderPassEntryLength(renderPassEntry) { if (!renderPassEntry) { return 0; } else { return renderPassEntry.element ? renderPassEntry.element.visible ? 1 : 0 : renderPassEntry.stack.unProjected.opaque.length + renderPassEntry.stack.unProjected.transparent.length + renderPassEntry.stack.projected.opaque.length + renderPassEntry.stack.projected.transparent.length; } } /** * Add a {@link ComputePass} to our scene {@link computePassEntries} array. * @param computePass - {@link ComputePass} to add. */ addComputePass(computePass) { this.computePassEntries.push(computePass); this.computePassEntries.sort((a, b) => { if (a.renderOrder !== b.renderOrder) { return a.renderOrder - b.renderOrder; } else { return a.index - b.index; } }); } /** * Remove a {@link ComputePass} from our scene {@link computePassEntries} array. * @param computePass - {@link ComputePass} to remove. */ removeComputePass(computePass) { this.computePassEntries = this.computePassEntries.filter((cP) => cP.uuid !== computePass.uuid); } /** * Add a {@link RenderTarget} to our scene {@link renderPassEntries} outputTarget array. * Every Meshes later added to this {@link RenderTarget} will be rendered to the {@link RenderTarget#renderTexture | RenderTarget Texture} using the {@link RenderTarget#renderPass.descriptor | RenderTarget RenderPass descriptor}. * @param renderTarget - {@link RenderTarget} to add. */ addRenderTarget(renderTarget) { if (!this.renderPassEntries.renderTarget.find((entry) => entry.renderPass.uuid === renderTarget.renderPass.uuid)) this.renderPassEntries.renderTarget.push({ label: renderTarget.options.label ? `${renderTarget.options.label} pass entry` : `RenderTarget ${renderTarget.uuid} pass entry`, renderPass: renderTarget.renderPass, renderTexture: renderTarget.renderTexture, onBeforeRenderPass: null, onAfterRenderPass: null, useCustomRenderPass: null, element: null, // explicitly set to null stack: { unProjected: { opaque: [], transparent: [] }, projected: { opaque: [], transparent: [] } } }); } /** * Remove a {@link RenderTarget} from our scene {@link renderPassEntries} outputTarget array. * @param renderTarget - {@link RenderTarget} to add. */ removeRenderTarget(renderTarget) { this.renderPassEntries.renderTarget = this.renderPassEntries.renderTarget.filter( (entry) => entry.renderPass.uuid !== renderTarget.renderPass.uuid ); } /** * Get the {@link RenderPassEntry} in the {@link renderPassEntries} `renderTarget` array (or `screen` array if no {@link RenderTarget} is passed) corresponding to the given {@link RenderTarget}. * @param renderTarget - {@link RenderTarget} to use to retrieve the {@link RenderPassEntry} if any. * @returns - {@link RenderPassEntry} found. */ getRenderTargetPassEntry(renderTarget = null) { return renderTarget ? this.renderPassEntries.renderTarget.find( (passEntry) => passEntry.renderPass.uuid === renderTarget.renderPass.uuid ) : this.renderPassEntries.screen.find((passEntry) => passEntry.renderPass.uuid === this.renderer.renderPass.uuid); } /** * Get the correct {@link renderPassEntries | render pass entry} (either {@link renderPassEntries} outputTarget or {@link renderPassEntries} screen) {@link Stack} onto which this Mesh should be added, depending on whether it's projected or not. * @param mesh - Mesh to check. * @returns - The corresponding render pass entry {@link Stack}. */ getMeshProjectionStack(mesh) { const renderPassEntry = mesh.options.useCustomScenePassEntry ? mesh.options.useCustomScenePassEntry : "transmissive" in mesh.options && mesh.options.transmissive ? this.renderer.transmissionTarget.passEntry : this.getRenderTargetPassEntry(mesh.outputTarget); const { stack } = renderPassEntry; return mesh.material.options.rendering.useProjection ? stack.projected : stack.unProjected; } /** * Order a {@link SceneStackedObject} array by using the {@link core/meshes/Mesh.Mesh.renderOrder | renderOrder} or {@link core/meshes/Mesh.Mesh.index | index} properties. * @param stack - {@link SceneStackedObject} to sort, filled with {@link RenderedMesh} or {@link RenderBundle}. */ orderStack(stack) { stack.sort((a, b) => { return a.renderOrder - b.renderOrder || a.index - b.index; }); } /** * Test whether a {@link SceneStackedObject} is a {@link RenderBundle} or not. * @param object - Object to test. * @returns - Whether the object is a {@link RenderBundle} or not. */ isStackObjectRenderBundle(object) { return object.type === "RenderBundle"; } /** * Add a {@link SceneStackedMesh} to the given {@link RenderTarget} corresponding {@link RenderPassEntry}. * @param mesh - {@link SceneStackedMesh} to add. * @param renderTarget - {@link RenderTarget} to get the {@link RenderPassEntry} from. If not set, will add to the first {@link renderPassEntries} `screen` array entry. */ addMeshToRenderTargetStack(mesh, renderTarget = null) { const renderPassEntry = this.getRenderTargetPassEntry(renderTarget); const { stack } = renderPassEntry; const projectionStack = mesh.material.options.rendering.useProjection ? stack.projected : stack.unProjected; const isTransparent = !!mesh.transparent; const similarMeshes = isTransparent ? projectionStack.transparent : projectionStack.opaque; similarMeshes.push(mesh); this.orderStack(similarMeshes); } /** * Add a Mesh to the correct {@link renderPassEntries | render pass entry} {@link Stack} array. * Meshes are then ordered by their {@link core/meshes/mixins/MeshBaseMixin.MeshBaseClass#index | indexes (order of creation]}, {@link core/pipelines/RenderPipelineEntry.RenderPipelineEntry#index | pipeline entry indexes} and then {@link core/meshes/mixins/MeshBaseMixin.MeshBaseClass#renderOrder | renderOrder}. * @param mesh - Mesh to add. */ addMesh(mesh) { if (mesh.renderBundle) { mesh.renderBundle.addMesh(mesh, mesh.outputTarget ? mesh.outputTarget.renderPass : this.renderer.renderPass); } const { useProjection } = mesh.material.options.rendering; if (!mesh.renderBundle) { const projectionStack = this.getMeshProjectionStack(mesh); const isTransparent = !!mesh.transparent; const similarMeshes = isTransparent ? projectionStack.transparent : projectionStack.opaque; similarMeshes.push(mesh); this.orderStack(similarMeshes); } if ("parent" in mesh && !mesh.parent && useProjection) { mesh.parent = this; } } /** * Remove a Mesh from our {@link Scene}. * @param mesh - Mesh to remove. */ removeMesh(mesh) { if (mesh.renderBundle) { mesh.renderBundle.removeMesh(mesh, false); } else { const projectionType = mesh.material.options.rendering.useProjection ? "projected" : "unProjected"; const isTransparent = !!mesh.transparent; const transparencyType = isTransparent ? "transparent" : "opaque"; for (const renderPassEntries of Object.values(this.renderPassEntries)) { renderPassEntries.forEach((renderPassEntry) => { if (renderPassEntry.stack) { renderPassEntry.stack[projectionType][transparencyType] = renderPassEntry.stack[projectionType][transparencyType].filter((m) => m.uuid !== mesh.uuid); } }); } } if ("transmissive" in mesh.options && mesh.options.transmissive) { const transmissivePassEntry = this.renderer.transmissionTarget.passEntry; const nbTransmissiveObjects = transmissivePassEntry ? this.getRenderPassEntryLength(transmissivePassEntry) : 0; if (nbTransmissiveObjects === 0) { this.renderer.destroyTransmissionTarget(); } } if ("parent" in mesh && mesh.parent && mesh.parent.object3DIndex === this.object3DIndex) { mesh.parent = null; } } /** * Add a {@link RenderBundle} to the correct {@link renderPassEntries | render pass entry} {@link Stack} array. * @param renderBundle - {@link RenderBundle} to add. * @param projectionStack - {@link ProjectionStack} onto which to add the {@link RenderBundle}. */ addRenderBundle(renderBundle, projectionStack) { const similarObjects = !!renderBundle.transparent ? projectionStack.transparent : projectionStack.opaque; similarObjects.push(renderBundle); this.orderStack(similarObjects); } /** * Remove a {@link RenderBundle} from our {@link Scene}. * @param renderBundle - {@link RenderBundle} to remove. */ removeRenderBundle(renderBundle) { const isProjected = !!renderBundle.useProjection; const projectionType = isProjected ? "projected" : "unProjected"; const isTransparent = !!renderBundle.transparent; const transparencyType = isTransparent ? "transparent" : "opaque"; const renderPassEntry = this.renderPassEntries.renderTarget.find( (passEntry) => passEntry.renderPass.uuid === renderBundle.options.renderPass?.uuid ); if (renderPassEntry) { const { stack } = renderPassEntry; const projectionStack = stack[projectionType]; projectionStack[transparencyType] = projectionStack[transparencyType].filter( (bundle) => bundle.uuid !== renderBundle.uuid ); } else { this.renderPassEntries.screen.forEach((renderPassEntry2) => { if (renderPassEntry2.stack) { renderPassEntry2.stack[projectionType][transparencyType] = renderPassEntry2.stack[projectionType][transparencyType].filter((m) => m.uuid !== renderBundle.uuid); } }); } } /** * Add a {@link ShaderPass} to our scene {@link renderPassEntries} `prePass` or `postProPass` array. * Before rendering the {@link ShaderPass}, we will copy the correct input texture into its {@link ShaderPass#renderTexture | renderTexture}. * This also handles the {@link renderPassEntries} `postProPass` array entries order: We will first draw selective passes and then finally global post processing passes. * @see {@link https://codesandbox.io/p/sandbox/webgpu-render-to-2-textures-without-texture-copy-c4sx4s?file=%2Fsrc%2Findex.js%3A10%2C4 | minimal code example} * @param shaderPass - {@link ShaderPass} to add. */ addShaderPass(shaderPass) { const onBeforeRenderPass = shaderPass.inputTarget || shaderPass.outputTarget ? null : (commandEncoder, swapChainTexture) => { if (shaderPass.renderTexture && swapChainTexture) { this.renderer.copyGPUTextureToTexture(swapChainTexture, shaderPass.renderTexture, commandEncoder); } this.renderer.postProcessingPass.setLoadOp("clear"); }; const onAfterRenderPass = shaderPass.options.copyOutputToRenderTexture ? (commandEncoder, swapChainTexture) => { if (shaderPass.renderTexture && swapChainTexture) { this.renderer.copyGPUTextureToTexture(swapChainTexture, shaderPass.renderTexture, commandEncoder); } } : null; const outputPass = shaderPass.outputTarget ? shaderPass.outputTarget.renderPass : !shaderPass.options.isPrePass ? this.renderer.postProcessingPass : this.renderer.renderPass; const label = shaderPass.options.isPrePass ? shaderPass.options.label + " scene pre pass" : shaderPass.options.label + " scene post processing pass"; const shaderPassEntry = { label, // use output target or postprocessing render pass renderPass: outputPass, // render to output target renderTexture or directly to screen renderTexture: shaderPass.outputTarget ? shaderPass.outputTarget.renderTexture : null, onBeforeRenderPass, onAfterRenderPass, useCustomRenderPass: null, element: shaderPass, stack: null // explicitly set to null }; if (shaderPass.renderBundle) { const { renderBundle } = shaderPass; if (renderBundle.meshes.size >= 1) { throwWarning( `${renderBundle.options.label} (${renderBundle.type}): Cannot add more than 1 ShaderPass to a render bundle. This ShaderPass will not be added: ${shaderPass.options.label}` ); shaderPass.renderBundle = null; } else { renderBundle.addMesh(shaderPass, outputPass); } } if (!shaderPass.options.isPrePass) { this.renderPassEntries.postProPass.push(shaderPassEntry); this.renderPassEntries.postProPass.sort((a, b) => { const isPostProA = a.element && !a.element.outputTarget; const renderOrderA = a.element ? a.element.renderOrder : 0; const indexA = a.element ? a.element.index : 0; const isPostProB = b.element && !b.element.outputTarget; const renderOrderB = b.element ? b.element.renderOrder : 0; const indexB = b.element ? b.element.index : 0; if (isPostProA && !isPostProB) { return 1; } else if (!isPostProA && isPostProB) { return -1; } else if (renderOrderA !== renderOrderB) { return renderOrderA - renderOrderB; } else { return indexA - indexB; } }); } else { this.renderPassEntries.prePass.push(shaderPassEntry); this.renderPassEntries.prePass.sort( (a, b) => a.element.renderOrder - b.element.renderOrder || a.element.index - b.element.index ); } } /** * Remove a {@link ShaderPass} from our scene {@link renderPassEntries} `prePass` or `postProPass` array. * @param shaderPass - {@link ShaderPass} to remove. */ removeShaderPass(shaderPass) { if (shaderPass.renderBundle) { shaderPass.renderBundle.empty(); } if (!shaderPass.options.isPrePass) { this.renderPassEntries.postProPass = this.renderPassEntries.postProPass.filter( (entry) => !entry.element || entry.element.uuid !== shaderPass.uuid ); } else { this.renderPassEntries.prePass = this.renderPassEntries.prePass.filter( (entry) => !entry.element || entry.element.uuid !== shaderPass.uuid ); } } /** * Add a {@link PingPongPlane} to our scene {@link renderPassEntries} pingPong array. * After rendering the {@link PingPongPlane}, we will copy the context current texture into its {@link PingPongPlane#renderTexture | renderTexture} so we'll be able to use it as an input for the next pass. * @see {@link https://codesandbox.io/p/sandbox/webgpu-render-ping-pong-to-texture-use-in-quad-gwjx9p | minimal code example}. * @param pingPongPlane - {@link PingPongPlane} to add. */ addPingPongPlane(pingPongPlane) { this.renderPassEntries.pingPong.push({ label: pingPongPlane.options.label + " scene pass", renderPass: pingPongPlane.outputTarget.renderPass, renderTexture: pingPongPlane.outputTarget.renderTexture, onBeforeRenderPass: null, onAfterRenderPass: (commandEncoder, swapChainTexture) => { this.renderer.copyGPUTextureToTexture(swapChainTexture, pingPongPlane.renderTexture, commandEncoder); }, useCustomRenderPass: null, element: pingPongPlane, stack: null // explicitly set to null }); if (pingPongPlane.renderBundle) { const { renderBundle } = pingPongPlane; if (renderBundle.meshes.size >= 1) { throwWarning( `${renderBundle.options.label} (${renderBundle.type}): Cannot add more than 1 PingPongPlane to a render bundle. This PingPongPlane will not be added: ${pingPongPlane.options.label}` ); pingPongPlane.renderBundle = null; } else { renderBundle.addMesh(pingPongPlane, pingPongPlane.outputTarget.renderPass); } } this.renderPassEntries.pingPong.sort((a, b) => a.element.renderOrder - b.element.renderOrder); } /** * Remove a {@link PingPongPlane} from our scene {@link renderPassEntries} pingPong array. * @param pingPongPlane - {@link PingPongPlane} to remove. */ removePingPongPlane(pingPongPlane) { if (pingPongPlane.renderBundle) { pingPongPlane.renderBundle.empty(); } this.renderPassEntries.pingPong = this.renderPassEntries.pingPong.filter( (entry) => entry.element.uuid !== pingPongPlane.uuid ); } /** * Get any rendered object or {@link RenderTarget} {@link RenderPassEntry}. Useful to override a {@link RenderPassEntry#onBeforeRenderPass | RenderPassEntry onBeforeRenderPass} or {@link RenderPassEntry#onAfterRenderPass | RenderPassEntry onAfterRenderPass} default behavior. * @param object - The object from which we want to get the parentMesh {@link RenderPassEntry} * @returns - The {@link RenderPassEntry} if found. */ getObjectRenderPassEntry(object) { if (object.type === "RenderTarget") { return this.renderPassEntries.renderTarget.find( (entry) => entry.renderPass.uuid === object.renderPass.uuid ); } else if (object.type === "PingPongPlane") { return this.renderPassEntries.pingPong.find((entry) => entry.element.uuid === object.uuid); } else if (object.type === "ShaderPass") { if (object.options.isPrePass) { return this.renderPassEntries.prePass.find((entry) => entry.element?.uuid === object.uuid); } else { return this.renderPassEntries.postProPass.find((entry) => entry.element?.uuid === object.uuid); } } else { const entryType = object.outputTarget ? "renderTarget" : "screen"; if (object.renderBundle) { return this.renderPassEntries[entryType].find((entry) => { return [ ...entry.stack.unProjected.opaque, ...entry.stack.unProjected.transparent, ...entry.stack.projected.opaque, ...entry.stack.projected.transparent ].filter((object2) => object2.type === "RenderBundle").some((bundle) => { return bundle.meshes.get(object.uuid); }); }); } else { return this.renderPassEntries[entryType].find((entry) => { return [ ...entry.stack.unProjected.opaque, ...entry.stack.unProjected.transparent, ...entry.stack.projected.opaque, ...entry.stack.projected.transparent ].some((mesh) => mesh.uuid === object.uuid); }); } } } /** * Sort transparent projected meshes by their render order or distance to the camera (farther meshes should be drawn first). * @param meshes - Transparent projected meshes array to sort. */ sortTransparentMeshes(meshes) { meshes.sort((meshA, meshB) => { if (meshA.renderOrder !== meshB.renderOrder) { return meshA.renderOrder - meshB.renderOrder; } if (this.isStackObjectRenderBundle(meshA) || this.isStackObjectRenderBundle(meshB)) { return meshA.renderOrder - meshB.renderOrder; } meshA.geometry ? posA.copy(meshA.geometry.boundingBox.center).applyMat4(meshA.worldMatrix) : meshA.worldMatrix.getTranslation(posA); meshB.geometry ? posB.copy(meshB.geometry.boundingBox.center).applyMat4(meshB.worldMatrix) : meshB.worldMatrix.getTranslation(posB); const radiusA = meshA.geometry ? meshA.geometry.boundingBox.radius * meshA.worldMatrix.getMaxScaleOnAxis() : 0; const radiusB = meshB.geometry ? meshB.geometry.boundingBox.radius * meshB.worldMatrix.getMaxScaleOnAxis() : 0; return meshB.camera.worldMatrix.getTranslation(camPosB).distance(posB) - radiusB - (meshA.camera.worldMatrix.getTranslation(camPosA).distance(posA) - radiusA); }); } /** * Here we render a {@link RenderPassEntry}: * - Set its {@link RenderPass#descriptor | renderPass descriptor} view or resolveTarget and get it at as swap chain texture. * - Execute {@link RenderPassEntry#onBeforeRenderPass | onBeforeRenderPass} callback if specified. * - Begin the {@link GPURenderPassEncoder | GPU render pass encoder} using our {@link RenderPass#descriptor | renderPass descriptor}. * - Render the single element if specified or the render pass entry {@link Stack}: draw unprojected opaque / transparent meshes first, then set the {@link core/renderers/GPUCameraRenderer.GPUCameraRenderer#cameraLightsBindGroup | camera and lights bind group} and draw projected opaque / transparent meshes. * - End the {@link GPURenderPassEncoder | GPU render pass encoder}. * - Execute {@link RenderPassEntry#onAfterRenderPass | onAfterRenderPass} callback if specified. * - Reset {@link core/pipelines/PipelineManager.PipelineManager#currentPipelineIndex | pipeline manager current pipeline}. * @param commandEncoder - Current {@link GPUCommandEncoder}. * @param renderPassEntry - {@link RenderPassEntry} to render. */ renderSinglePassEntry(commandEncoder, renderPassEntry) { const swapChainTexture = renderPassEntry.renderPass.updateView(renderPassEntry.renderTexture?.texture); renderPassEntry.onBeforeRenderPass && renderPassEntry.onBeforeRenderPass(commandEncoder, swapChainTexture); if (renderPassEntry.useCustomRenderPass) { renderPassEntry.useCustomRenderPass(commandEncoder); } else { const pass = renderPassEntry.renderPass.beginRenderPass(commandEncoder); if (!this.renderer.production) { pass.pushDebugGroup( renderPassEntry.element ? `${renderPassEntry.element.options.label} render pass using ${renderPassEntry.renderPass.options.label} descriptor` : `Render stack pass using ${renderPassEntry.renderPass.options.label}${renderPassEntry.renderTexture ? " onto " + renderPassEntry.renderTexture.options.label : ""}` ); } if (renderPassEntry.element) { if (renderPassEntry.element.renderBundle) { renderPassEntry.element.renderBundle.render(pass); } else { renderPassEntry.element.render(pass); } } else if (renderPassEntry.stack) { for (const mesh of renderPassEntry.stack.unProjected.opaque) { mesh.render(pass); } for (const mesh of renderPassEntry.stack.unProjected.transparent) { mesh.render(pass); } if (renderPassEntry.stack.projected.opaque.length || renderPassEntry.stack.projected.transparent.length) { for (const mesh of renderPassEntry.stack.projected.opaque) { mesh.render(pass); } this.sortTransparentMeshes(renderPassEntry.stack.projected.transparent); for (const mesh of renderPassEntry.stack.projected.transparent) { mesh.render(pass); } } } if (!this.renderer.production) pass.popDebugGroup(); pass.end(); } renderPassEntry.onAfterRenderPass && renderPassEntry.onAfterRenderPass(commandEncoder, swapChainTexture); this.renderer.pipelineManager.resetCurrentPipeline(); if (renderPassEntry.renderPass.options.useDepth && renderPassEntry.renderPass.options.renderToSwapChain && !renderPassEntry.renderPass.options.depthReadOnly && renderPassEntry.renderPass.options.depthStoreOp === "store" && renderPassEntry.renderPass.depthTexture.uuid === this.renderer.renderPass.depthTexture?.uuid) { __privateSet(this, _shouldLoadDepth, true); } } /** * Before actually rendering the scene, update matrix stack and frustum culling checks. Batching these calls greatly improve performance. Called by the {@link renderer} before rendering. */ onBeforeRender() { if ("lights" in this.renderer) { this.renderer.lights.forEach((light) => { light.onBeforeRenderScene(); }); } this.renderer.meshes.forEach((mesh) => { mesh.onBeforeRenderScene(); }); this.renderer.animations.forEach((targetsAnimation) => targetsAnimation.update()); this.updateMatrixStack(); this.renderer.animations.forEach((targetsAnimation) => targetsAnimation.onAfterUpdate()); for (const mesh of this.renderer.meshes) { if ("checkFrustumCulling" in mesh && mesh.visible) { mesh.checkFrustumCulling(); } } } /** * Render our {@link Scene}. * - Execute {@link onBeforeRender} first. * - Then render {@link computePassEntries}. * - And finally render our {@link renderPassEntries}. * @param commandEncoder - Current {@link GPUCommandEncoder}. */ render(commandEncoder) { for (const computePass of this.computePassEntries) { const pass = commandEncoder.beginComputePass(); if (!this.renderer.production) pass.pushDebugGroup(`${computePass.options.label}: begin compute pass`); computePass.render(pass); if (!this.renderer.production) pass.popDebugGroup(); pass.end(); computePass.copyBufferToResult(commandEncoder); this.renderer.pipelineManager.resetCurrentPipeline(); } __privateSet(this, _shouldLoadColors, false); __privateSet(this, _shouldLoadDepth, false); for (const renderPassEntryType in this.renderPassEntries) { if (renderPassEntryType === "postProPass") { this.renderer.renderPass.setDepthReadOnly(false); this.renderer.renderPass.setDepthLoadOp("clear"); __privateSet(this, _shouldLoadColors, true); } this.renderPassEntries[renderPassEntryType].forEach((renderPassEntry) => { if (!this.getRenderPassEntryLength(renderPassEntry)) return; if (renderPassEntryType === "prePass" || renderPassEntryType === "postProPass") { renderPassEntry.renderPass.setDepthReadOnly(true); } else { renderPassEntry.renderPass.setDepthReadOnly(false); } if (renderPassEntryType === "postProPass" && renderPassEntry.renderPass.uuid !== this.renderer.postProcessingPass.uuid) { __privateSet(this, _shouldLoadColors, false); } renderPassEntry.renderPass.setLoadOp(__privateGet(this, _shouldLoadColors) ? "load" : "clear"); renderPassEntry.renderPass.setDepthLoadOp( __privateGet(this, _shouldLoadDepth) && !renderPassEntry.renderPass.options.depthReadOnly ? "load" : "clear" ); this.renderSinglePassEntry(commandEncoder, renderPassEntry); if (renderPassEntryType !== "renderTarget") { __privateSet(this, _shouldLoadColors, true); } }); } } } _shouldLoadColors = new WeakMap(); _shouldLoadDepth = new WeakMap(); export { Scene };