Enhance foliage rendering with view-based updates and quality settings support
This commit is contained in:
@@ -1,29 +1,39 @@
|
|||||||
import {
|
import {
|
||||||
|
Camera,
|
||||||
Color,
|
Color,
|
||||||
|
Frustum,
|
||||||
Group,
|
Group,
|
||||||
InstancedMesh,
|
InstancedMesh,
|
||||||
Matrix4,
|
Matrix4,
|
||||||
Mesh,
|
Mesh,
|
||||||
|
Vector3,
|
||||||
type BufferGeometry,
|
type BufferGeometry,
|
||||||
type Material
|
type Material
|
||||||
} from "three";
|
} from "three";
|
||||||
|
|
||||||
import type { Terrain } from "../document/terrains";
|
import type { Terrain } from "../document/terrains";
|
||||||
|
import {
|
||||||
|
resolveFoliageQualitySettings,
|
||||||
|
type FoliageQualitySettings
|
||||||
|
} from "../document/world-settings";
|
||||||
import { applyRendererRenderCategoryFromMaterial } from "../rendering/render-layers";
|
import { applyRendererRenderCategoryFromMaterial } from "../rendering/render-layers";
|
||||||
import { loadBundledFoliageModelTemplate } from "./bundled-foliage-model-loader";
|
import { loadBundledFoliageModelTemplate } from "./bundled-foliage-model-loader";
|
||||||
import {
|
import {
|
||||||
createFoliageInstanceMatrix,
|
createFoliageInstanceMatrix,
|
||||||
createFoliageRenderBatches,
|
createFoliageRenderBatches,
|
||||||
type FoliageRenderBatch
|
type FoliageRenderBatch,
|
||||||
|
type FoliageRenderView
|
||||||
} from "./foliage-render-batches";
|
} from "./foliage-render-batches";
|
||||||
import type {
|
import type {
|
||||||
|
FoliageLayer,
|
||||||
FoliageLayerRegistry,
|
FoliageLayerRegistry,
|
||||||
FoliagePrototypeRegistry
|
FoliagePrototypeRegistry
|
||||||
} from "./foliage";
|
} from "./foliage";
|
||||||
import {
|
import {
|
||||||
createFoliageScatterPrototypeRegistry,
|
createFoliageScatterPrototypeRegistry,
|
||||||
generateFoliageScatterForScene,
|
generateFoliageScatterForScene,
|
||||||
type FoliageScatterPrototypeSource
|
type FoliageScatterPrototypeSource,
|
||||||
|
type FoliageScatterResult
|
||||||
} from "./foliage-scatter";
|
} from "./foliage-scatter";
|
||||||
|
|
||||||
export interface FoliageInstancedRendererSyncInput {
|
export interface FoliageInstancedRendererSyncInput {
|
||||||
@@ -31,6 +41,7 @@ export interface FoliageInstancedRendererSyncInput {
|
|||||||
foliageLayers: FoliageLayerRegistry;
|
foliageLayers: FoliageLayerRegistry;
|
||||||
foliagePrototypes?: FoliagePrototypeRegistry;
|
foliagePrototypes?: FoliagePrototypeRegistry;
|
||||||
bundledFoliagePrototypes?: FoliageScatterPrototypeSource;
|
bundledFoliagePrototypes?: FoliageScatterPrototypeSource;
|
||||||
|
quality?: FoliageQualitySettings | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface FoliageInstancedRendererOptions {
|
export interface FoliageInstancedRendererOptions {
|
||||||
@@ -44,6 +55,8 @@ interface FoliageTemplateSourceMesh {
|
|||||||
localMatrix: Matrix4;
|
localMatrix: Matrix4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const VIEW_SIGNATURE_PRECISION = 100;
|
||||||
|
|
||||||
function cloneMaterial(material: Material): Material {
|
function cloneMaterial(material: Material): Material {
|
||||||
return material.clone();
|
return material.clone();
|
||||||
}
|
}
|
||||||
@@ -101,6 +114,62 @@ function normalizeTerrainRegistry(
|
|||||||
return terrains as Record<string, Terrain>;
|
return terrains as Record<string, Terrain>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function scaleFoliageLayerDensity(
|
||||||
|
layer: FoliageLayer,
|
||||||
|
densityMultiplier: number
|
||||||
|
): FoliageLayer {
|
||||||
|
return {
|
||||||
|
...layer,
|
||||||
|
density: layer.density * densityMultiplier
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function scaleFoliageLayerRegistryDensities(
|
||||||
|
layers: FoliageLayerRegistry,
|
||||||
|
densityMultiplier: number
|
||||||
|
): FoliageLayerRegistry {
|
||||||
|
if (densityMultiplier === 1) {
|
||||||
|
return layers;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(layers).map(([layerId, layer]) => [
|
||||||
|
layerId,
|
||||||
|
scaleFoliageLayerDensity(layer, densityMultiplier)
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRenderViewFromCamera(camera: Camera): FoliageRenderView {
|
||||||
|
camera.updateMatrixWorld();
|
||||||
|
const cameraPosition = new Vector3();
|
||||||
|
camera.getWorldPosition(cameraPosition);
|
||||||
|
const projectionViewMatrix = new Matrix4().multiplyMatrices(
|
||||||
|
camera.projectionMatrix,
|
||||||
|
camera.matrixWorldInverse
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
cameraPosition: {
|
||||||
|
x: cameraPosition.x,
|
||||||
|
y: cameraPosition.y,
|
||||||
|
z: cameraPosition.z
|
||||||
|
},
|
||||||
|
frustum: new Frustum().setFromProjectionMatrix(projectionViewMatrix)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCameraViewSignature(camera: Camera): string {
|
||||||
|
const values = [
|
||||||
|
...camera.matrixWorld.elements,
|
||||||
|
...camera.projectionMatrix.elements
|
||||||
|
];
|
||||||
|
|
||||||
|
return values
|
||||||
|
.map((value) => Math.round(value * VIEW_SIGNATURE_PRECISION))
|
||||||
|
.join("|");
|
||||||
|
}
|
||||||
|
|
||||||
function collectTemplateSourceMeshes(template: Group): FoliageTemplateSourceMesh[] {
|
function collectTemplateSourceMeshes(template: Group): FoliageTemplateSourceMesh[] {
|
||||||
const sourceMeshes: FoliageTemplateSourceMesh[] = [];
|
const sourceMeshes: FoliageTemplateSourceMesh[] = [];
|
||||||
|
|
||||||
@@ -135,9 +204,11 @@ function createInstancedMeshForSource(
|
|||||||
);
|
);
|
||||||
const color = new Color();
|
const color = new Color();
|
||||||
|
|
||||||
mesh.name = `Foliage:${batch.prototypeId}:${batch.lodLevel}`;
|
mesh.name = `Foliage:${batch.prototypeId}:${batch.chunkId}:${batch.lodLevel}`;
|
||||||
mesh.userData.nonPickable = true;
|
mesh.userData.nonPickable = true;
|
||||||
|
mesh.userData.shadowIgnored = !batch.castShadow;
|
||||||
mesh.userData.foliageBatchKey = batch.key;
|
mesh.userData.foliageBatchKey = batch.key;
|
||||||
|
mesh.userData.foliageChunkId = batch.chunkId;
|
||||||
mesh.userData.foliagePrototypeId = batch.prototypeId;
|
mesh.userData.foliagePrototypeId = batch.prototypeId;
|
||||||
mesh.userData.foliageLayerId = batch.layerId;
|
mesh.userData.foliageLayerId = batch.layerId;
|
||||||
mesh.userData.foliageTerrainId = batch.terrainId;
|
mesh.userData.foliageTerrainId = batch.terrainId;
|
||||||
@@ -174,6 +245,11 @@ export class FoliageInstancedRenderer {
|
|||||||
|
|
||||||
private requestId = 0;
|
private requestId = 0;
|
||||||
private activeBatchGroup: Group | null = null;
|
private activeBatchGroup: Group | null = null;
|
||||||
|
private scatter: FoliageScatterResult | null = null;
|
||||||
|
private prototypeRegistry: FoliagePrototypeRegistry = {};
|
||||||
|
private quality: FoliageQualitySettings = resolveFoliageQualitySettings(null);
|
||||||
|
private currentView: FoliageRenderView | null = null;
|
||||||
|
private viewSignature: string | null = null;
|
||||||
private readonly onRebuilt?: () => void;
|
private readonly onRebuilt?: () => void;
|
||||||
private readonly onDiagnostic?: (message: string) => void;
|
private readonly onDiagnostic?: (message: string) => void;
|
||||||
|
|
||||||
@@ -185,19 +261,67 @@ export class FoliageInstancedRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sync(input: FoliageInstancedRendererSyncInput) {
|
sync(input: FoliageInstancedRendererSyncInput) {
|
||||||
const requestId = ++this.requestId;
|
|
||||||
const terrains = normalizeTerrainRegistry(input.terrains);
|
const terrains = normalizeTerrainRegistry(input.terrains);
|
||||||
|
const quality = resolveFoliageQualitySettings(input.quality);
|
||||||
const prototypeRegistry = createFoliageScatterPrototypeRegistry({
|
const prototypeRegistry = createFoliageScatterPrototypeRegistry({
|
||||||
foliagePrototypes: input.foliagePrototypes,
|
foliagePrototypes: input.foliagePrototypes,
|
||||||
bundledFoliagePrototypes: input.bundledFoliagePrototypes
|
bundledFoliagePrototypes: input.bundledFoliagePrototypes
|
||||||
});
|
});
|
||||||
const scatter = generateFoliageScatterForScene({
|
const foliageLayers = scaleFoliageLayerRegistryDensities(
|
||||||
|
input.foliageLayers,
|
||||||
|
quality.densityMultiplier
|
||||||
|
);
|
||||||
|
|
||||||
|
this.quality = quality;
|
||||||
|
this.prototypeRegistry = prototypeRegistry;
|
||||||
|
|
||||||
|
if (!quality.enabled || quality.densityMultiplier <= 0) {
|
||||||
|
this.scatter = null;
|
||||||
|
this.clearActiveBatches();
|
||||||
|
this.onRebuilt?.();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scatter = generateFoliageScatterForScene({
|
||||||
terrains,
|
terrains,
|
||||||
foliageLayers: input.foliageLayers,
|
foliageLayers,
|
||||||
foliagePrototypes: input.foliagePrototypes,
|
foliagePrototypes: input.foliagePrototypes,
|
||||||
bundledFoliagePrototypes: input.bundledFoliagePrototypes
|
bundledFoliagePrototypes: input.bundledFoliagePrototypes
|
||||||
});
|
});
|
||||||
const batches = createFoliageRenderBatches(scatter, prototypeRegistry);
|
this.rebuildCurrentBatches();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateView(camera: Camera) {
|
||||||
|
this.currentView = createRenderViewFromCamera(camera);
|
||||||
|
|
||||||
|
if (this.scatter === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextViewSignature = createCameraViewSignature(camera);
|
||||||
|
|
||||||
|
if (nextViewSignature === this.viewSignature) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.viewSignature = nextViewSignature;
|
||||||
|
this.rebuildCurrentBatches();
|
||||||
|
}
|
||||||
|
|
||||||
|
private rebuildCurrentBatches() {
|
||||||
|
const requestId = ++this.requestId;
|
||||||
|
const scatter = this.scatter;
|
||||||
|
|
||||||
|
if (scatter === null) {
|
||||||
|
this.clearActiveBatches();
|
||||||
|
this.onRebuilt?.();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const batches = createFoliageRenderBatches(scatter, this.prototypeRegistry, {
|
||||||
|
view: this.currentView,
|
||||||
|
quality: this.quality
|
||||||
|
});
|
||||||
|
|
||||||
this.clearActiveBatches();
|
this.clearActiveBatches();
|
||||||
|
|
||||||
@@ -211,6 +335,9 @@ export class FoliageInstancedRenderer {
|
|||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
this.requestId += 1;
|
this.requestId += 1;
|
||||||
|
this.scatter = null;
|
||||||
|
this.prototypeRegistry = {};
|
||||||
|
this.viewSignature = null;
|
||||||
this.clearActiveBatches();
|
this.clearActiveBatches();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user