PerInstanceRandom with Pathtracer/Cryptomatte

I have a scene with VAT foliage using WPO and PerInstanceRandom to randomize their time offsets. I’m using this cvar to increase the number of variations in pathtracer

r.RayTracing.Geometry.InstancedStaticMeshes.SimulationCount 30

but I’m running into the issue where the beauty render no longer matches the Object ID pass. Setting the cvar to -1 to disable it is not an option as that immediately crashes the engine due to the amount of foliage in the scene. But I’m curious if there’s a way to apply the same “simulation count” limit to lit/unlit so the Object ID stays consistent with the pathtracer.

Similarly, the renders also seem non-deterministic, meaning that for each render, the variations applied through the cvar are randomized across the instances. Is there any way to prevent that?

Hi,

the issues you’re describing are current limitations of the engine. The reason why the Object ID pass doesn’t match the path traced beauty pass when r.RayTracing.Geometry.InstancedStaticMeshes.SimulationCount is enabled, is because the Object ID pass is rendered with rasterization, while the CVar only applies to raytraced/paththraced rendering and because there is no raster equivalent of SimulationCount. r.RayTracing.Geometry.InstancedStaticMeshes.SimulationCount is a specific optimization for the ray tracing pipeline and limits the number of unique world position variations the BVH has to bake for instances. Rasterization (used for Object ID) evaluates the WPO for every single instance on the fly. Setting to CVar to -1 does not disable WPO simulation, but removes the cap (which is set at 256) on the number of meshes that get WPO applied. In order to achieve a match between the path traced beauty and Object ID pass, you could consider adding a custom path traced Object ID pass, which uses a material override that outputs a stable ID. That way, both passes will respect the SimulationCount setting.

The non-deterministic behaviour of the PerInstanceRandom node is discussed in [this [Content removed] where a workaround is also provided in the form of custom HLSL node which calculates a random number by hashing the instance index, though the results are still dependent on the order of primitives in the scene, so any culling, visibility changes, or any change to the order in which shapes are passed to the GPU for raytracing will change the results. To avoid that, you could expand the custom node to calculate a hash from the instance position instead or generate your own random offset per instance and store it in the per instance custom data.

I hope that helps, but let me know if you have more questions.

Thanks,

Sam

Hi,

thanks a lot for the in depth answer. We investigated this issue a bit more and eventually landed at the same suggestion you described about using per instance custom data to drive the WPO. It seems that the cvar takes the unique WPO of the first X instances (X is set by the cvar) in the ISM array and applies it to the remaining instances sequentially so the custom data can be set to match. However, this only seems to work with ISM as the Foliage ISM applies the unique WPO seemingly at random (and also changes when the instances are moved around). Do you know what the rules are to this WPO matching for foliage?

Could you provide any more info about making a custom path traced Object ID pass? We’d need a proper implementation of the cryptomatte definition as using just ‘flat’ colors doesn’t give us the level of detail we need, like proper antialiased selection.

Thanks.

Hi,

the logic which relies on the SimulationCount can be found in InstancedStaticMesh.cpp (look for SimulatedInstances), where this setting seems to be used in the calculation of the DynamicInstanceIndex (you could place some break points or debug logging in that part of the code to see if that instance index is random).

I could not find anything specific in the code that distinguishes between foliage and non-foliage ISMs, however I believe the reason the foliage ISM seems random is because foliage is optimised for occlusion culling and is partitioned into clusters, and both of these may change the order of the instances and the instance index. If the calculation of the random number that drives the WPO relies on the instance ID, that would result in non-deterministic WPO. If that’s the case, you could look into calculating a random number based on using the ObjectPivotPoint node where ObjectPivot Location uses Local Space (which will give you the location of the primitive’s pivot), and Mesh Particle Pivot Location uses the Instance and Particle Space as explained [Content removed] (or use the Local Position node with Exclude Material Shader Offsets with Instance as Local Origin).

I did some investigation into implementing a path traced Object ID pass that is compliant with the cryptomatte spec and discovered that it would require a way to output multiple material/object IDs per pixel for edges, transparency and motion blur in a cryptomatte-style buffer which is not a trivial thing. If you want, I can pass this as a feature request to Epic. Please let me know.

Thanks,

Sam

Hi Sam,

I think a path-traced Cryptomatte pass is necessary for reliable compositing, since screen-space-based IDs can diverge from path-traced results in many ways.

If you could please pass this along as a feature request, we would really appreciate it.

Best,

Josef

Hi,

I understand that a path traced cryptomatte would solve a lot of those issues. Thanks for bringing it up, I will pass it on.

Thanks,

Sam