When an Instanced Static Mesh Component (ISMC) uses a material with a non-zero Max World Position Offset Displacement, the bounds are correctly expanded at initialization. However, after calling UpdateInstanceTransform() on an instance of the ISMC at runtime, the instance's bounds appear to be recalculated without considering the material's Max WPO Displacement value. This results in incorrect bounds for the updated instance, which in turn causes incorrect frustum culling results.
Expected Behavior:
Updating an instance transform via UpdateInstanceTransform() should preserve or recompute the instance’s bounds while respecting the Max World Position Offset Displacement setting on the applied material, ensuring proper visibility and culling behavior.
Actual Behavior:
Calling UpdateInstanceTransform() on an instance causes its bounds to be updated without accounting for the material’s Max WPO Displacement value, resulting in incorrect culling behavior.
Suggested Fix:
The issue appears to stem from the implementation in FPrimitiveSceneProxy::UpdateInstances_RenderThread, where bounds are updated using the following logic:
Bounds = InBounds;
LocalBounds = InLocalBounds;
This logic does not reapply the material-based WPO displacement padding.
A suggested fix is to mirror the bounds padding behavior used in FPrimitiveSceneProxy::SetTransform() by replacing the above lines with:
const float PadAmount = GetAbsMaxDisplacement();
Bounds = PadBounds(InBounds, PadAmount);
LocalBounds = PadLocalBounds(InLocalBounds, LocalToWorld, PadAmount);
This change ensures that updated instance transforms continue to respect the material’s Max World Position Offset Displacement and avoids incorrect culling behavior.
To avoid duplication and improve code maintainability, consider refactoring this logic into a shared helper function (e.g., FPrimitiveSceneProxy::SetBounds(...)) that can be reused in both SetTransform and UpdateInstances_RenderThread.
This would ensure consistent handling of WPO-based displacement across all paths that modify primitive bounds.
1. Create a material that uses World Position Offset to displace vertices and set a corresponding Max World Position Offset Displacement in the material properties.
2. Apply this material to a mesh used in an Instanced Static Mesh Component.
3. At runtime, call UpdateInstanceTransform() on one of the instances.
4. Observe that the instance now has incorrect bounds (can be observed in editor by enabling the `bounds` showflag) and may be incorrectly culled from view from certain camera angles.
There's no existing public thread on this issue, so head over to Questions & Answers just mention UE-297159 in the post.