Incorrect Bounds in material graph

Bounds node in material graph doesn’t properly work with MaxWorldPositionOffsetDisplacement.

On a static mesh the value is “correct” (it reports the bounds of the static mesh editor).

On an instanced static mesh it is enlarged by MaxWorldPositionOffsetDisplacement.

On an instanced static mesh when you set not to evaluate WPO, it is enlarged by MaxWorldPositionOffsetDisplacement.

Is the simple static mesh bugged, or should it be enlarged too?

Shouldn’t MaxWorldPositionOffsetDisplacement be always ignored when WPO should not be evaluated?

Since you do not expose a way to detect when a drawcall is instanced or not (or do you?), how do we get the correct value?

[Attachment Removed]

Steps to Reproduce
Look at Actor3, Actor6, Actor5. They should show a bounds of 100 units. Instead they show 1100.

[Attachment Removed]

Hi Marco,

I took a look at your project and, in short, the Bounds node returns the renderer’s culling bounds, not the mesh’s bounds. Those get padded by MaxWorldPositionOffsetDisplacement, and the SM and ISM paths pad differently. It’s a known issue for us, as we’ve tagged the relevant code with TODO comments.

1. Is the simple SM bugged, or should the ISM be enlarged too?

Neither is strictly a bug, but they’re inconsistent. Both report culling bounds. The SM pad is gated by whether WPO is actually evaluated (GetMaxWorldPositionOffsetExtent, PrimitiveSceneProxy.h:902-910), so you usually see the raw mesh bounds. The ISM pad is applied unconditionally from material properties (ISMInstanceDataManager.cpp:489). The SM value matches the node’s intended meaning, while the ISM value is off due to the extra padding.

2. Shouldn’t the pad be ignored when WPO isn’t evaluated?

For the SM path, it already is. The ISM path never checks bEvaluateWorldPositionOffset: it’s derived purely from the used materials (PrimitiveComponentHelper.h:65, InstanceDataManager.cpp:561-564). This is exactly why toggling Evaluate WPO off on the ISM doesn’t shrink the bounds. This seems like a bug, and I can file one.

3. Do you expose a way to detect instanced draws?

No, and you don’t want to rely on one. Switching node type won’t help either: on an ISM, Object Local and Pre-Skinned Local are primitive-level (whole-component aggregate, not per-instance mesh), and Object Local is padded too. Only Pre-Skinned is explicitly unpadded (PrimitiveSceneProxy.cpp:1199).

4. How do we get the correct value?

Feed the Static Mesh asset bounds into the material yourself via a material parameter or Custom Primitive Data. That’s decoupled from the culling pad and identical for SM and ISM. WPOMaxExtent is exposed at BaseMaterialExpressions.ini:1135, but WPO-evaluation gates it while the ISM pad isn’t, so subtracting it back out mismatches precisely in your “WPO off” case.

I hope that helps. Please let me know if you have any further questions.

[Attachment Removed]