From b00880c21feeb6c2954017f6f8de4986b870b32c Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Fri, 24 Apr 2026 15:42:58 +0900 Subject: [PATCH] wip --- packages/frontend/src/pages/room.vue | 19 +++--- .../frontend/src/world/room/controller.ts | 19 +++--- packages/frontend/src/world/room/engine.ts | 65 ++++++++++--------- 3 files changed, 56 insertions(+), 47 deletions(-) diff --git a/packages/frontend/src/pages/room.vue b/packages/frontend/src/pages/room.vue index f8fea24b6f..e2bbaa8d95 100644 --- a/packages/frontend/src/pages/room.vue +++ b/packages/frontend/src/pages/room.vue @@ -96,9 +96,9 @@ SPDX-License-Identifier: AGPL-3.0-only @@ -123,6 +123,7 @@ import MkRange from '@/components/MkRange.vue'; import { RoomController } from '@/world/room/controller.js'; import { cm, getHex, getRgb } from '@/world/utility.js'; import { deepClone } from '@/utility/clone.js'; +import { GRAPHICS_QUALITY_HIGH, GRAPHICS_QUALITY_LOW, GRAPHICS_QUALITY_MEDIUM } from '@/world/room/engine.js'; const canvas = useTemplateRef('canvas'); @@ -140,7 +141,7 @@ function resize() { const isZenMode = ref(false); const isRoomSettingsOpen = ref(false); const isChanged = ref(false); -const graphicsQuality = ref<'low' | 'medium' | 'high'>('medium'); +const graphicsQuality = ref(0); const data = localStorage.getItem('roomData') != null ? JSON.parse(localStorage.getItem('roomData')!) : { heya: { @@ -179,12 +180,12 @@ const data = localStorage.getItem('roomData') != null ? JSON.parse(localStorage. let latestData = deepClone(data); -const controller = new RoomController(data); +const controller = new RoomController(data, { + graphicsQuality: graphicsQuality.value, +}); onMounted(async () => { - controller.init(canvas.value!, { - graphicsQuality: graphicsQuality.value, - }); + controller.init(canvas.value!); canvas.value!.focus(); @@ -311,7 +312,7 @@ async function revert() { } async function reload() { - await controller.reset(null, null, { + await controller.reset(null, { graphicsQuality: graphicsQuality.value, }); } diff --git a/packages/frontend/src/world/room/controller.ts b/packages/frontend/src/world/room/controller.ts index 7d5cc194a5..18b3bbad59 100644 --- a/packages/frontend/src/world/room/controller.ts +++ b/packages/frontend/src/world/room/controller.ts @@ -15,7 +15,7 @@ import * as sound from '@/utility/sound.js'; type Options = { workerMode?: boolean; - graphicsQuality?: 'low' | 'medium' | 'high'; + graphicsQuality: number; }; // 抽象化レイヤー @@ -38,8 +38,9 @@ export class RoomController { public roomState: ShallowRef; public initializeProgress = ref(0); - constructor(roomState: RoomState) { + constructor(roomState: RoomState, options: Options) { this.roomState = shallowRef(roomState); + this.options = options; this.onCanvasKeydown = this.onCanvasKeydown.bind(this); this.onCanvasKeyup = this.onCanvasKeyup.bind(this); @@ -50,23 +51,22 @@ export class RoomController { this.onCanvasClick = this.onCanvasClick.bind(this); } - public async init(canvas: HTMLCanvasElement, options: Options = {}) { + public async init(canvas: HTMLCanvasElement) { this.canvas = canvas; this.canvas.width = canvas.clientWidth; this.canvas.height = canvas.clientHeight; - this.options = options; - if (options.workerMode) { + if (this.options.workerMode) { const offscreen = canvas.transferControlToOffscreen(); this.worker = new RoomWorker(); - this.worker.postMessage({ type: 'init', canvas: offscreen, roomState: this.roomState.value, graphicsQuality: options.graphicsQuality }, [offscreen]); + this.worker.postMessage({ type: 'init', canvas: offscreen, roomState: this.roomState.value, graphicsQuality: this.options.graphicsQuality }, [offscreen]); this.isReady.value = true; } else { const babylonEngine = new BABYLON.WebGPUEngine(canvas, { doNotHandleContextLost: true }); babylonEngine.compatibilityMode = false; await babylonEngine.initAsync(); - this.engine = new RoomEngine(this.roomState.value, { canvas, engine: babylonEngine, graphicsQuality: options.graphicsQuality }); + this.engine = new RoomEngine(this.roomState.value, { canvas, engine: babylonEngine, graphicsQuality: this.options.graphicsQuality }); this.engine.on('loadingProgress', ({ progress }) => { this.initializeProgress.value = progress; }); @@ -171,16 +171,17 @@ export class RoomController { return false; } - public async reset(roomState?: RoomState, canvas?: HTMLCanvasElement, options: Options = {}) { + public async reset(roomState?: RoomState | null, options?: Options | null, canvas?: HTMLCanvasElement | null) { this.destroy(); if (roomState != null) this.roomState.value = roomState; + if (options != null) this.options = options; this.isReady.value = false; this.isSitting.value = false; this.isEditMode.value = false; this.grabbing.value = null; this.selected.value = null; this.initializeProgress.value = 0; - await this.init(canvas ?? this.canvas!, options ?? this.options); + await this.init(canvas ?? this.canvas!); } public pauseRender() { diff --git a/packages/frontend/src/world/room/engine.ts b/packages/frontend/src/world/room/engine.ts index 1a626b7e33..31c13cdd93 100644 --- a/packages/frontend/src/world/room/engine.ts +++ b/packages/frontend/src/world/room/engine.ts @@ -93,6 +93,10 @@ function enableObjectCollision(meshes: BABYLON.Mesh[]) { } } +export const GRAPHICS_QUALITY_HIGH = 1; +export const GRAPHICS_QUALITY_MEDIUM = 0; +export const GRAPHICS_QUALITY_LOW = -1; + export type RoomEngineEvents = { 'changeSelectedState': (ctx: { selected: { @@ -121,7 +125,7 @@ export class RoomEngine extends EventEmitter { private engine: BABYLON.WebGPUEngine; public scene: BABYLON.Scene; private shadowGeneratorForRoomLight: BABYLON.ShadowGenerator; - private shadowGeneratorForSunLight: BABYLON.ShadowGenerator; + private shadowGeneratorForSunLight: BABYLON.ShadowGenerator | null = null; public camera: BABYLON.UniversalCamera; public objectEntities: Map { constructor(roomState: RoomState, options: { canvas: HTMLCanvasElement; engine: BABYLON.WebGPUEngine; - graphicsQuality?: 'low' | 'medium' | 'high'; + graphicsQuality: number; }) { super(); @@ -231,13 +235,13 @@ export class RoomEngine extends EventEmitter { }; this.canvas = options.canvas; - this.fps = options.graphicsQuality === 'low' || options.graphicsQuality === 'medium' ? 30 : null; - this.useGlow = options.graphicsQuality !== 'low'; + this.fps = options.graphicsQuality >= GRAPHICS_QUALITY_HIGH ? null : 30; + this.useGlow = options.graphicsQuality >= GRAPHICS_QUALITY_MEDIUM; registerBuiltInLoaders(); this.engine = options.engine; - if (options.graphicsQuality === 'low') this.engine.setHardwareScalingLevel(2); + if (options.graphicsQuality <= GRAPHICS_QUALITY_LOW) this.engine.setHardwareScalingLevel(2); this.scene = new BABYLON.Scene(this.engine); // なんかレンダリングがおかしくなるときがあるのでコメントアウト // オブジェクトを選択し、後ろを向いて別のオブジェクトを選択した後、最初のオブジェクトに振り返ると消えているなど @@ -314,11 +318,6 @@ export class RoomEngine extends EventEmitter { //this.scene.activeCamera = this.camera; - const ambientLight = new BABYLON.HemisphericLight('ambientLight', new BABYLON.Vector3(0, 1, -0.5), this.scene); - ambientLight.diffuse = new BABYLON.Color3(1.0, 1.0, 1.0); - ambientLight.intensity = 0.3; - //ambientLight.intensity = 0; - this.roomLight = new BABYLON.SpotLight('roomLight', new BABYLON.Vector3(0, cm(249), 0), new BABYLON.Vector3(0, -1, 0), 16, 8, this.scene); this.roomLight.diffuse = new BABYLON.Color3(1.0, 0.9, 0.8); this.roomLight.shadowMinZ = cm(10); @@ -327,28 +326,36 @@ export class RoomEngine extends EventEmitter { this.shadowGeneratorForRoomLight = new BABYLON.ShadowGenerator(2048, this.roomLight); this.shadowGeneratorForRoomLight.forceBackFacesOnly = true; - this.shadowGeneratorForRoomLight.bias = 0.0001; + this.shadowGeneratorForRoomLight.bias = 0.00001; this.shadowGeneratorForRoomLight.usePercentageCloserFiltering = true; this.shadowGeneratorForRoomLight.filteringQuality = BABYLON.ShadowGenerator.QUALITY_HIGH; - this.shadowGeneratorForRoomLight.getShadowMap().refreshRate = 60; + if (options.graphicsQuality <= GRAPHICS_QUALITY_LOW) { + this.shadowGeneratorForRoomLight.getShadowMap().refreshRate = 120; + } else if (options.graphicsQuality <= GRAPHICS_QUALITY_MEDIUM) { + this.shadowGeneratorForRoomLight.getShadowMap().refreshRate = 60; + } //this.shadowGenerator1.useContactHardeningShadow = true; - const sunLight = new BABYLON.DirectionalLight('sunLight', new BABYLON.Vector3(0.2, -1, -1), this.scene); - sunLight.position = new BABYLON.Vector3(cm(-20), cm(1000), cm(1000)); - sunLight.diffuse = this.time === 0 ? new BABYLON.Color3(1.0, 0.9, 0.8) : this.time === 1 ? new BABYLON.Color3(1.0, 0.8, 0.6) : new BABYLON.Color3(0.6, 0.8, 1.0); - sunLight.intensity = this.time === 0 ? 3 : this.time === 1 ? 1 : 0.25; - sunLight.shadowMinZ = cm(1000); - sunLight.shadowMaxZ = cm(2000); + if (options.graphicsQuality >= GRAPHICS_QUALITY_MEDIUM) { + const sunLight = new BABYLON.DirectionalLight('sunLight', new BABYLON.Vector3(0.2, -1, -1), this.scene); + sunLight.position = new BABYLON.Vector3(cm(-20), cm(1000), cm(1000)); + sunLight.diffuse = this.time === 0 ? new BABYLON.Color3(1.0, 0.9, 0.8) : this.time === 1 ? new BABYLON.Color3(1.0, 0.8, 0.6) : new BABYLON.Color3(0.6, 0.8, 1.0); + sunLight.intensity = this.time === 0 ? 3 : this.time === 1 ? 1 : 0.25; + sunLight.shadowMinZ = cm(1000); + sunLight.shadowMaxZ = cm(2000); - this.shadowGeneratorForSunLight = new BABYLON.ShadowGenerator(2048, sunLight); - this.shadowGeneratorForSunLight.forceBackFacesOnly = true; - this.shadowGeneratorForSunLight.bias = 0.0001; - this.shadowGeneratorForSunLight.usePercentageCloserFiltering = true; - this.shadowGeneratorForSunLight.usePoissonSampling = true; - this.shadowGeneratorForSunLight.getShadowMap().refreshRate = 60; + this.shadowGeneratorForSunLight = new BABYLON.ShadowGenerator(2048, sunLight); + this.shadowGeneratorForSunLight.forceBackFacesOnly = true; + this.shadowGeneratorForSunLight.bias = 0.00001; + this.shadowGeneratorForSunLight.usePercentageCloserFiltering = true; + this.shadowGeneratorForSunLight.usePoissonSampling = true; + if (options.graphicsQuality <= GRAPHICS_QUALITY_MEDIUM) { + this.shadowGeneratorForSunLight.getShadowMap().refreshRate = 60; + } + } this.lightContainer = new BABYLON.ClusteredLightContainer('clustered', [], this.scene); - this.lightContainer.maxRange = options.graphicsQuality === 'high' ? cm(1000) : options.graphicsQuality === 'medium' ? cm(100) : cm(50); + this.lightContainer.maxRange = options.graphicsQuality >= GRAPHICS_QUALITY_HIGH ? cm(1000) : options.graphicsQuality >= GRAPHICS_QUALITY_MEDIUM ? cm(100) : cm(50); this.lightContainer.verticalTiles = 32; this.lightContainer.horizontalTiles = 32; this.lightContainer.depthSlices = 32; @@ -829,7 +836,7 @@ export class RoomEngine extends EventEmitter { m.checkCollisions = false; m.receiveShadows = true; this.shadowGeneratorForRoomLight.addShadowCaster(m); - this.shadowGeneratorForSunLight.addShadowCaster(m); + this.shadowGeneratorForSunLight?.addShadowCaster(m); //if (m.material) (m.material as BABYLON.PBRMaterial).ambientColor = new BABYLON.Color3(1, 1, 1); if (m.material) { (m.material as BABYLON.PBRMaterial).reflectionTexture = this.envMapIndoor; @@ -1081,7 +1088,7 @@ export class RoomEngine extends EventEmitter { if (def.receiveShadows !== false) mesh.receiveShadows = true; if (def.castShadows !== false) { this.shadowGeneratorForRoomLight.addShadowCaster(mesh); - this.shadowGeneratorForSunLight.addShadowCaster(mesh); + this.shadowGeneratorForSunLight?.addShadowCaster(mesh); } //if (mesh.material) (mesh.material as BABYLON.PBRMaterial).ambientColor = new BABYLON.Color3(0.2, 0.2, 0.2); @@ -1369,7 +1376,7 @@ export class RoomEngine extends EventEmitter { private turnOnRoomLight(forInit = false) { if (!forInit && SNAPSHOT_RENDERING) this.sr.disableSnapshotRendering(); // このメソッドは参照カウント方式な点に留意 - this.roomLight.intensity = 10 * WORLD_SCALE * WORLD_SCALE; + this.roomLight.intensity = 15 * WORLD_SCALE * WORLD_SCALE; this.envMapIndoor.level = 0.6; if (!forInit && SNAPSHOT_RENDERING) { setTimeout(() => { @@ -1381,7 +1388,7 @@ export class RoomEngine extends EventEmitter { private turnOffRoomLight() { if (SNAPSHOT_RENDERING) this.sr.disableSnapshotRendering(); // このメソッドは参照カウント方式な点に留意 this.roomLight.intensity = 0; - this.envMapIndoor.level = 0; + this.envMapIndoor.level = 0.025; setTimeout(() => { if (SNAPSHOT_RENDERING) this.sr.enableSnapshotRendering(); // このメソッドは参照カウント方式な点に留意 }, 10);