UNPKG

playcanvas

Version:

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

354 lines (353 loc) 10.5 kB
import { EventHandler } from "../core/event-handler.js"; import { Color } from "../core/math/color.js"; import { Vec3 } from "../core/math/vec3.js"; import { Quat } from "../core/math/quat.js"; import { math } from "../core/math/math.js"; import { Mat3 } from "../core/math/mat3.js"; import { Mat4 } from "../core/math/mat4.js"; import { PIXELFORMAT_RGBA8, ADDRESS_CLAMP_TO_EDGE, FILTER_LINEAR } from "../platform/graphics/constants.js"; import { BAKE_COLORDIR, LAYERID_IMMEDIATE } from "./constants.js"; import { LightingParams } from "./lighting/lighting-params.js"; import { GSplatParams } from "./gsplat-unified/gsplat-params.js"; import { Sky } from "./skybox/sky.js"; import { Immediate } from "./immediate/immediate.js"; import { EnvLighting } from "./graphics/env-lighting.js"; import { FogParams } from "./fog-params.js"; class Scene extends EventHandler { static EVENT_SETLAYERS = "set:layers"; static EVENT_SETSKYBOX = "set:skybox"; static EVENT_PRERENDER = "prerender"; static EVENT_POSTRENDER = "postrender"; static EVENT_PRERENDER_LAYER = "prerender:layer"; static EVENT_POSTRENDER_LAYER = "postrender:layer"; static EVENT_PRECULL = "precull"; static EVENT_POSTCULL = "postcull"; ambientBake = false; ambientBakeOcclusionBrightness = 0; ambientBakeOcclusionContrast = 0; ambientLight = new Color(0, 0, 0); ambientLuminance = 0; exposure = 1; lightmapSizeMultiplier = 1; lightmapMaxResolution = 2048; lightmapMode = BAKE_COLORDIR; lightmapFilterEnabled = false; lightmapHDR = false; root = null; physicalUnits = false; _envAtlas = null; _skyboxCubeMap = null; _fogParams = new FogParams(); forcePassThroughSpecular = false; constructor(graphicsDevice) { super(); this.device = graphicsDevice; this._gravity = new Vec3(0, -9.8, 0); this._layers = null; this._prefilteredCubemaps = []; this._internalEnvAtlas = null; this._skyboxIntensity = 1; this._skyboxLuminance = 0; this._skyboxMip = 0; this._skyboxHighlightMultiplier = 1; this._skyboxRotationShaderInclude = false; this._skyboxRotation = new Quat(); this._skyboxRotationMat3 = new Mat3(); this._skyboxRotationMat4 = new Mat4(); this._ambientBakeNumSamples = 1; this._ambientBakeSpherePart = 0.4; this._lightmapFilterRange = 10; this._lightmapFilterSmoothness = 0.2; this._clusteredLightingEnabled = true; this._lightingParams = new LightingParams(this.device.supportsAreaLights, this.device.maxTextureSize, () => { this.updateShaders = true; }); this.gsplatCentersEnabled = true; this._gsplatParams = new GSplatParams(this.device); this._sky = new Sky(this); this._stats = { meshInstances: 0, lights: 0, dynamicLights: 0, bakedLights: 0, updateShadersTime: 0 // deprecated }; this.updateShaders = true; this._shaderVersion = 0; this.immediate = new Immediate(this.device); } get defaultDrawLayer() { return this.layers.getLayerById(LAYERID_IMMEDIATE); } set ambientBakeNumSamples(value) { this._ambientBakeNumSamples = math.clamp(Math.floor(value), 1, 255); } get ambientBakeNumSamples() { return this._ambientBakeNumSamples; } set ambientBakeSpherePart(value) { this._ambientBakeSpherePart = math.clamp(value, 1e-3, 1); } get ambientBakeSpherePart() { return this._ambientBakeSpherePart; } set clusteredLightingEnabled(value) { if (this.device.isWebGPU && !value) { return; } if (!this._clusteredLightingEnabled && value) { console.error("Turning on disabled clustered lighting is not currently supported"); return; } this._clusteredLightingEnabled = value; } get clusteredLightingEnabled() { return this._clusteredLightingEnabled; } set envAtlas(value) { if (value !== this._envAtlas) { this._envAtlas = value; if (value) { value.addressU = ADDRESS_CLAMP_TO_EDGE; value.addressV = ADDRESS_CLAMP_TO_EDGE; value.minFilter = FILTER_LINEAR; value.magFilter = FILTER_LINEAR; value.mipmaps = false; } this._prefilteredCubemaps = []; if (this._internalEnvAtlas) { this._internalEnvAtlas.destroy(); this._internalEnvAtlas = null; } this._resetSkyMesh(); } } get envAtlas() { return this._envAtlas; } set layers(layers) { const prev = this._layers; this._layers = layers; this.fire("set:layers", prev, layers); } get layers() { return this._layers; } get sky() { return this._sky; } get lighting() { return this._lightingParams; } get gsplat() { return this._gsplatParams; } get fog() { return this._fogParams; } set lightmapFilterRange(value) { this._lightmapFilterRange = Math.max(value, 1e-3); } get lightmapFilterRange() { return this._lightmapFilterRange; } set lightmapFilterSmoothness(value) { this._lightmapFilterSmoothness = Math.max(value, 1e-3); } get lightmapFilterSmoothness() { return this._lightmapFilterSmoothness; } set prefilteredCubemaps(value) { value = value || []; const cubemaps = this._prefilteredCubemaps; const changed = cubemaps.length !== value.length || cubemaps.some((c, i) => c !== value[i]); if (changed) { const complete = value.length === 6 && value.every((c) => !!c); if (complete) { this._internalEnvAtlas = EnvLighting.generatePrefilteredAtlas(value, { target: this._internalEnvAtlas }); this._envAtlas = this._internalEnvAtlas; } else { if (this._internalEnvAtlas) { this._internalEnvAtlas.destroy(); this._internalEnvAtlas = null; } this._envAtlas = null; } this._prefilteredCubemaps = value.slice(); this._resetSkyMesh(); } } get prefilteredCubemaps() { return this._prefilteredCubemaps; } set skybox(value) { if (value !== this._skyboxCubeMap) { this._skyboxCubeMap = value; this._resetSkyMesh(); } } get skybox() { return this._skyboxCubeMap; } set skyboxIntensity(value) { if (value !== this._skyboxIntensity) { this._skyboxIntensity = value; this._resetSkyMesh(); } } get skyboxIntensity() { return this._skyboxIntensity; } set skyboxLuminance(value) { if (value !== this._skyboxLuminance) { this._skyboxLuminance = value; this._resetSkyMesh(); } } get skyboxLuminance() { return this._skyboxLuminance; } set skyboxMip(value) { if (value !== this._skyboxMip) { this._skyboxMip = value; this._resetSkyMesh(); } } get skyboxMip() { return this._skyboxMip; } set skyboxHighlightMultiplier(value) { if (value !== this._skyboxHighlightMultiplier) { this._skyboxHighlightMultiplier = value; this._resetSkyMesh(); } } get skyboxHighlightMultiplier() { return this._skyboxHighlightMultiplier; } set skyboxRotation(value) { if (!this._skyboxRotation.equals(value)) { const isIdentity = value.equals(Quat.IDENTITY); this._skyboxRotation.copy(value); if (isIdentity) { this._skyboxRotationMat3.setIdentity(); } else { this._skyboxRotationMat4.setTRS(Vec3.ZERO, value, Vec3.ONE); this._skyboxRotationMat3.invertMat4(this._skyboxRotationMat4); } if (!this._skyboxRotationShaderInclude && !isIdentity) { this._skyboxRotationShaderInclude = true; this._resetSkyMesh(); } } } get skyboxRotation() { return this._skyboxRotation; } destroy() { this._sky.destroy(); this.root = null; this.off(); } drawLine(start, end, color = Color.WHITE, depthTest = true, layer = this.defaultDrawLayer) { const batch = this.immediate.getBatch(layer, depthTest); batch.addLines([start, end], [color, color]); } drawLines(positions, colors, depthTest = true, layer = this.defaultDrawLayer) { const batch = this.immediate.getBatch(layer, depthTest); batch.addLines(positions, colors); } drawLineArrays(positions, colors, depthTest = true, layer = this.defaultDrawLayer) { const batch = this.immediate.getBatch(layer, depthTest); batch.addLinesArrays(positions, colors); } applySettings(settings) { const physics = settings.physics; const render = settings.render; this._gravity.set(physics.gravity[0], physics.gravity[1], physics.gravity[2]); this.ambientLight.set(render.global_ambient[0], render.global_ambient[1], render.global_ambient[2]); this.ambientLuminance = render.ambientLuminance; this.fog.type = render.fog; this.fog.color.set(render.fog_color[0], render.fog_color[1], render.fog_color[2]); this.fog.start = render.fog_start; this.fog.end = render.fog_end; this.fog.density = render.fog_density; this.lightmapSizeMultiplier = render.lightmapSizeMultiplier; this.lightmapMaxResolution = render.lightmapMaxResolution; this.lightmapMode = render.lightmapMode; this.exposure = render.exposure; this._skyboxIntensity = render.skyboxIntensity ?? 1; this._skyboxLuminance = render.skyboxLuminance ?? 2e4; this._skyboxMip = render.skyboxMip ?? 0; if (render.skyboxRotation) { this.skyboxRotation = new Quat().setFromEulerAngles(render.skyboxRotation[0], render.skyboxRotation[1], render.skyboxRotation[2]); } this.sky.applySettings(render); this.clusteredLightingEnabled = render.clusteredLightingEnabled ?? false; this.lighting.applySettings(render); [ "lightmapFilterEnabled", "lightmapFilterRange", "lightmapFilterSmoothness", "ambientBake", "ambientBakeNumSamples", "ambientBakeSpherePart", "ambientBakeOcclusionBrightness", "ambientBakeOcclusionContrast" ].forEach((setting) => { if (render.hasOwnProperty(setting)) { this[setting] = render[setting]; } }); this._resetSkyMesh(); } // get the actual texture to use for skybox rendering _getSkyboxTex() { const cubemaps = this._prefilteredCubemaps; if (this._skyboxMip) { const skyboxMapping = [ 0, 1, /* 2 */ 3, 4, 5, 6 ]; return cubemaps[skyboxMapping[this._skyboxMip]] || this._envAtlas || cubemaps[0] || this._skyboxCubeMap; } return this._skyboxCubeMap || cubemaps[0] || this._envAtlas; } _updateSkyMesh() { if (!this.sky.skyMesh) { this.sky.updateSkyMesh(); } this.sky.update(); } _resetSkyMesh() { this.sky.resetSkyMesh(); this.updateShaders = true; } setSkybox(cubemaps) { if (!cubemaps) { this.skybox = null; this.envAtlas = null; } else { this.skybox = cubemaps[0] || null; if (cubemaps[1] && !cubemaps[1].cubemap) { this.envAtlas = cubemaps[1]; } else { this.prefilteredCubemaps = cubemaps.slice(1); } } } get lightmapPixelFormat() { return this.lightmapHDR && this.device.getRenderableHdrFormat() || PIXELFORMAT_RGBA8; } } export { Scene };