feat(render): weather-driven foliage wind for procedural scenery, shadows follow (Campaign VM VM6b)
Procedural-scenery foliage (trees/bushes — entity ids in the ProceduralSceneryIdAllocator's 0x8XXYYIII namespace) sways with weather in mesh_atmospheric.vert and all four directional_shadow_world_* caster vertex shaders, both calling the identical new foliage_wind.glsl include so the shadow moves with the leaf by construction. Classification (FoliageWindClassification, AcDream.App.Rendering.Wb): two new BatchData.flags bits, computed once per (entity, subset) from four inputs — entity id (bit 31 for procedural scenery), the pack's declared FoliageExclusions membership, the subset's TranslucencyKind, and ObjectRenderData.HasCutoutSubset (computed once per mesh at build time, not per frame). Bit 1 marks an alpha-cutout leaf subset; bit 2 marks an opaque trunk subset (only when its own mesh also owns a cutout subset, so rocks stay still). WbDrawDispatcher.ClassifyBatches (world receiver) and AddDirectionalShadowBatches (caster) call this with the same four inputs, so casters and receivers classify identically without needing to share state. Retail's mesh_modern/terrain_modern/mesh_detail pipelines never read these bits, so pack-off output is unaffected. Motion model (foliage_wind.glsl, mirrored bit-for-bit in the new FoliageWindModel for hermetic CPU tests): height-squared-scaled slow lean for every foliage subset, plus branch swing and per-vertex-hash-decorrelated flutter for cutout subsets only. AtmosphericPostProcessGraph.ResolveFoliageWind resolves the wind block once per frame.Serial — advanced by whichever of RenderDirectionalShadows (which runs first) or RenderPostProcess is called first that frame, with the second reading the already-advanced state, which is what keeps the caster and receiver reading byte-identical clock/strength values. The per-day-group mean/gust target (AtmospherePolicyDeclaration. FoliageWindByDayGroup, keyed by the same day-group index convention ActiveDayGroupMultipliers already established: Clear/Cloudy/Overcast/Rainy) eases toward its target over WeatherSystem.TransitionSeconds (10s) so a weather change never snaps; wind-enabled off or indoor instead gates the OUTPUT to an exact zero (not an asymptotic approach) so a settings toggle or cell transition is immediate. The wind clock is a Stopwatch started at graph construction (monotonic, session-relative magnitude for GPU sin() accuracy), overridable by the same ACDREAM_SKY_PHASE_SECONDS pin SkyRenderer already uses, for deterministic offline gates. New settings: wind-enabled, wind-strength, wind-direction-degrees (225° default — no authored retail wind direction exists to read), wind-lean-metres, wind-branch-metres, wind-flutter-metres (0 on Low), wind-canopy-height-metres. Register row IA-25 files this as an intentional, strictly opt-in divergence: retail applies no per-vertex wind displacement to any geometry. Known, accepted limitation: classification is per mesh-subset (one BatchData.flags word per indirect-draw batch), not per entity instance, so the rare case of one mesh subset being reachable from both a procedural-scenery and a non-scenery placement would classify all of that subset's instances alike. Tests: FoliageWindClassificationTests (the full classification matrix), FoliageWindModelTests (identity on non-foliage/calm-wind/base-vertex, canopy-top displacement bound, z-never-increases, trunk has no flutter term), RenderPackAtmospherePolicyEvaluationTests (exact day-group lookup, no interpolation across day-group ids, easing convergence without overshoot or discontinuity), AtmosphericShaderAbiTests (each of the five shaders calls acdreamFoliageDisplace exactly once; mesh_modern/terrain/mesh_detail call it never), and four AtmosphericPostProcessGraphTests additions (indoor/disabled exact-zero gating, settings-to-UBO wiring, same-frame-Serial idempotency — the last proxies the caster/receiver agreement invariant without needing this hermetic harness's WbDrawDispatcher/TerrainModernRenderer dependency chain to exercise RenderDirectionalShadows directly). App hermetic filter: 6015/6017 (the same 2 pre-existing failures as VM6a, confirmed unrelated). Core.Tests hermetic: 4697/4697. RenderPackValidator.Tests: 30/30. Full solution Debug and Release builds green. Shader recompile touched exactly the 5 edited files' .spv (plus manifest); the retail oracle set and every other pack shader are byte-identical. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
0930c35d1d
commit
39e8408c7d
34 changed files with 1536 additions and 64 deletions
|
|
@ -366,6 +366,14 @@ public enum RenderSettingSemantic
|
|||
VolumetricStrength,
|
||||
VolumetricRayMarchSteps,
|
||||
AutomaticQuality,
|
||||
// Campaign VM VM6: foliage wind.
|
||||
WindEnabled,
|
||||
WindStrength,
|
||||
WindDirectionDegrees,
|
||||
WindLeanMetres,
|
||||
WindBranchMetres,
|
||||
WindFlutterMetres,
|
||||
WindCanopyHeightMetres,
|
||||
}
|
||||
|
||||
/// <summary>One point on a declared sun-elevation response curve.</summary>
|
||||
|
|
@ -378,6 +386,16 @@ public sealed record ActiveDayGroupMultiplier(
|
|||
int ActiveDayGroup,
|
||||
double Multiplier);
|
||||
|
||||
/// <summary>
|
||||
/// Explicit mapping from an authored AC day group to the foliage-wind mean
|
||||
/// and gust strength for that group (both in the declared [0,1] range before
|
||||
/// the pack's global wind-strength setting scales them). Campaign VM VM6.
|
||||
/// </summary>
|
||||
public sealed record FoliageWindDayGroupPoint(
|
||||
int ActiveDayGroup,
|
||||
double Mean,
|
||||
double Gust);
|
||||
|
||||
/// <summary>Visible authored-atmosphere interpretation owned by the pack.</summary>
|
||||
public sealed record AtmospherePolicyDeclaration(
|
||||
IReadOnlyList<SunElevationResponsePoint> SunElevationResponse,
|
||||
|
|
@ -404,6 +422,32 @@ public sealed record AtmospherePolicyDeclaration(
|
|||
/// </summary>
|
||||
public IReadOnlyList<SunElevationResponsePoint> VolumetricShaftSunElevationResponse
|
||||
{ get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Campaign VM VM6: exact per-day-group foliage-wind mean/gust targets,
|
||||
/// looked up by the same categorical <c>activeDayGroup</c> id
|
||||
/// <see cref="ActiveDayGroupMultipliers"/> already keys on (an exact
|
||||
/// match, not an interpolation across day-group ids — day groups are not
|
||||
/// ordered by "how windy"). The host smooths the resolved (mean, gust)
|
||||
/// target toward whatever this table returns over time using the
|
||||
/// existing weather delta-seconds clock, so a day-group change never
|
||||
/// snaps. A day group absent from this table gets zero wind. A pack
|
||||
/// using foliage wind should declare an entry for every day group it
|
||||
/// cares about distinguishing.
|
||||
/// </summary>
|
||||
public IReadOnlyList<FoliageWindDayGroupPoint> FoliageWindByDayGroup
|
||||
{ get; init; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Campaign VM VM6: object/GfxObj ids excluded from foliage-wind sway
|
||||
/// even though their entity id falls in the procedural-scenery
|
||||
/// namespace and their subset classification would otherwise qualify —
|
||||
/// the rare scenery object that is cutout-material but not actually
|
||||
/// foliage (e.g. a cutout rock or fence prop drawn from the scenery
|
||||
/// table). There is no include list: outside this exclusion set, the
|
||||
/// classification rule in <c>WbDrawDispatcher</c> is the rule.
|
||||
/// </summary>
|
||||
public IReadOnlyList<uint> FoliageExclusions { get; init; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue