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
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
- Enable the VoxelForge plugin and restart the editor so the
VoxelForgeLivingPlaneruntime module and its shader mapping load. - Create a material named
M_LivingPlaneusing the graph below. - Place
VoxelForgeLivingPlaneActorin the level. AssignM_LivingPlaneto Plane Material. - Keep the actor on the world XY plane with unit actor scale.
Plane Length Meterscontrols the mesh width and simulation width; the built-in plane mesh is scaled to that size. - 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:
- Read
LivingFieldatTextureCoordinate[0]with a Texture Sample. - Multiply its R output by
100to convert metres to Unreal centimetres. - 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)andTexCoord + (1/FieldSize, 0). - Down/up UV:
TexCoord + (0, -1/FieldSize)andTexCoord + (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.csSource/VoxelForgeLivingPlane/Public/VoxelForgeLivingPlaneActor.hSource/VoxelForgeLivingPlane/Private/VoxelForgeLivingPlaneActor.cppSource/VoxelForgeLivingPlane/Private/VoxelForgeLivingPlaneModule.cppShaders/LivingPlane.usf
The VoxelForge.uplugin runtime-module entry must ship with them.