-
Notifications
You must be signed in to change notification settings - Fork 0
Heightfield Relief Spike
Status: Phase 1 + Phase 2 SHIPPED (approaches A and B productionised).
-
Phase 1 (approach A — screen-space hillshade + cast shadows): prototype
behind
--heightfieldspike(Engine/Diagnostics/HeightfieldReliefProbe.cs); production post-pass inEngine/Rendering/Lighting/HeightfieldRelief2D.cs, applied inFractalRenderHost.UploadProcessedBuffer, driven byFractalParameters.Relief2D*+ the "Relief 3D (2D heightfield)" section inEscapeTimeParamsView. -
Phase 2 (approach B — oblique 3D raymarch + volumetric): production
renderer in
Engine/Rendering/Lighting/HeightfieldRaymarch2D.cs, selected in the same host block whenFractalParameters.Relief2DRaymarchis set. Extrudes the smooth-count field into a true surfacey = h(x,z), raymarches it from an oblique camera (Relief2DCamera{Azimuth,Elevation,Fov}Deg), and routes every hit throughShadingPipeline.Shade<TDe>— so the sharedLightingFxDatastack (soft shadow, AO, PBR spec, Beer–Lambert fog + volumetric in-scatter god-rays) lights the 2D fractal. Gate:--heightfieldraymarch(Engine/Diagnostics/HeightfieldRaymarchProbe.cs). Q5 (volumetric) answered.
Both opt-in; cover Mandelbrot + the EscapeTimeCalculator family + the CalcGen
generated escape-time types (all IHeightFieldSource).
2D escape-time fractals currently get "3D themes" that are normal-map Phong
bump only (IColorMap.Map(..., nx, ny) → PhongHelper). The per-pixel normal
lights the escape-potential slope, but the surface has no actual height, so
it cannot:
- cast shadows onto itself,
- self-occlude / show a silhouette,
- be extruded to a mesh,
- host a volume (no Z axis → no fog/in-scatter).
Two user asks share one missing primitive — a Z axis for 2D fractals:
- Q4b — real relief 3D: true cast shadows / AO / silhouette, beyond bump.
- Q5 — volumetric fx: fog / god-rays / clouds, which need a 3D medium.
Every escape-time render fills EscapeTimeCalculator.SmoothBuffer (continuous
iteration count) and DistanceBuffer (exterior DE). Either is a ready-made
height field:
- Smooth count — dense, visually smooth, good for relief. Interior = base plane (height 0).
- DE — metric distance to the boundary; better for crisp filament ridges.
The prototype recomputes a small Mandelbrot smooth field standalone (to avoid
entangling the render host); production reads SmoothBuffer directly.
Treat height h(x,y) as terrain in screen space:
- Hillshade — normal from the height gradient, Lambert against the light.
- Horizon cast shadow — march each pixel toward the light across the field (1-px DDA), tracking the max elevation angle; occluded ⇒ in shadow.
Cost O(W·H·steps), no 3D camera, no DE-march. Prototype at 512² default
Mandelbrot: 83 % exterior, 10.5 % of exterior pixels in genuine cast shadow,
relief range [0.003, 0.60]. The radiating dark streaks in
heightfield-relief.ppm are real self-shadows — impossible with bump-only Phong.
Limitations seen: single-pixel DDA gives slightly streaky/aliased shadow edges
(fix: sub-pixel step + a small blur, or a maximum-mipmap horizon map for
O(W·H·log)), and the relief reads subtle at low heightScale.
Verdict: cheapest honest upgrade. Ships the "real relief" win (shadows, self-occlusion) with no new geometry or camera. Recommended as Phase 1.
Build an explicit 3D surface z = h(x,y) and raymarch it from a real camera —
reuse ShadingPipeline (it already does AO / soft shadow / SSAO / reflection /
tonemap for the DE raymarchers). Gives arbitrary camera angle, true perspective
relief, silhouette, and — crucially — a scene the existing volumetric stack
(LightingFxData.VolumeSteps / fog / in-scatter / cloud-noise) can fill.
Cost: a camera + a heightfield DE (de(p) = p.z − h(p.x, p.y), or a proper
lower-bound estimate). Higher effort; overlaps the mesh work (#101) since a
marching-squares extrusion of the same field yields an exportable mesh.
Verdict: the honest path to Q5 (volumetric) and oblique-angle relief. Phase 2.
SHIPPED in HeightfieldRaymarch2D — see the status header. The heightfield
DE is f(p) = (p.y − h(p.x,p.z)) · invLip, Lipschitz-normalised by the field's
measured max world-space slope so the sphere trace can't overshoot ridges; the
primary ray is clipped to the terrain AABB (ray-slab) before marching, so
ray-miss = sky and the silhouette is exact. Volumetric fog comes for free: the
same DE struct is handed to Shade<TDe>, whose in-scatter walk already samples
it for god-ray shadowing.
- Iteration-band depth fog — tint by iteration count as pseudo-distance. Trivial, partial look, no real occlusion.
- Buddhabrot / Nebulabrot already are 2D orbit-density fields → genuinely fog/glow-able volumetrically with no invented geometry. Cheapest real volumetric, but only for that family.
-
Phase 1 (Q4b): productionize approach A — read
SmoothBuffer, add aHeightScale+ light-elevation param (FractalParameters, per [tunable-params convention]), emit relief into the 2D theme path as an opt-in post step. Sub-pixel shadow march + light blur to fix aliasing. -
Phase 2 (Q5): approach B — heightfield DE + camera, reuse
ShadingPipeline+LightingFxDatavolumetric knobs. Shares the extruded surface with the marching-squares mesh exporter (pairs with #101). - Opportunistic: wire approach C's Buddhabrot-density volumetric as a quick, real volumetric demo while B is built.
- Height quality tracks the potential/DE field — pairs with #100 (approximate DE for more 2D types gives crisper ridges on Tricorn / Burning Ship).
- Phase 2 volumetric reuses the raymarcher
LightingFxDatastack directly.
dotnet run --project FracturingFogCLD.csproj -- --heightfieldspikeWrites heightfield-relief.ppm + heightfieldspike.out next to the exe;
asserts non-degenerate exterior, relief range, and cast-shadow coverage.