Feature: Implement wind simulation for foliage rendering across renderer, runtime, and viewport hosts

This commit is contained in:
2026-05-21 06:08:12 +02:00
parent d0a33aa97d
commit fe1bb3b9ce
4 changed files with 69 additions and 3 deletions

View File

@@ -473,6 +473,8 @@ export class FoliageInstancedRenderer {
private scatter: FoliageScatterResult | null = null;
private prototypeRegistry: FoliagePrototypeRegistry = {};
private quality: FoliageQualitySettings = resolveFoliageQualitySettings(null);
private windTime = 0;
private windUniforms: FoliageWindUniformSet[] = [];
private currentView: FoliageRenderView | null = null;
private viewSignature: string | null = null;
private renderResourceSignature: string | null = null;
@@ -498,6 +500,18 @@ export class FoliageInstancedRenderer {
this.group.userData.nonPickable = true;
}
updateWind(deltaSeconds: number) {
if (!this.shouldApplyWindShader()) {
return;
}
if (Number.isFinite(deltaSeconds) && deltaSeconds > 0) {
this.windTime += deltaSeconds;
}
this.writeWindUniforms();
}
sync(input: FoliageInstancedRendererSyncInput) {
const terrains = normalizeTerrainRegistry(input.terrains);
const quality = resolveFoliageQualitySettings(input.quality);
@@ -536,6 +550,7 @@ export class FoliageInstancedRenderer {
renderResourceSignature === this.renderResourceSignature &&
this.scatter !== null
) {
this.writeWindUniforms();
this.applyCurrentViewToRenderResources();
return;
}
@@ -668,6 +683,7 @@ export class FoliageInstancedRenderer {
this.currentView = null;
this.viewSignature = null;
this.renderResourceSignature = null;
this.windUniforms = [];
this.sourceMeshPromisesByBundledPath.clear();
this.clearActiveBatches();
}
@@ -677,6 +693,7 @@ export class FoliageInstancedRenderer {
this.activeBatchKeyByChunkKey.clear();
this.activeLodLevelByChunkKey.clear();
this.renderChunks = [];
this.windUniforms = [];
if (this.activeBatchGroup === null) {
return;
@@ -696,6 +713,41 @@ export class FoliageInstancedRenderer {
console.warn(message);
}
private shouldApplyWindShader(): boolean {
return (
this.quality.enabled &&
this.quality.densityMultiplier > 0 &&
this.quality.windEnabled
);
}
private registerWindUniforms = (uniforms: FoliageWindUniformSet) => {
writeFoliageWindUniformValues(uniforms, this.quality, this.windTime);
this.windUniforms.push(uniforms);
};
private writeWindUniforms() {
if (!this.shouldApplyWindShader()) {
return;
}
for (const uniforms of this.windUniforms) {
writeFoliageWindUniformValues(uniforms, this.quality, this.windTime);
}
}
private getWindMaterialOptions(): FoliageWindMaterialOptions | undefined {
if (!this.shouldApplyWindShader()) {
return undefined;
}
return {
getSettings: () => this.quality,
getTime: () => this.windTime,
registerUniforms: this.registerWindUniforms
};
}
private loadTemplateSourceMeshes(
bundledPath: string
): Promise<FoliageTemplateSourceMesh[]> {
@@ -761,8 +813,12 @@ export class FoliageInstancedRenderer {
batchGroup.userData.foliageLayerId = batch.layerId;
batchGroup.userData.foliageTerrainId = batch.terrainId;
const windOptions = this.getWindMaterialOptions();
for (const sourceMesh of sourceMeshes) {
batchGroup.add(createInstancedMeshForSource(batch, sourceMesh));
batchGroup.add(
createInstancedMeshForSource(batch, sourceMesh, windOptions)
);
}
applyRendererRenderCategoryFromMaterial(batchGroup);

View File

@@ -5704,6 +5704,7 @@ export class RuntimeHost {
this.updateTerrainLodVisibility();
this.foliageRenderer.updateView(this.camera);
this.foliageRenderer.updateWind(simulationDt);
this.updateUnderwaterSceneFog();
this.syncCelestialShadowState();

View File

@@ -14188,6 +14188,7 @@ export class ViewportHost {
this.updateTransformGizmoPose();
this.updateTerrainLodVisibility();
this.foliageRenderer.updateView(this.getActiveCamera());
this.foliageRenderer.updateWind(dt);
this.volumeTime += dt;
for (const uniform of this.volumeAnimatedUniforms) {