playcanvas
Version:
PlayCanvas WebGL game engine
355 lines (352 loc) • 11.2 kB
JavaScript
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 };