PopcornFX / O3DEPopcornFXPlugin

PopcornFX plugin for O3DE
https://www.popcornfx.com/
Other
13 stars 12 forks source link

Sorting is difficult / impossible to control for particles with transparency #43

Open mbalfour-amzn opened 1 year ago

mbalfour-amzn commented 1 year ago

Short version: In the MultiplayerSample project, we've run into multiple particle sorting issues, and there doesn't seem to be a way to work around or fix it. This is a request to at a minimum provide a way to "unbatch" particle emitters for specific emitters to make it possible to fix sorting issues when problems arise.

Long version: Atom sorts transparent draw calls first by sort key, and then by depth. By default, everything uses a sort key of 0. PopcornFX is also currently hard-coding a sort key of 0 in CPopcornFXFeatureProcessor::Render(). In the case of MultiplayerSample, we need depth sorting since the particles appear in a level that has translucent walls everywhere, so this is OK. It would also be nice if sort key controls were exposed instead of hard-coded, but that wouldn't help for this situation anyways.

The problems show up due to particle batching. The particle effects we're using have multiple layers to them, and each layer has different-sized particles. For simplicity, let's just say there are 2 layers, one with a 1 meter particle and one with a 5 meter particle. We also reuse the effects multiple times around the level, so the same effect can draw hundreds of meters apart. These two factors are affecting the sorting in negative ways:

  1. The 2 layers of the particle effect draw in inconsistent orders relative to each other. Each layer is batched separately and contains a bounding box affected by the size of the particles, so even though both layers start with the same emitter centers, the bounding boxes become different sizes due to the size of the particles, which means that the centers can have different depths to them. This will change which layer sorts in front of the other layer when drawing, because even if the emitter centers create a 100 m bounding box, the bounding box of the two layers will be 101 m and 105 m, so they will have slightly different center points that will sort differently depending on camera location. The following video demonstrates the problem where the light blue swirl briefly sorts in front of the dark blue center depending on the overall locations of multiple particle systems relative to the camera.

https://user-images.githubusercontent.com/82224783/232830259-4a82b918-7423-492a-8a93-e46926438b52.mp4

  1. A batched set of particle effects only has one depth value that's used to sort it vs the world. Consequently, all of the effects need to appear on the same side of a second transparent item in the world or else they will sort incorrectly. The following images demonstrate the problem. There's no way to make the two different effects sort with different depth values so that one appears in front of the window and the other appears behind it.

image image image

HugoPKFX commented 1 year ago

Hi, thanks for the detailed explanation

  1. This is usually fixed using the Camera Sort Offset of renderer nodes. If the O3DE plugin doesn't support it, it's a bug, we'll look into it. How it works is PKFX just offsets draw call bboxes along the camera forward axis (world unit offset)

  2. This is the main issue with PopcornFX batching sim units. Internally PopcornFX does not simulate these two emitters separately (simplifying it to one one layer in that effect for this example). Particles are evaluated in "waves" of 512/1024/.. particles depending on the platform (wave size can be specified per layer). Because of that, the memory layout of particle storage (positions/sizes/..) needed for rendering isn't location dependent but spawn-order dependent (positions of emitter A and B are stored in the same positions array, depending on particle spawn order).

We built on top of this two systems:

  1. Is PopcornFX main issue sorting wise, some titles shipping with slicing enabled and in most cases it helped, but it definitely isn't a bulletproof solution. There will always be a case where this won't work properly, where the slicing heuristic would need to be tweaked to the game specifics. We thought about draw calls slicing using the emitter ID, but this can quickly become hardcore performance wise, as two emitters being close to each other, with particles from each overlapping could generate thousands of draw calls. Some other titles using PopcornFX completely removed CPU sorting and used OIT, that can be a solution too but it also has its share of limitations. The other option is to rewrite billboarding code so instead of slicing draw calls downstream, GPU buffers are filled per instance directly, but this will come at a cost obviously as instead of doing memcpy (for the most part) of source particle data, we'll have to iterate on each particle to determine what is the target GPU buffer. Also, one set of GPU buffers will be required per emitter in the level (or this could be a global buffer with different offsets)..

