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.
732 lines (729 loc) • 27.7 kB
JavaScript
import { GPURenderer } from './GPURenderer.mjs';
import { PerspectiveCamera } from '../cameras/PerspectiveCamera.mjs';
import { BufferBinding } from '../bindings/BufferBinding.mjs';
import { BindGroup } from '../bindGroups/BindGroup.mjs';
import { throwWarning } from '../../utils/utils.mjs';
import { directionalShadowStruct } from '../shadows/DirectionalShadow.mjs';
import { pointShadowStruct } from '../shadows/PointShadow.mjs';
import { spotShadowStruct } from '../shadows/SpotShadow.mjs';
import { Texture } from '../textures/Texture.mjs';
import { Sampler } from '../samplers/Sampler.mjs';
import { OrthographicCamera } from '../cameras/OrthographicCamera.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"), 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 __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
var _shouldUpdateCameraLightsBindGroup, _GPUCameraRenderer_instances, initLights_fn, updateLightsCount_fn;
class GPUCameraRenderer extends GPURenderer {
/**
* GPUCameraRenderer constructor
* @param parameters - {@link GPUCameraRendererParams | parameters} used to create this {@link GPUCameraRenderer}
*/
constructor({
deviceManager,
label,
container,
pixelRatio = 1,
autoResize = true,
context = {},
renderPass,
camera = {},
lights = {}
}) {
super({
deviceManager,
label,
container,
pixelRatio,
autoResize,
context,
renderPass
});
__privateAdd(this, _GPUCameraRenderer_instances);
/** @ignore */
__privateAdd(this, _shouldUpdateCameraLightsBindGroup);
this.type = "GPUCameraRenderer";
camera = { ...{ fov: 50, near: 0.1, far: 1e3 }, ...camera };
if (lights !== false) {
lights = {
...{
maxAmbientLights: 2,
maxDirectionalLights: 5,
maxPointLights: 5,
maxSpotLights: 5,
useUniformsForShadows: false
},
...lights
};
}
this.options = {
...this.options,
camera,
lights
};
this.bindings = {};
__privateSet(this, _shouldUpdateCameraLightsBindGroup, true);
this.lights = [];
this.cameraViewport = null;
this.setCamera(camera);
this.setCameraBinding();
if (this.options.lights) {
__privateMethod(this, _GPUCameraRenderer_instances, initLights_fn).call(this);
}
this.setCameraLightsBindGroup();
}
/**
* Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} is lost.
* Reset all our samplers, force all our scene objects and camera bind group to lose context.
*/
loseContext() {
super.loseContext();
this.cameraLightsBindGroup.loseContext();
this.pointShadowsCubeFaceBindGroups.forEach((bindGroup) => bindGroup.loseContext());
}
/**
* Called when the {@link core/renderers/GPUDeviceManager.GPUDeviceManager#device | device} has been restored.
* Configure the context again, resize the {@link core/renderPasses/RenderTarget.RenderTarget | render targets} and {@link core/textures/Texture.Texture | textures}, restore our {@link renderedObjects | rendered objects} context, re-write our {@link cameraLightsBindGroup | camera, lights and shadows bind group} bindings.
*/
restoreContext() {
super.restoreContext();
this.cameraLightsBindGroup?.restoreContext();
this.pointShadowsCubeFaceBindGroups.forEach((bindGroup) => bindGroup.restoreContext());
this.updateCameraBindings();
}
/**
* Set our {@link renderPass | main render pass} and our {@link transmissionTarget} sampler.
*/
setMainRenderPasses() {
super.setMainRenderPasses();
this.transmissionTarget = {
sampler: new Sampler(this, {
label: "Transmission sampler",
name: "transmissionSampler",
magFilter: "linear",
minFilter: "linear",
mipmapFilter: "linear",
addressModeU: "clamp-to-edge",
addressModeV: "clamp-to-edge"
})
};
}
/* CAMERA */
/**
* Set the default {@link camera}.
* @param cameraParameters - {@link PerspectiveCameraBaseOptions | parameters} used to create the {@link camera}.
*/
setCamera(cameraParameters) {
const { width, height } = this.rectBBox;
this.useCamera(
new PerspectiveCamera({
fov: cameraParameters.fov,
near: cameraParameters.near,
far: cameraParameters.far,
width,
height,
pixelRatio: this.pixelRatio,
onMatricesChanged: () => {
this.onCameraMatricesChanged();
}
})
);
}
/**
* Tell our {@link GPUCameraRenderer} to use this {@link RendererCamera}. If a {@link RendererCamera | camera} has already been set, reset the {@link GPUCameraRenderer#bindings.camera | camera binding} inputs view values and the {@link meshes} {@link RendererCamera} object.
* @param camera - New {@link RendererCamera} to use.
* @returns - This {@link GPUCameraRenderer} with its {@link RendererCamera | camera} type updated.
*/
useCamera(camera) {
if (this.camera && camera && this.camera.uuid === camera.uuid) return;
if (this.camera) {
this.camera.parent = null;
this.camera.onMatricesChanged = () => {
};
}
this.camera = camera;
this.camera.parent = this.scene;
this.resizeCamera();
if (this.bindings.camera) {
this.camera.onMatricesChanged = () => this.onCameraMatricesChanged();
this.bindings.camera.inputs.view.value = this.camera.viewMatrix;
this.bindings.camera.inputs.projection.value = this.camera.projectionMatrix;
this.bindings.camera.inputs.position.value = this.camera.actualPosition;
for (const mesh of this.meshes) {
if ("modelViewMatrix" in mesh) {
mesh.camera = this.camera;
}
}
}
return this;
}
/**
* Update the {@link cameraViewport} if needed (i.e. if the camera use a different aspect ratio than the renderer).
*/
updateCameraViewport() {
let { width, height } = this.canvas;
if (this.viewport) {
width = Math.min(width, this.viewport.width);
height = Math.min(height, this.viewport.height);
}
if (this.camera instanceof PerspectiveCamera && this.camera.forceAspect) {
width = Math.min(width, height * this.camera.forceAspect);
height = Math.min(width / this.camera.forceAspect, height);
this.setCameraViewport({
width,
height,
top: (this.canvas.height - height) * 0.5,
left: (this.canvas.width - width) * 0.5,
minDepth: 0,
maxDepth: 1
});
} else {
this.setCameraViewport();
}
}
/**
* Resize the {@link camera}, first by updating the {@link cameraViewport} and then resetting the {@link camera} projection.
*/
resizeCamera() {
this.updateCameraViewport();
const { width, height } = this.cameraViewport ?? this.viewport ?? this.canvas;
if (this.camera instanceof PerspectiveCamera) {
this.camera?.setPerspective({
width,
height,
pixelRatio: this.pixelRatio
});
} else if (this.camera instanceof OrthographicCamera) {
const aspect = width / height;
const frustumSize = this.camera.top * 2;
this.camera.setOrthographic({
left: -frustumSize * aspect / 2,
right: frustumSize * aspect / 2,
pixelRatio: this.pixelRatio
});
}
}
/**
* Set the {@link cameraViewport} (that should be contained within the renderer {@link GPURenderer#viewport | viewport} if any) and update the {@link renderPass} and {@link postProcessingPass} {@link GPURenderer#viewport | viewport} values.
* @param viewport - {@link RenderPassViewport} settings to use if any.
*/
setCameraViewport(viewport = null) {
this.cameraViewport = viewport;
if (!this.cameraViewport) {
this.renderPass?.setViewport(this.viewport);
this.postProcessingPass?.setViewport(this.viewport);
} else {
if (this.viewport) {
const aspect = this.cameraViewport.width / this.cameraViewport.height;
this.cameraViewport.width = Math.min(this.viewport.width, this.viewport.height * aspect);
this.cameraViewport.height = Math.min(this.cameraViewport.width / aspect, this.viewport.height);
this.cameraViewport.left = Math.max(0, (this.viewport.width - this.cameraViewport.width) * 0.5);
this.cameraViewport.top = Math.max(0, (this.viewport.height - this.cameraViewport.height) * 0.5);
}
this.renderPass?.setViewport(this.cameraViewport);
this.postProcessingPass?.setViewport(this.cameraViewport);
}
}
/**
* Resize the {@link camera} whenever the {@link GPURenderer#viewport | viewport} is updated.
* @param viewport - {@link RenderPassViewport} settings to use if any. Can be set to `null` to cancel the {@link GPURenderer#viewport | viewport}.
*/
setViewport(viewport = null) {
super.setViewport(viewport);
this.resizeCamera();
}
/**
* Update the {@link core/renderers/GPURenderer.ProjectedMesh | projected meshes} sizes and positions when the {@link camera} {@link Camera#position | position} changes.
*/
onCameraMatricesChanged() {
this.updateCameraBindings();
for (const mesh of this.meshes) {
if ("modelViewMatrix" in mesh) {
mesh.shouldUpdateProjectionMatrixStack();
}
}
}
/**
* Create a {@link BufferBinding} from a given {@link Camera}. Used internally but can also be used to create a new {@link BufferBinding} from a different camera than this renderer's {@link RendererCamera | camera}.
* @param camera - {@link Camera} to use to create the {@link BufferBinding}.
* @param label - Optional label to use for the {@link BufferBinding}.
* @returns - Newly created {@link BufferBinding}.
*/
createCameraBinding(camera, label = "Camera") {
return new BufferBinding({
label,
name: "camera",
visibility: ["vertex", "fragment"],
struct: {
view: {
// camera view matrix
type: "mat4x4f",
value: camera.viewMatrix
},
projection: {
// camera projection matrix
type: "mat4x4f",
value: camera.projectionMatrix
},
position: {
// camera world position
type: "vec3f",
value: camera.actualPosition
}
}
});
}
/**
* Set the {@link GPUCameraRenderer#bindings.camera | camera BufferBinding}.
*/
setCameraBinding() {
this.bindings.camera = this.createCameraBinding(this.camera);
}
/**
* Add a {@link Light} to the {@link lights} array.
* @param light - {@link Light} to add.
*/
addLight(light) {
this.lights.push(light);
__privateMethod(this, _GPUCameraRenderer_instances, updateLightsCount_fn).call(this, light.type);
}
/**
* Remove a {@link Light} from the {@link lights} array.
* @param light - {@link Light} to remove.
*/
removeLight(light) {
this.lights = this.lights.filter((l) => l.uuid !== light.uuid);
__privateMethod(this, _GPUCameraRenderer_instances, updateLightsCount_fn).call(this, light.type);
}
/**
* Set the lights {@link BufferBinding} based on the {@link lightsBindingParams}.
*/
setLightsBinding() {
if (!this.options.lights) return;
this.lightsBindingParams = {
ambientLights: {
max: this.options.lights.maxAmbientLights,
label: "Ambient lights",
params: {
color: {
type: "array<vec3f>",
size: 3
}
}
},
directionalLights: {
max: this.options.lights.maxDirectionalLights,
label: "Directional lights",
params: {
color: {
type: "array<vec3f>",
size: 3
},
direction: {
type: "array<vec3f>",
size: 3
}
}
},
pointLights: {
max: this.options.lights.maxPointLights,
label: "Point lights",
params: {
color: {
type: "array<vec3f>",
size: 3
},
position: {
type: "array<vec3f>",
size: 3
},
range: {
type: "array<f32>",
size: 1
}
}
},
spotLights: {
max: this.options.lights.maxSpotLights,
label: "Spot lights",
params: {
color: {
type: "array<vec3f>",
size: 3
},
direction: {
type: "array<vec3f>",
size: 3
},
position: {
type: "array<vec3f>",
size: 3
},
coneCos: {
type: "array<f32>",
size: 1
},
penumbraCos: {
type: "array<f32>",
size: 1
},
range: {
type: "array<f32>",
size: 1
}
}
}
};
const lightsBindings = {
ambientLights: null,
directionalLights: null,
pointLights: null,
spotLights: null
};
Object.keys(lightsBindings).forEach((lightsType) => {
this.setLightsTypeBinding(lightsType);
});
}
/**
* Set or reset the {@link BufferBinding} for a given {@link LightsType | type of light}.
* @param lightsType - {@link LightsType | Type of light} for which to create the {@link BufferBinding}.
*/
setLightsTypeBinding(lightsType) {
const structParams = Object.keys(this.lightsBindingParams[lightsType].params).map((paramKey) => {
return {
key: paramKey,
type: this.lightsBindingParams[lightsType].params[paramKey].type,
size: this.lightsBindingParams[lightsType].params[paramKey].size
};
}).reduce((acc, binding) => {
acc[binding.key] = {
type: binding.type,
value: new Float32Array(Math.max(this.lightsBindingParams[lightsType].max, 1) * binding.size)
};
return acc;
}, {});
this.bindings[lightsType] = new BufferBinding({
label: this.lightsBindingParams[lightsType].label,
name: lightsType,
bindingType: "storage",
visibility: ["fragment", "compute"],
// TODO needed in compute?
struct: {
count: {
type: "i32",
value: 0
},
...structParams
}
});
}
/**
* Called when a {@link LightsType | type of light} has overflown its maximum capacity. Destroys the associated {@link BufferBinding} (and eventually the associated shadow {@link BufferBinding}), recreates the {@link cameraLightsBindGroup | camera, lights and shadows bind group} and reset all lights for this {@link LightsType | type of light}.
* @param lightsType - {@link LightsType | Type of light} that has overflown its maximum capacity.
* @param lightIndex - The {@link Light#index | light index} that caused overflow. Will be used to reset the new max light count.
*/
onMaxLightOverflow(lightsType, lightIndex = 0) {
if (!this.options.lights) {
if (!this.production) {
throwWarning(
`${this.options.label} (${this.type}): You are adding a light (${lightsType}) to a renderer that should not initially handle lights. The renderer bind group will be re-created. This should be avoided.`
);
}
__privateMethod(this, _GPUCameraRenderer_instances, initLights_fn).call(this);
this.cameraLightsBindGroup.destroy();
this.setCameraLightsBindGroup();
} else {
if (!this.production) {
throwWarning(
`${this.options.label} (${this.type}): You are overflowing the current max lights count of '${this.lightsBindingParams[lightsType].max}' for this type of lights: ${lightsType}. This should be avoided by setting a larger ${"max" + lightsType.charAt(0).toUpperCase() + lightsType.slice(1)} when instancing your ${this.type}.`
);
}
this.lightsBindingParams[lightsType].max = Math.max(this.lightsBindingParams[lightsType].max, lightIndex + 1);
const oldLightBinding = this.cameraLightsBindGroup.getBindingByName(lightsType);
if (oldLightBinding) {
this.cameraLightsBindGroup.destroyBufferBinding(oldLightBinding);
}
this.setLightsTypeBinding(lightsType);
const lightBindingIndex = this.cameraLightsBindGroup.bindings.findIndex((binding) => binding.name === lightsType);
if (lightBindingIndex !== -1) {
this.cameraLightsBindGroup.bindings[lightBindingIndex] = this.bindings[lightsType];
} else {
this.bindings[lightsType].shouldResetBindGroup = true;
this.bindings[lightsType].shouldResetBindGroupLayout = true;
this.cameraLightsBindGroup.addBinding(this.bindings[lightsType]);
this.shouldUpdateCameraLightsBindGroup();
}
if (lightsType === "directionalLights" || lightsType === "pointLights" || lightsType === "spotLights") {
const shadowsType = lightsType.replace("Lights", "") + "Shadows";
const oldShadowsBinding = this.cameraLightsBindGroup.getBindingByName(shadowsType);
if (oldShadowsBinding) {
this.cameraLightsBindGroup.destroyBufferBinding(oldShadowsBinding);
}
this.setShadowsTypeBinding(lightsType);
const shadowsBindingIndex = this.cameraLightsBindGroup.bindings.findIndex(
(binding) => binding.name === shadowsType
);
if (shadowsBindingIndex !== -1) {
this.cameraLightsBindGroup.bindings[shadowsBindingIndex] = this.bindings[shadowsType];
} else {
this.bindings[shadowsType].shouldResetBindGroup = true;
this.bindings[shadowsType].shouldResetBindGroupLayout = true;
this.cameraLightsBindGroup.addBinding(this.bindings[shadowsType]);
this.shouldUpdateCameraLightsBindGroup();
}
}
this.cameraLightsBindGroup.resetEntries();
this.cameraLightsBindGroup.createBindGroup();
this.lights.forEach((light) => {
if (light.type === lightsType) {
light.reset();
}
});
}
}
/* SHADOW MAPS */
/**
* Get all the current {@link ShadowCastingLights | lights that can cast shadows}.
* @returns - All {@link ShadowCastingLights | lights that can cast shadows}.
*/
get shadowCastingLights() {
return this.lights?.filter(
(light) => light.type === "directionalLights" || light.type === "pointLights" || light.type === "spotLights"
);
}
/**
* Set the shadows {@link BufferBinding} based on the {@link shadowsBindingsStruct}.
*/
setShadowsBinding() {
this.shadowsBindingsStruct = {
directional: directionalShadowStruct,
point: pointShadowStruct,
spot: spotShadowStruct
};
this.setShadowsTypeBinding("directionalLights");
this.setShadowsTypeBinding("pointLights");
this.setShadowsTypeBinding("spotLights");
}
/**
* Set or reset the associated shadow {@link BufferBinding} for a given {@link LightsType | type of light}.
* @param lightsType - {@link LightsType | Type of light} for which to create the associated shadow {@link BufferBinding}.
*/
setShadowsTypeBinding(lightsType) {
const type = lightsType.replace("Lights", "");
const shadowsType = type + "Shadows";
const struct = this.shadowsBindingsStruct[type];
const label = type.charAt(0).toUpperCase() + type.slice(1) + " shadows";
this.bindings[shadowsType] = new BufferBinding({
label,
name: shadowsType,
bindingType: this.options.lights && this.options.lights.useUniformsForShadows ? "uniform" : "storage",
visibility: ["vertex", "fragment", "compute"],
// TODO needed in compute?
childrenBindings: [
{
binding: new BufferBinding({
label: label + " element",
name: shadowsType + "Elements",
bindingType: "uniform",
visibility: ["vertex", "fragment"],
struct
}),
count: Math.max(1, this.lightsBindingParams[lightsType].max),
forceArray: true
// needs to be iterable anyway!
}
]
});
}
/* CAMERA, LIGHTS & SHADOWS BIND GROUP */
/**
* Set the {@link cameraLightsBindGroup | camera, lights and shadows bind group}.
*/
setCameraLightsBindGroup() {
this.cameraLightsBindGroup = new BindGroup(this, {
label: this.options.label + ": Camera and lights uniform bind group",
bindings: Object.keys(this.bindings).map((bindingName) => this.bindings[bindingName]).flat()
});
this.cameraLightsBindGroup.consumers.add(this.uuid);
this.pointShadowsCubeFaceBindGroups = [];
for (let face = 0; face < 6; face++) {
const cubeFace = new BufferBinding({
label: "Cube face",
name: "cubeFace",
bindingType: "uniform",
visibility: ["vertex"],
struct: {
face: {
type: "u32",
value: face
}
}
});
const cubeBindGroup = new BindGroup(this, {
label: `Cube face bind group ${face}`,
bindings: [cubeFace]
});
cubeBindGroup.createBindGroup();
cubeBindGroup.consumers.add(this.uuid);
this.pointShadowsCubeFaceBindGroups.push(cubeBindGroup);
}
if (this.device) {
this.createCameraLightsBindGroup();
}
}
/**
* Create the {@link cameraLightsBindGroup | camera, lights and shadows bind group} buffers
*/
createCameraLightsBindGroup() {
if (this.cameraLightsBindGroup && this.cameraLightsBindGroup.shouldCreateBindGroup) {
this.cameraLightsBindGroup.setIndex(0);
this.cameraLightsBindGroup.createBindGroup();
}
}
/**
* Tell our {@link cameraLightsBindGroup | camera, lights and shadows bind group} to update.
*/
shouldUpdateCameraLightsBindGroup() {
__privateSet(this, _shouldUpdateCameraLightsBindGroup, true);
}
/**
* Tell our {@link GPUCameraRenderer#bindings.camera | camera buffer binding} that we should update its bindings and update the bind group. Called each time the camera matrices change.
*/
updateCameraBindings() {
this.bindings.camera?.shouldUpdateBinding("view");
this.bindings.camera?.shouldUpdateBinding("projection");
this.bindings.camera?.shouldUpdateBinding("position");
this.shouldUpdateCameraLightsBindGroup();
}
/**
* Update the {@link cameraLightsBindGroup | camera and lights BindGroup}.
*/
updateCameraLightsBindGroup() {
if (this.cameraLightsBindGroup && __privateGet(this, _shouldUpdateCameraLightsBindGroup)) {
this.cameraLightsBindGroup.update();
__privateSet(this, _shouldUpdateCameraLightsBindGroup, false);
}
}
/**
* Get all objects ({@link core/renderers/GPURenderer.RenderedMesh | rendered meshes} or {@link core/computePasses/ComputePass.ComputePass | compute passes}) using a given {@link AllowedBindGroups | bind group}, including {@link cameraLightsBindGroup | camera and lights bind group}.
* Useful to know if a resource is used by multiple objects and if it is safe to destroy it or not.
* @param bindGroup - {@link AllowedBindGroups | bind group} to check
*/
getObjectsByBindGroup(bindGroup) {
return this.deviceRenderedObjects.filter((object) => {
return [
...object.material.bindGroups,
...object.material.inputsBindGroups,
...object.material.clonedBindGroups,
this.cameraLightsBindGroup
].some((bG) => bG.uuid === bindGroup.uuid);
});
}
/* TRANSMISSIVE */
/**
* Create the {@link transmissionTarget} {@link Texture} and {@link RenderPassEntry} if not already created.
*/
createTransmissionTarget() {
if (!this.transmissionTarget.texture) {
this.transmissionTarget.passEntry = this.scene.createScreenPassEntry("Transmission scene screen render pass");
this.transmissionTarget.texture = new Texture(this, {
label: "Transmission background scene render target output",
name: "transmissionBackgroundTexture",
generateMips: true,
// needed for roughness LOD!
format: this.options.context.format,
autoDestroy: false
});
this.transmissionTarget.passEntry.onBeforeRenderPass = (commandEncoder, swapChainTexture) => {
this.copyGPUTextureToTexture(swapChainTexture, this.transmissionTarget.texture, commandEncoder);
};
}
}
/**
* Destroy the {@link transmissionTarget} {@link Texture} and {@link RenderPassEntry} if already created.
*/
destroyTransmissionTarget() {
if (this.transmissionTarget.texture) {
this.transmissionTarget.texture.destroy();
this.scene.renderPassEntries.screen = this.scene.renderPassEntries.screen.filter(
(passEntry) => passEntry.label !== "Transmission scene screen render pass"
);
this.transmissionTarget.texture = null;
this.transmissionTarget.passEntry = null;
}
}
/**
* Resize our {@link GPUCameraRenderer} and resize our {@link camera} before anything else.
* @param rectBBox - the optional new {@link canvas} {@link RectBBox} to set
*/
resize(rectBBox = null) {
this.setSize(rectBBox);
this.resizeCamera();
this._onResizeCallback && this._onResizeCallback();
this.resizeObjects();
this._onAfterResizeCallback && this._onAfterResizeCallback();
}
/* RENDER */
/**
* {@link createCameraLightsBindGroup | Set the camera bind group if needed} and then call our {@link GPURenderer#render | GPURenderer render method}.
* @param commandEncoder - Current {@link GPUCommandEncoder}.
*/
render(commandEncoder) {
if (!this.ready) return;
this.createCameraLightsBindGroup();
this.updateCameraLightsBindGroup();
super.render(commandEncoder);
if (this.cameraLightsBindGroup) {
this.cameraLightsBindGroup.needsPipelineFlush = false;
}
}
/**
* Destroy our {@link GPUCameraRenderer}
*/
destroy() {
this.cameraLightsBindGroup?.destroy();
this.pointShadowsCubeFaceBindGroups.forEach((bindGroup) => bindGroup.destroy());
this.destroyTransmissionTarget();
this.lights.forEach((light) => light.destroy());
super.destroy();
this.lights.forEach((light) => this.removeLight(light));
}
}
_shouldUpdateCameraLightsBindGroup = new WeakMap();
_GPUCameraRenderer_instances = new WeakSet();
/* LIGHTS */
/**
* Initialize the lights and shadows bindings.
* @private
*/
initLights_fn = function() {
if (!this.options.lights) {
this.options.lights = {
maxAmbientLights: 2,
maxDirectionalLights: 5,
maxPointLights: 5,
maxSpotLights: 5,
useUniformsForShadows: false
};
}
this.setLightsBinding();
this.setShadowsBinding();
};
/**
* Update the lights count for the given {@link LightsType} based on the max light index.
* @param lightsType - {@link LightsType} for which to update the light count.
* @private
*/
updateLightsCount_fn = function(lightsType) {
let maxIndex = 0;
this.lights.filter((light) => light.type === lightsType).forEach((light) => {
maxIndex = Math.max(maxIndex, light.index + 1);
});
this.bindings[lightsType].inputs.count.value = maxIndex;
this.bindings[lightsType].inputs.count.shouldUpdate = true;
};
export { GPUCameraRenderer };