diff --git a/src/foliage/foliage-instanced-renderer.ts b/src/foliage/foliage-instanced-renderer.ts index 06ccc8c2..456cde75 100644 --- a/src/foliage/foliage-instanced-renderer.ts +++ b/src/foliage/foliage-instanced-renderer.ts @@ -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 { @@ -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); diff --git a/src/runtime-three/runtime-host.ts b/src/runtime-three/runtime-host.ts index a4e78038..d9aa4ecd 100644 --- a/src/runtime-three/runtime-host.ts +++ b/src/runtime-three/runtime-host.ts @@ -5704,6 +5704,7 @@ export class RuntimeHost { this.updateTerrainLodVisibility(); this.foliageRenderer.updateView(this.camera); + this.foliageRenderer.updateWind(simulationDt); this.updateUnderwaterSceneFog(); this.syncCelestialShadowState(); diff --git a/src/viewport-three/viewport-host.ts b/src/viewport-three/viewport-host.ts index 7a654311..0aeb48cd 100644 --- a/src/viewport-three/viewport-host.ts +++ b/src/viewport-three/viewport-host.ts @@ -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) { diff --git a/tests/domain/foliage-render-batches.test.ts b/tests/domain/foliage-render-batches.test.ts index eb7fa078..b6986557 100644 --- a/tests/domain/foliage-render-batches.test.ts +++ b/tests/domain/foliage-render-batches.test.ts @@ -341,7 +341,11 @@ describe("foliage render batch helpers", () => { enabled: false, densityMultiplier: 1, maxDistanceMultiplier: 1, - shadows: "near" + shadows: "near", + windEnabled: true, + windStrength: 1, + windSpeed: 1, + windDirectionDegrees: 35 } } ); @@ -373,7 +377,11 @@ describe("foliage render batch helpers", () => { enabled: true, densityMultiplier: 1, maxDistanceMultiplier: 1, - shadows: "near" + shadows: "near", + windEnabled: true, + windStrength: 1, + windSpeed: 1, + windDirectionDegrees: 35 } } );