Files
VoxelForge/Experiments/LivingPlane/UE-SETUP.md
T
Fr0zkaandClaude Opus 5.5 3b71d35eba experiment: LivingPlane repeatable + standalone UE runtime module (L164)
Readback barrier fix (GL_TEXTURE_UPDATE_BARRIER_BIT before glGetTexImage); check_repeatability.py compares full-field
checksums across query/capture settings - Intel GPU PASS (3b9d45905c7aafff), llvmpipe PASS, mismatch control fails.
New runtime module VoxelForgeLivingPlane (separate from generation): RDG global shader (Shaders/LivingPlane.usf),
ping-pong RGBA32F render targets, actor with WPO plane and object inputs, no CPU readback. Compiles in Linux UE 5.7.
Windows build: Source/VoxelForgeLivingPlane/*, Shaders/LivingPlane.usf, VoxelForge.uplugin. Setup: UE-SETUP.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
2026-11-02 11:10:43 +01:00

4.1 KiB

Living Plane in Unreal Engine 5.7

VoxelForgeLivingPlane is a separate runtime module. It does not call the VoxelForge terrain generators and it does not read the simulated texture back to the CPU. The actor owns two transient RGBA32f render targets and advances them with an RDG compute pass. The CPU sends object state and fixed-step uniforms only.

Add it to a level

  1. Enable the VoxelForge plugin and restart the editor so the VoxelForgeLivingPlane runtime module and its shader mapping load.
  2. Create a material named M_LivingPlane using the graph below.
  3. Place VoxelForgeLivingPlaneActor in the level. Assign M_LivingPlane to Plane Material.
  4. Keep the actor on the world XY plane with unit actor scale. Plane Length Meters controls the mesh width and simulation width; the built-in plane mesh is scaled to that size.
  5. The actor creates and clears its two GPU state targets on BeginPlay. The mesh has collision disabled; this surface is render-only and does not change VoxelForge density or collision queries.

Material graph

Use a Surface, Opaque material. The actor binds these parameters on its dynamic material instance:

  • LivingField: Texture Object Parameter. The texture is the current RGBA32f simulation state; R is displacement in metres.
  • PlaneLengthMeters: Scalar Parameter.
  • FieldSize: Scalar Parameter, equal to the render target width and height.

For World Position Offset:

  1. Read LivingField at TextureCoordinate[0] with a Texture Sample.
  2. Multiply its R output by 100 to convert metres to Unreal centimetres.
  3. Append (0, 0) to that value, making (0, 0, heightCm), and connect it to World Position Offset.

For the displaced normal, sample the height at four UV offsets using the same texture object:

  • Left/right UV: TexCoord + (-1/FieldSize, 0) and TexCoord + (1/FieldSize, 0).
  • Down/up UV: TexCoord + (0, -1/FieldSize) and TexCoord + (0, 1/FieldSize).
  • dHdx = (Right.R - Left.R) * FieldSize / (2 * PlaneLengthMeters).
  • dHdy = (Up.R - Down.R) * FieldSize / (2 * PlaneLengthMeters).
  • Make and normalize the tangent-space vector (-dHdx, -dHdy, 1), then connect it to Normal.

The built-in plane's U/V tangent directions align with local X/Y. Use a neutral base colour and roughness around 0.7 to start. The graph assumes a horizontal plane; add a local-to-world transform if the plane needs arbitrary rotation.

Drive objects

ObjectInputs has two slots. Each FVFLivingPlaneObjectInput contains world position in centimetres, world velocity in centimetres per second, radius in centimetres, base push acceleration in metres per second squared, and an active flag. Blueprint can use Set Object State or set the array entry; C++ can call the same method. Set the object's center about one radius above the plane while it contacts it, and mark the slot inactive after lift-off.

The compute shader uses the radius to scale the Gaussian contact footprint. The original wave equation keeps the L163 wave speed, damping, foundation spring, travelling breathing force, and recovering imprint. The base push acceleration supplies the object force; lateral speed adds 0.25 m/s² per 1 m/s, and downward speed adds 0.5 m/s² per 1 m/s. Upward velocity does not add force. Object positions and velocities are transformed into the plane's local frame and converted from centimetres to metres before upload.

Each step uses dt = 1.30 / sqrt(8*c*c/(dx*dx) + foundationStiffness), with dx = PlaneLengthMeters / FieldSize. Simulation time passed to breathing is the fixed step index times dt. Game-thread delta time only determines how many fixed steps are queued; no GPU field data is read back in the normal path.

Build files

The owner's Windows build needs every added source and shader file:

  • Source/VoxelForgeLivingPlane/VoxelForgeLivingPlane.Build.cs
  • Source/VoxelForgeLivingPlane/Public/VoxelForgeLivingPlaneActor.h
  • Source/VoxelForgeLivingPlane/Private/VoxelForgeLivingPlaneActor.cpp
  • Source/VoxelForgeLivingPlane/Private/VoxelForgeLivingPlaneModule.cpp
  • Shaders/LivingPlane.usf

The VoxelForge.uplugin runtime-module entry must ship with them.