Integrate foliage rendering system into RuntimeHost

This commit is contained in:
2026-05-02 04:54:32 +02:00
parent 04c53b1ae6
commit 6e312864ac
3 changed files with 52 additions and 0 deletions

View File

@@ -41,6 +41,7 @@ import {
createModelInstanceRenderGroup, createModelInstanceRenderGroup,
disposeModelInstance disposeModelInstance
} from "../assets/model-instance-rendering"; } from "../assets/model-instance-rendering";
import { FoliageInstancedRenderer } from "../foliage/foliage-instanced-renderer";
import type { LoadedModelAsset } from "../assets/gltf-model-import"; import type { LoadedModelAsset } from "../assets/gltf-model-import";
import type { LoadedImageAsset } from "../assets/image-assets"; import type { LoadedImageAsset } from "../assets/image-assets";
import type { LoadedAudioAsset } from "../assets/audio-assets"; import type { LoadedAudioAsset } from "../assets/audio-assets";
@@ -759,6 +760,11 @@ export class RuntimeHost {
LightVolumeRenderObjects LightVolumeRenderObjects
>(); >();
private readonly modelRenderObjects = new Map<string, Group>(); private readonly modelRenderObjects = new Map<string, Group>();
private readonly foliageRenderer = new FoliageInstancedRenderer({
onRebuilt: () => {
this.applyShadowState();
}
});
private readonly materialTextureCache = new Map< private readonly materialTextureCache = new Map<
string, string,
CachedMaterialTexture CachedMaterialTexture
@@ -882,6 +888,7 @@ export class RuntimeHost {
this.scene.add(this.lightVolumeGroup); this.scene.add(this.lightVolumeGroup);
this.scene.add(this.brushGroup); this.scene.add(this.brushGroup);
this.scene.add(this.terrainGroup); this.scene.add(this.terrainGroup);
this.scene.add(this.foliageRenderer.group);
this.scene.add(this.modelGroup); this.scene.add(this.modelGroup);
this.targetingLuxMesh.renderOrder = 10000; this.targetingLuxMesh.renderOrder = 10000;
this.targetingLuxGlowMesh.renderOrder = 9999; this.targetingLuxGlowMesh.renderOrder = 9999;
@@ -1208,6 +1215,7 @@ export class RuntimeHost {
this.rebuildLightVolumes(runtimeScene.volumes.light); this.rebuildLightVolumes(runtimeScene.volumes.light);
this.rebuildBrushMeshes(runtimeScene.brushes); this.rebuildBrushMeshes(runtimeScene.brushes);
this.rebuildTerrainMeshes(runtimeScene.terrains); this.rebuildTerrainMeshes(runtimeScene.terrains);
this.rebuildFoliage(runtimeScene);
this.rebuildModelRenderObjects( this.rebuildModelRenderObjects(
runtimeScene.modelInstances, runtimeScene.modelInstances,
runtimeScene.npcDefinitions runtimeScene.npcDefinitions
@@ -1404,6 +1412,7 @@ export class RuntimeHost {
this.clearLightVolumes(); this.clearLightVolumes();
this.clearBrushMeshes(); this.clearBrushMeshes();
this.clearTerrainMeshes(); this.clearTerrainMeshes();
this.foliageRenderer.dispose();
this.clearModelRenderObjects(); this.clearModelRenderObjects();
this.collisionWorldRequestId += 1; this.collisionWorldRequestId += 1;
this.clearCollisionWorld(); this.clearCollisionWorld();
@@ -3163,6 +3172,11 @@ export class RuntimeHost {
); );
} }
applyAdvancedRenderingRenderableShadowFlags(
this.foliageRenderer.group,
shadowsEnabled
);
for (const renderGroup of this.modelRenderObjects.values()) { for (const renderGroup of this.modelRenderObjects.values()) {
applyAdvancedRenderingRenderableShadowFlags(renderGroup, shadowsEnabled); applyAdvancedRenderingRenderableShadowFlags(renderGroup, shadowsEnabled);
} }
@@ -5012,6 +5026,14 @@ export class RuntimeHost {
this.terrainMeshes.clear(); this.terrainMeshes.clear();
} }
private rebuildFoliage(runtimeScene: RuntimeSceneDefinition) {
this.foliageRenderer.sync({
terrains: runtimeScene.foliage.terrains,
foliageLayers: runtimeScene.foliage.layers,
foliagePrototypes: runtimeScene.foliage.prototypes
});
}
private disposeUniqueMaterials(materials: Material[]) { private disposeUniqueMaterials(materials: Material[]) {
for (const material of new Set(materials)) { for (const material of new Set(materials)) {
material.dispose(); material.dispose();

View File

@@ -61,12 +61,19 @@ import {
type ScenePathPoint type ScenePathPoint
} from "../document/paths"; } from "../document/paths";
import { import {
cloneTerrain,
getTerrainBounds, getTerrainBounds,
getTerrainFootprintDepth, getTerrainFootprintDepth,
getTerrainFootprintWidth, getTerrainFootprintWidth,
getTerrains, getTerrains,
type Terrain type Terrain
} from "../document/terrains"; } from "../document/terrains";
import {
cloneFoliageLayerRegistry,
cloneFoliagePrototypeRegistry,
type FoliageLayerRegistry,
type FoliagePrototypeRegistry
} from "../foliage/foliage";
import { import {
cloneWorldSettings, cloneWorldSettings,
type WorldSettings type WorldSettings
@@ -496,6 +503,12 @@ export interface RuntimePath {
totalLength: number; totalLength: number;
} }
export interface RuntimeFoliageDefinition {
terrains: Terrain[];
layers: FoliageLayerRegistry;
prototypes: FoliagePrototypeRegistry;
}
export interface RuntimeEntityCollection { export interface RuntimeEntityCollection {
playerStarts: RuntimePlayerStart[]; playerStarts: RuntimePlayerStart[];
sceneEntries: RuntimeSceneEntry[]; sceneEntries: RuntimeSceneEntry[];
@@ -527,6 +540,7 @@ export interface RuntimeSceneDefinition {
staticColliders: RuntimeSceneCollider[]; staticColliders: RuntimeSceneCollider[];
colliders: RuntimeSceneCollider[]; colliders: RuntimeSceneCollider[];
sceneBounds: RuntimeSceneBounds | null; sceneBounds: RuntimeSceneBounds | null;
foliage: RuntimeFoliageDefinition;
modelInstances: RuntimeModelInstance[]; modelInstances: RuntimeModelInstance[];
paths: RuntimePath[]; paths: RuntimePath[];
npcDefinitions: RuntimeNpcDefinition[]; npcDefinitions: RuntimeNpcDefinition[];
@@ -1895,6 +1909,11 @@ export function buildRuntimeSceneFromDocument(
const terrains = enabledTerrains.map((terrain) => const terrains = enabledTerrains.map((terrain) =>
buildRuntimeTerrain(terrain, document) buildRuntimeTerrain(terrain, document)
); );
const foliage: RuntimeFoliageDefinition = {
terrains: enabledTerrains.map((terrain) => cloneTerrain(terrain)),
layers: cloneFoliageLayerRegistry(document.foliageLayers),
prototypes: cloneFoliagePrototypeRegistry(document.foliagePrototypes)
};
const staticColliders: RuntimeSceneCollider[] = []; const staticColliders: RuntimeSceneCollider[] = [];
const volumes: RuntimeBoxVolumeCollection = { const volumes: RuntimeBoxVolumeCollection = {
fog: [], fog: [],
@@ -2069,6 +2088,7 @@ export function buildRuntimeSceneFromDocument(
staticColliders, staticColliders,
colliders, colliders,
sceneBounds: combinedSceneBounds, sceneBounds: combinedSceneBounds,
foliage,
modelInstances, modelInstances,
paths, paths,
npcDefinitions: collections.npcDefinitions, npcDefinitions: collections.npcDefinitions,

View File

@@ -86,6 +86,7 @@ import {
disposeModelInstance, disposeModelInstance,
syncModelInstanceSelectionShell syncModelInstanceSelectionShell
} from "../assets/model-instance-rendering"; } from "../assets/model-instance-rendering";
import { FoliageInstancedRenderer } from "../foliage/foliage-instanced-renderer";
import type { LoadedModelAsset } from "../assets/gltf-model-import"; import type { LoadedModelAsset } from "../assets/gltf-model-import";
import type { LoadedImageAsset } from "../assets/image-assets"; import type { LoadedImageAsset } from "../assets/image-assets";
import type { ProjectAssetRecord } from "../assets/project-assets"; import type { ProjectAssetRecord } from "../assets/project-assets";
@@ -840,6 +841,11 @@ export class ViewportHost {
LightVolumeRenderObjects LightVolumeRenderObjects
>(); >();
private readonly modelRenderObjects = new Map<string, Group>(); private readonly modelRenderObjects = new Map<string, Group>();
private readonly foliageRenderer = new FoliageInstancedRenderer({
onRebuilt: () => {
this.applyShadowState();
}
});
private readonly materialTextureCache = new Map< private readonly materialTextureCache = new Map<
string, string,
CachedMaterialTexture CachedMaterialTexture
@@ -1043,6 +1049,8 @@ export class ViewportHost {
this.scene.add(this.lightVolumeGroup); this.scene.add(this.lightVolumeGroup);
this.scene.add(this.brushGroup); this.scene.add(this.brushGroup);
this.scene.add(this.terrainGroup); this.scene.add(this.terrainGroup);
this.scene.add(this.foliageRenderer.group);
this.syncFoliageVisibility();
this.terrainBrushPreviewGroup.visible = false; this.terrainBrushPreviewGroup.visible = false;
this.terrainBrushPreviewLine.frustumCulled = false; this.terrainBrushPreviewLine.frustumCulled = false;
this.terrainBrushPreviewCenter.frustumCulled = false; this.terrainBrushPreviewCenter.frustumCulled = false;
@@ -1358,6 +1366,7 @@ export class ViewportHost {
this.currentActiveSelectionId this.currentActiveSelectionId
); );
} }
this.rebuildFoliage(document);
this.rebuildPaths(document, this.currentSelection); this.rebuildPaths(document, this.currentSelection);
this.rebuildEntityMarkers(document, this.currentSelection); this.rebuildEntityMarkers(document, this.currentSelection);
this.rebuildModelInstances(document, this.currentSelection); this.rebuildModelInstances(document, this.currentSelection);
@@ -1813,6 +1822,7 @@ export class ViewportHost {
this.clearLightVolumes(); this.clearLightVolumes();
this.clearBrushMeshes(); this.clearBrushMeshes();
this.clearTerrains(); this.clearTerrains();
this.foliageRenderer.dispose();
this.clearPaths(); this.clearPaths();
this.clearEntityMarkers(); this.clearEntityMarkers();
this.creationPreviewChangeHandler = null; this.creationPreviewChangeHandler = null;