Other solutions to mitigate sorting issues:

We'll let you know our findings with slicing in the O3DE plugin

HugoPKFX commented 1 year ago

Hi,

After investigating this issue, this is what I found:

As you can see in the following image, without talking about PKFX not correctly sorting with O3DE transparent objects, we can see the smaller sphere in this image that is technically behind, is drawn on top: image

This is due to this effect using a "sort by custom value" with a key that is invalid: image

Reverting the effect to use CameraDistance sort instead, I suspect the artefacts seen in video (1) will be resolved. It is also possible to mitigate further with a CameraSortOffset value different on some renderers from that effect.

About (2), PR #54 re-enables experimental slicing in the plugin, here are some initial results: https://github.com/PopcornFX/O3DEPopcornFXPlugin/assets/15339931/3f4abd49-1020-4622-b042-1a1e450e2bc0

There's definitely some flickering, and sorting result will differ depending on the camera angle. Once the PR is merged, it would be interesting to test this in the level, and see if slicing helps. The slicing algorithm can also be customized in the Gem so it's possible to try various approaches. One of the main problem is within the slicing code itself, it currently ignores game engine geometry:

If we were to consider slicing to be working properly with all O3DE transparent geometry, the merge pass would need to take into account both PKFX slices & O3DE transparent objects draw calls somehow (ideally, before the draw packets are built to reduce overhead of rejected/merged slices) We could also disable the draw calls merge pass, but that would mean that for each draw batch, there can be up to p_PopcornFXMaxSlices sliced draw calls..

Food for thought, let's see the results of #54 once merged (by default, slicing is disabled).

mbalfour-amzn commented 1 year ago

Wow, that's some great progress! Slicing controls definitely seem like they'll go a long way towards giving the designers some ability to tune performance vs sorting correctness. One other possible idea for slice controls would be exposing some sort of "manual slice bins" on each emitter, where by default they would auto-group together into one or more auto-binned slices, but if there are a few problematic emitters on the outskirts of the level or something, they could be manually placed into their own slice.

Definitely looking forward to seeing the results of this change! Are you also going to submit a change to the effect itself to correctly use CameraDistance, or is that something that someone else (me?) needs to do?

HugoPKFX commented 1 year ago

Wow, that's some great progress! Slicing controls definitely seem like they'll go a long way towards giving the designers some ability to tune performance vs sorting correctness. One other possible idea for slice controls would be exposing some sort of "manual slice bins" on each emitter, where by default they would auto-group together into one or more auto-binned slices, but if there are a few problematic emitters on the outskirts of the level or something, they could be manually placed into their own slice.

Yes the issue is particles positions/sizes/colors/.. aren't written into GPU buffers based on their world locations but based on their spawn order. So we're left with slicing the index buffer basically, but this will be a good thing to experiment with the slicing algorithm and expose additional CVARs that can help fix problematic situations

In this specific example, the dark/marine-blue orb could very well be alpha tested particles, they seem to be alpha blended with an alpha set to 1..

Just saw another artefact in the video: image

Do the gems have a transparent material too ?

Definitely looking forward to seeing the results of this change! Are you also going to submit a change to the effect itself to correctly use CameraDistance, or is that something that someone else (me?) needs to do?

I'll take a look asap and push a fix for this effect on the MPS repo, I also saw another effect (I don't remember the name) that doesn't play where spawned but at world origin (one of the effects when firing). Will do a small cleanup pass by the end of the week

HugoPKFX commented 1 year ago

You can find two PRs here: https://github.com/o3de/o3de-multiplayersample/pull/421 https://github.com/o3de/o3de-multiplayersample/pull/422

Once these are merged (and #54) we'll take a look at the result of slicing in the level