UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

154 lines (151 loc) 7.8 kB
import { DebugGraphics } from '../../platform/graphics/debug-graphics.js'; import { BlendState } from '../../platform/graphics/blend-state.js'; import { RenderPass } from '../../platform/graphics/render-pass.js'; import { SHADER_DEPTH_PICK, SHADER_PICK } from '../../scene/constants.js'; /** * @import { BindGroup } from '../../platform/graphics/bind-group.js' * @import { CameraComponent } from '../components/camera/component.js' * @import { Scene } from '../../scene/scene.js' * @import { Layer } from '../../scene/layer.js' * @import { MeshInstance } from '../../scene/mesh-instance.js' * @import { GSplatComponent } from '../components/gsplat/component.js' */ const tempMeshInstances = []; const lights = [ [], [], [] ]; const defaultShadowAtlasParams = new Float32Array(2); /** * A render pass implementing rendering of mesh instances into a pick buffer. * * @ignore */ class RenderPassPicker extends RenderPass { destroy() { this.viewBindGroups.forEach((bg)=>{ bg.defaultUniformBuffer.destroy(); bg.destroy(); }); this.viewBindGroups.length = 0; } /** * @param {CameraComponent} camera - The camera component used for picking. * @param {Scene} scene - The scene to pick from. * @param {Layer[]} layers - The layers to pick from. * @param {Map<number, MeshInstance | GSplatComponent>} mapping - Map to store ID to object mappings. * @param {boolean} depth - Whether to render depth information. */ update(camera, scene, layers, mapping, depth) { this.camera = camera; this.scene = scene; this.layers = layers; this.mapping = mapping; this.depth = depth; if (scene.clusteredLightingEnabled) { this.emptyWorldClusters = this.renderer.worldClustersAllocator.empty; } } // Filter qualifying layers and prepare gsplat pick mesh instances for the compute-based // renderer. The execute() loop iterates the pre-built list instead of re-filtering. before() { this._qualifiedLayerIndices.length = 0; this._pickMeshInstances.clear(); const { camera, scene, layers } = this; const srcLayers = scene.layers.layerList; const subLayerEnabled = scene.layers.subLayerEnabled; const gsplatDirector = this.renderer.gsplatDirector; const pickerWidth = this.renderTarget?.width ?? 1; const pickerHeight = this.renderTarget?.height ?? 1; for(let i = 0; i < srcLayers.length; i++){ const srcLayer = srcLayers[i]; if (layers && layers.indexOf(srcLayer) < 0) continue; if (!srcLayer.enabled || !subLayerEnabled[i]) continue; if (!srcLayer.camerasSet.has(camera.camera)) continue; // store the index of the layers we need to render this._qualifiedLayerIndices.push(i); // kick off a compute tiled renderer for the gsplat manager on this layer, and store the mesh instance // which copies the results to the pick buffer if (gsplatDirector) { const pickMI = gsplatDirector.prepareForPicking(camera.camera, pickerWidth, pickerHeight, srcLayer); if (pickMI) { this._pickMeshInstances.set(i, pickMI); } } } } execute() { const device = this.device; const { renderer, camera, scene, mapping, renderTarget } = this; const srcLayers = scene.layers.layerList; const isTransparent = scene.layers.subLayerList; for (const i of this._qualifiedLayerIndices){ const srcLayer = srcLayers[i]; const transparent = isTransparent[i]; DebugGraphics.pushGpuMarker(device, `${srcLayer.name}(${transparent ? 'TRANSP' : 'OPAQUE'})`); // if the layer clears the depth if (srcLayer._clearDepthBuffer) { renderer.clear(camera.camera, false, true, false); } // Use mesh instances from the layer. Ideally we'd just pick culled instances for the camera, // but we have no way of knowing if culling has been performed since changes to the layer. // Disadvantage here is that we render all mesh instances, even those not visible by the camera. const meshInstances = srcLayer.meshInstances; // only need mesh instances with a pick flag for(let j = 0; j < meshInstances.length; j++){ const meshInstance = meshInstances[j]; if (meshInstance.pick && meshInstance.transparent === transparent) { tempMeshInstances.push(meshInstance); // keep the index -> meshInstance index mapping mapping.set(meshInstance.id, meshInstance); } } // Inject gsplat pick mesh instance for this layer (compute-based renderer only) const pickMI = this._pickMeshInstances.get(i); if (pickMI) { tempMeshInstances.push(pickMI); } // Process gsplat placements when ID is enabled // The gsplat unified mesh instance is already handled above (added to layer.meshInstances) // Here we just need to add the placement ID -> component mapping if (scene.gsplat.enableIds) { const placements = srcLayer.gsplatPlacements; for(let j = 0; j < placements.length; j++){ const placement = placements[j]; const component = placement.node?.gsplat; if (component) { mapping.set(placement.id, component); } } } if (tempMeshInstances.length > 0) { // upload clustered lights uniforms const clusteredLightingEnabled = scene.clusteredLightingEnabled; if (clusteredLightingEnabled) { const lightClusters = this.emptyWorldClusters; lightClusters.activate(); } renderer.setCameraUniforms(camera.camera, renderTarget); // TODO: These uniforms are not required by the picker pass, but it uses the // forward view format which includes them. Ideally, each pass should be able // to specify its own view format to avoid setting unnecessary uniforms. renderer.dispatchGlobalLights(scene); device.scope.resolve('shadowAtlasParams').setValue(defaultShadowAtlasParams); if (device.supportsUniformBuffers) { // Initialize view bind group format if not already done renderer.initViewBindGroupFormat(clusteredLightingEnabled); renderer.setupViewUniformBuffers(this.viewBindGroups, renderer.viewUniformFormat, renderer.viewBindGroupFormat, null); } const shaderPass = this.depth ? SHADER_DEPTH_PICK : SHADER_PICK; renderer.renderForward(camera.camera, renderTarget, tempMeshInstances, lights, shaderPass, (meshInstance)=>{ device.setBlendState(this.blendState); }); tempMeshInstances.length = 0; } DebugGraphics.popGpuMarker(device); } } constructor(device, renderer){ super(device), /** @type {BindGroup[]} */ this.viewBindGroups = [], /** @type {BlendState} */ this.blendState = BlendState.NOBLEND, /** @type {number[]} */ this._qualifiedLayerIndices = [], /** @type {Map<number, MeshInstance|null>} */ this._pickMeshInstances = new Map(); this.renderer = renderer; } } export { RenderPassPicker };