Author transient surface wetness and HDRP rain-gloss response
verifiedAgainst b1311b5 · verifiedOn 2026-09-10.
What you will build
Section titled “What you will build”In Voxamine, environmental weather does not merely spawn overhead raindrop particles. When rain falls across voxel terrain, the ground physically reflects that moisture (MASTERPLAN.md §13.1): blocks visibly darken from liquid saturation, specular highlights sharpen across stone and soil as thin water films accumulate, and sun and moon directional highlights travel smoothly across pooled surface gloss.
You will implement the transient surface moisture pipeline using SurfaceWetnessMath.cs, differentiate fast surface films from slow biological soil moisture (SoilMoistureMath.cs), configure biome-dependent drying rates via BiomeWeatherProfile.cs, map global wetness into an HDRP Lit albedo darkening and specular response curve using WetSurfacePresentationMath.cs, and drive terrain materials self-bootstrappingly via SurfaceWetnessRuntime.cs.
// Evaluate instantaneous surface material look during a downpour:WetSurfaceMaterialLook look = WetSurfacePresentationMath.Evaluate( authoredBaseColor: stoneColor, authoredSmoothness: 0.15f, globalWetness: 0.85f, materialResponse: 1.0f);
// Albedo darkens by 28% from water film saturation:// look.BaseColor -> darkened stone RGB// Smoothness climbs to mirror-like wet specular sheen:// look.Smoothness -> 0.76f (sharpened HDRP highlight!)Where this sits
Section titled “Where this sits”The surface response architecture decouples transient visual presentation from persistent world data:
- Weather Engine (
WeatherRuntime&WeatherSnapshot) publishes the current weather state (Rain,Snow,Clear) and precipitation intensity. - Pure Film Math (
SurfaceWetnessMath) integrates rain soaking ($+0.11/\text{s}$) and arid evaporation without allocating memory or touching game objects. - Presentation Finishing (
WetSurfacePresentationMath) translates scalar wetness and snow cover into non-destructive HDRP material modifications. - Runtime Coordinator (
SurfaceWetnessRuntime) samples the player’s active biome, evaluates wetness over delta time, and updates material properties on active chunk renderers.
flowchart TD
subgraph SIMULATION["Weather Simulation"]
WEATHER["WeatherRuntime.CurrentSnapshot\n(WeatherType, PrecipitationIntensity)"]
BIOME["BiomeWeatherProfile.For(biome)\n(PrecipitationSusceptibility, Freezes)"]
end
subgraph MATH["VoxelSandbox.World.Environment"]
FILM["SurfaceWetnessMath.Advance\n(+0.11/s rain soak, -0.028/s base dry)"]
GLOSS["SurfaceWetnessMath.EvaluateSurfaceGloss\n(SmoothStep [0.05, 0.8] ramp)"]
LOOK["WetSurfacePresentationMath.Evaluate\n(Albedo darkening 28%, Max smoothness 0.76)"]
end
subgraph RUNTIME["SurfaceWetnessRuntime (MonoBehaviour)"]
UPDATE["Update loop (0.5s refresh timer)\nGlobalSurfaceWetness ∈ [0, 1]"]
MAT_CACHE["Dictionary<Material, WetMaterialState>\n(Preserves authored albedo & smoothness)"]
end
subgraph HDRP["HDRP Chunk Rendering"]
TERRAIN["Terrain Chunk Mesh Renderers\n(_BaseColor, _Smoothness modulated)"]
end
WEATHER --> UPDATE
BIOME --> UPDATE
UPDATE --> FILM
FILM --> GLOSS
GLOSS --> LOOK
LOOK --> MAT_CACHE
MAT_CACHE --> TERRAIN
Before you start
Section titled “Before you start”Read Docs/MASTERPLAN.md §13.1 (“Weather and surface response”), then inspect:
SurfaceWetnessMath, differential accumulation and evaporation equations.WetSurfacePresentationMath, pure albedo darkening and specular response.SurfaceWetnessRuntime, single owner of material weather response.SurfaceWetnessMathTests, edit-mode tests asserting accumulation, drying, and clamping invariants.
Understand the distinction between the two moisture layers:
| Layer | Type | Timescale | Persistence | Effect |
|---|---|---|---|---|
Surface Wetness (SurfaceWetnessMath) |
Physical surface liquid film | Minutes (soaks in $10\text{ s}$, dries in $90\text{ s}$) | Transient (not persisted) | Darkens block albedo by up to $28%$, sharpens specular highlights to $0.76$ smoothness. |
Soil Moisture (SoilMoistureMath) |
Subsurface capillary ground water | In-game days | Persisted in save data | Drives crop growth rates, seed germination, and arid vegetation death. |
The build, step by step
Section titled “The build, step by step”1. Differential Film Accumulation and Drying
Section titled “1. Differential Film Accumulation and Drying”In SurfaceWetnessMath.Advance, advance the surface film $w \in [0, 1]$ over elapsed delta time:
$$\frac{dw}{dt} = \begin{cases} +0.11 \times I_{\text{precip}} & \text{if Rain} \ +0.02 \times I_{\text{precip}} & \text{if Snow} \ -0.028 \times \text{lerp}(0.55, 2.3, d_{\text{biome}}) & \text{if Clear} \end{cases}$$
public static float Advance(float current, WeatherType weather, float precipitationIntensity, float biomeDryness, float deltaSeconds){ float wetness = Mathf.Clamp01(current); if (float.IsNaN(deltaSeconds) || float.IsInfinity(deltaSeconds) || deltaSeconds <= 0f) { return wetness; }
float intensity = Mathf.Clamp01(precipitationIntensity); float rate = weather switch { WeatherType.Rain => RainSoakPerSecond * intensity, WeatherType.Snow => SnowSoakPerSecond * intensity, _ => -BaseDryPerSecond * Mathf.Lerp(0.55f, 2.3f, Mathf.Clamp01(biomeDryness)) };
return Mathf.Clamp01(wetness + rate * deltaSeconds);}Notice that drying is modulated by BiomeDryness: arid deserts evaporate wet films up to $4\times$ faster than humid rainforests or cold taigas.
2. Smoothstep Glossiness Mapping
Section titled “2. Smoothstep Glossiness Mapping”Linear wetness does not produce a natural optical response: puddles form distinct glossy patches rather than a foggy blur. In SurfaceWetnessMath.EvaluateSurfaceGloss, map scalar wetness through a smoothstep S-curve:
public static float EvaluateSurfaceGloss(float wetness){ // Ignores the first 5% dampness; saturates into full puddle gloss at 80%: return Mathf.SmoothStep(0f, 1f, Mathf.InverseLerp(0.05f, 0.8f, Mathf.Clamp01(wetness)));}3. Albedo Darkening and Specular Shaping
Section titled “3. Albedo Darkening and Specular Shaping”In WetSurfacePresentationMath.Evaluate, calculate the finished material look:
public static WetSurfaceMaterialLook Evaluate( Color authoredBaseColor, float authoredSmoothness, float globalWetness, float wetResponse, float globalSnowCover, float snowResponse){ float wr = Mathf.Clamp01(wetResponse); float wetness = Mathf.Clamp01(globalWetness) * wr; float gloss = SurfaceWetnessMath.EvaluateSurfaceGloss(globalWetness) * wr;
// Darken albedo by up to 28%: float darkening = 1f - WetDarkening * wetness; Color color = new( authoredBaseColor.r * darkening, authoredBaseColor.g * darkening, authoredBaseColor.b * darkening, authoredBaseColor.a);
// Boost smoothness toward 0.76 wet sheen: float smoothness = Mathf.Lerp(authoredSmoothness, MaximumWetSmoothness, gloss);
// Lying snow overlays the wet ground: float sr = Mathf.Clamp01(snowResponse); float snow = Mathf.Clamp01(globalSnowCover) * sr; if (snow > 0f) { color = Color.Lerp(color, SnowColor, snow); smoothness = Mathf.Lerp(smoothness, SnowSmoothness, snow); }
return new WetSurfaceMaterialLook(color, smoothness);}4. Non-Destructive Material Application
Section titled “4. Non-Destructive Material Application”In SurfaceWetnessRuntime.cs, materials are cached upon discovery so authored values are never permanently overwritten:
if (!wetMaterials.TryGetValue(mat, out WetMaterialState state)){ state = new WetMaterialState( mat.color, mat.GetFloat("_Smoothness"), ResolveWetResponse(mat)); wetMaterials.Add(mat, state);}
WetSurfaceMaterialLook look = WetSurfacePresentationMath.Evaluate( state.AuthoredColor, state.AuthoredSmoothness, wetness, state.Response);
mat.color = look.BaseColor;mat.SetFloat("_Smoothness", look.Smoothness);Because the runtime relaxes to equilibrium within 90 seconds, it requires zero disk persistence. A save game reloads at dry state and naturally reconverges if loaded during an active storm.
Verify
Section titled “Verify”1. Execute EditMode Tests Headlessly
Section titled “1. Execute EditMode Tests Headlessly”Run the dedicated surface response test suite:
/opt/unity/Editor/Unity -batchmode -nographics \ -projectPath /home/soulwax/workspace/engines/unity/minecraft/Minecraft-HD \ -runTests -testPlatform EditMode \ -testFilter VoxelSandbox.Tests.SurfaceWetnessMathTests/opt/unity/Editor/Unity -batchmode -nographics \ -projectPath /home/soulwax/workspace/engines/unity/minecraft/Minecraft-HD \ -runTests -testPlatform EditMode \ -testFilter VoxelSandbox.Tests.WetSurfacePresentationMathTestsVerify that all assertions pass:
Advance_RainSoaksTowardFullyWet: verifies steady-state convergence at $1.0$.Advance_ClearWeatherDriesTowardZero: verifies complete evaporation after rain stops.Advance_AridBiomesDryFasterThanHumidOnes: validates desert vs jungle evaporation multipliers.Evaluate_DarkensAlbedoAndRampsSmoothness: verifies the $28%$ darkening and $0.76$ max smoothness boundaries.Evaluate_SnowOverridesWetLook: proves lying snow covers the wet surface look.
2. Inspect in Play Mode
Section titled “2. Inspect in Play Mode”- Enter Play Mode in
Gameplay.unity. - Locate
[Voxel Sandbox] Surface Wetnessin the scene hierarchy. - Observe
GlobalSurfaceWetnessduring clear weather: reads0.0. - Trigger rain via the debug console or Weather Lab:
- Watch
GlobalSurfaceWetnessclimb smoothly from0.0to1.0over ~10 seconds. - Observe ground terrain darkening and specular glints appearing as sun highlights hit the wet surface.
- Watch
- Stop rain: observe the ground gradually brightening back to its authored matte appearance.
Now do your own
Section titled “Now do your own”To author a custom block material with specialized wetness response (e.g. Porous Sandstone or Water-Repellent Waxed Planks):
- Configure Material Response: Set custom
_WetResponsefloat on the material ($0.0$ for waterproof, $1.0$ for full soak). - Add Porosity Overrides: Extend
SurfaceWetnessRuntime.ResolveWetResponseto query block properties fromBlockDefinition. - Verify Restoration: Confirm that entering and exiting rain fully restores authored base color without drift.
- Add EditMode Tests: Add test assertions in
WetSurfacePresentationMathTests.cs.
Pitfalls
Section titled “Pitfalls”| Symptom | Cause | Fix |
|---|---|---|
| Save file size grows or save schema breaks across updates. | Surface wetness was saved to WorldSaveData.bin. |
Do not persist transient surface wetness. The differential equations converge to steady state within 90 seconds. |
| Ground turns completely black or blindingly specular during light rain. | EvaluateSurfaceGloss was mapped with a linear function instead of smoothstep. |
Use Mathf.SmoothStep(0f, 1f, Mathf.InverseLerp(0.05f, 0.8f, wetness)) to filter out minor dampness before sharpening highlights. |
| Crops stop growing during dry weather even with high surface wetness. | Plant growth was wired to SurfaceWetnessMath instead of SoilMoistureMath. |
Surface wetness is a transient visual film. Deep biological moisture belongs exclusively to SoilMoistureMath. |
| Material base colors become permanently darkened across scene reloads. | mat.color was modified without storing the initial authored color in a cache. |
Always cache state.AuthoredColor before applying weather adjustments. |
| Wetness updates hitch or spike CPU frame time in large worlds. | Every chunk material was queried and modified on every single frame. | Throttle updates using RefreshIntervalSeconds = 0.5f as implemented in SurfaceWetnessRuntime. |
User-contributed notes
Corrections, clarifications, and practical tips for this page. Anonymous is fine — a name is optional. Basic Markdown works:
**bold**,*italic*,`code`, and links.Notes policy
Notes are lightly filtered for spam and may be edited or removed. Keep them about this page — no support requests, no personal data, nothing you would not publish. Links are limited and marked
nofollow.