UNPKG

playcanvas

Version:

PlayCanvas WebGL game engine

355 lines (352 loc) 11.2 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 { ADDRESS_CLAMP_TO_EDGE, FILTER_LINEAR, PIXELFORMAT_RGBA8 } 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{ this.EVENT_SETLAYERS = 'set:layers'; } static{ this.EVENT_SETSKYBOX = 'set:skybox'; } static{ this.EVENT_PRERENDER = 'prerender'; } static{ this.EVENT_POSTRENDER = 'postrender'; } static{ this.EVENT_PRERENDER_LAYER = 'prerender:layer'; } static{ this.EVENT_POSTRENDER_LAYER = 'postrender:layer'; } static{ this.EVENT_PRECULL = 'precull'; } static{ this.EVENT_POSTCULL = 'postcull'; } constructor(graphicsDevice){ super(), this.ambientBake = false, this.ambientBakeOcclusionBrightness = 0, this.ambientBakeOcclusionContrast = 0, this.ambientLight = new Color(0, 0, 0), this.ambientLuminance = 0, this.exposure = 1, this.lightmapSizeMultiplier = 1, this.lightmapMaxResolution = 2048, this.lightmapMode = BAKE_COLORDIR, this.lightmapFilterEnabled = false, this.lightmapHDR = false, this.root = null, this.physicalUnits = false, this._envAtlas = null, this._skyboxCubeMap = null, this._fogParams = new FogParams(), this.forcePassThroughSpecular = false; 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._gsplatParams = new GSplatParams(); this._sky = new Sky(this); this._stats = { meshInstances: 0, lights: 0, dynamicLights: 0, bakedLights: 0, updateShadersTime: 0 }; 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, 0.001, 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, 0.001); } get lightmapFilterRange() { return this._lightmapFilterRange; } set lightmapFilterSmoothness(value) { this._lightmapFilterSmoothness = Math.max(value, 0.001); } 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._resetSkyMesh(); 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 ?? 20000; 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(); } _getSkyboxTex() { const cubemaps = this._prefilteredCubemaps; if (this._skyboxMip) { const skyboxMapping = [ 0, 1, 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 };