-
-
Notifications
You must be signed in to change notification settings - Fork 21.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Jitter raster occlusion camera to reduce false positives. #86121
Conversation
ac0d3c4
to
00f783f
Compare
eea6b9c
to
b310c86
Compare
b310c86
to
d59b2e6
Compare
Jitter off: off.mp4Jitter on: on.mp4Tried different multipliers for the jitter (0.5f, 1.0f), not much difference there Also tried using 12 frames instead of 8 but not much luck with that either. Not sure how to make this work |
If you have example scene I can try this out and figure why not working. |
Here you go MRP.zip |
Ah I think it could just a small problem with my timer logic (wrongly assuming UPDATE: I also suspect the timer logic may need a little tweak as described above, hence making draft till I can either confirm it works ok or fix it up. UPDATE: |
d59b2e6
to
c9227aa
Compare
c9227aa
to
e1895cc
Compare
Jitter 0.25f: 025.mp4Jitter 5.0f: 50.mp4Still possible to spot culling even at 5.0f but it can probably be worked around with level design. And it's much better than what is currently in godot It's probably best to expose the jitter value to the editor settings and get rid of the checkbox (let 0 mean "disabled") |
I can't seem to get it to behave like that in mine, which makes me wonder if there is a screen size dependency that isn't working correctly (the 0.25f jitter should work the same whatever the size of the occlusion buffer if it works correctly). 🤔 What screen resolution are you using for the window? Are you taking the videos by moving the viewer position in the editor? I managed to get some flickering by looking at one of the mountains through the corridor, which was how I fixed the above bug. It's much better for me now than before, but note that jittering won't solve all possible cases, notably:
Possibly at a later stage, but with initial testing the math might need some tweaking (as we see), so trying to maintain backward compatibility with such a parameter might be a pain. Also, for most users, they will prefer something that just works (TM) with a good balance. An alternative might be an enum of settings, like "CONSERVATIVE", "REGULAR" and "PERFORMANCE" or something like that. But may not good to commit to until we get some more testing / test scenes. |
I think that's the case. At 0.25f, here's the biggest gap I can catch at native resolution: And when I switch the viewport to half resolution this is the biggest gap (about twice as small):
I'm on a laptop with a 3456x2234 screen
Yeah I hold RMB, scroll all the way down to 0.2m/s and look for artifacts. Both position and camera angle contribute to them
Yeah I figured that much that's why I was experimenting with increasing the amount of frames with different offsets, but in the end of the day the value of 5.0f seems to be working fine even with 8 frames, and the only problem that's noticable with 5.0f in my case is the first one that you described (it just needs a couple of frames)
Yeah that's what I would prefer as well. Would be best if it culled in the safest way possible (only things that we're sure aren't visible), after all even when I set jitter very high and look at the overdraw it still looks near pixel perfect, so it just doesn't seems like culling too conservatively is ever gonna become a problem with godot's current culling approach |
Yeah it seems likely the calculation of how much to jitter by resolution could be improved, and that's why you are needing to vary the parameter. I'll take a look. 👍 Incidentally you should be able to roughly see how much it is jittering by, by selecting UPDATE: It now should be pretty good but I probably need to check through the jitter multiplier when I'm more awake (I'm not super familiar with this area, so its been a bit of reverse engineering) plus maybe one of the rendering team who did the TAA jitter etc can check this looks correct. |
e1895cc
to
a4f879c
Compare
Works really well with the latest changes. Biggest (persistent) gap that I could catch now is 4px wide on my screen (would be 2px in fullhd) I believe if we combine this with an option to shrink occluders by normals at baking it will eliminate the remaining flashes and be good enough for 99.99% of the games |
I've done some further tests to verify the settings, changing the occlusion buffer size by changing I've realized that your runtime occlusion buffer size is determined by how many cores your CPU has (as well as the resolution of the viewport), quite a complex calculation, so it's quite difficult to examine "like for like" without debugging or debug printing your occlusion buffer size or forcing it to a particular size. (If you place a breakpoint in As far as I can see, the multiplier can be adjusted to prevent glancing false occlusion from one side of an occluder, but it probably can't guarantee to always hit tiny gaps between occluders (but should usually work). |
I'm probably talking out of ignorance here but... |
GPU-based solutions have a delay of at least one frame (usually two), which means you need to cater for it when camera cuts occur (e.g. in cutscenes). Many games introduce a 1-frame stutter on camera cuts because of this. This delay can also manifest if the player quickly moves in front of a wall, revealing what would have been occluded on the previous frame. Example in The Talos Principle: out.webmGPU-based solutions are also difficult to put in practice when compute shaders are not available (i.e. the Compatibility rendering method). In contrast, the current CPU-based approach works on any rendering method, and could even work on a headless server with no GPU in theory (e.g. for server-side wallhack prevention). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tested locally, it works as expected.
Code looks good to me.
Oh, that makes a lot of sense. Can we query the occlusion system to get stuff like this? It could also help not updating peers about stuff that's behind a mountain or in the room next door :D PS: Ive read in another that Godot uses raycasts rather than rasterization. I wonder what is different about the two approaches and why we use raycasts... Can't embreee rasterize it for us faster and with no gaps between rays? |
Not currently, and I don't know if it'll be feasible without a frame delay. We try to minimize the amount of exposed RenderingServer getter methods because RenderingServer is designed to be able to run on multiple threads, so calling a getter method would require a slow synchronization process. (Setter methods don't have this issue.)
To my knowledge, Godot uses a raycast against the rasterized version of the scene (which is rendered on the CPU by Embree). |
Tested using the |
a4f879c
to
54dec21
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea seems pretty good and the implementation is really nice. I worry a little bit about under-occlusion though. 9 frames is a long time to leave something visible when it is fully occluded.
My gut tells me we could get the same impact without the delay by shrinking occluder meshes. But, at the same time, shrinking occluder meshes is not as foolproof with respect to different buffer sizes and screen-space projected sizes. So maybe both are needed anyway
From my point of view - I'm not aware of any obvious perfect solution here, with the occlusion buffer smaller than the window (as others have pointed out in #53288). Afaik we basically need some hacks to reduce the occlusion enough to prevent the problem in some way, to make up for taking the "small occlusion buffer" short cut.
It isn't ideal, but realistically the occlusion efficiency should still likely be >95% in most cases. In a lot of cases this might be e.g. a character still being rendered for 1/6 second after going through doorway. For users, the alternative at the moment is very broken occlusion (which may be unusable for some game releases).
Shrinking occluder meshes was mentioned in the issue, but I do suspect it could turn out to be a whole barrel of worms, given that it depends on the angle of view, distance from camera etc as you say: It would be possible to pre-process and fix one scenario, but many others will remain broken. So shrinking might have to be done at runtime (equivalent of vertex shader, but in software), and there are a lot of potential bugs. Particularly you have to deal with shrinking adjacent walls potentially creating gaps and drastically reducing any occlusion, which probably requires pre-processing and marking edges. Post-processing the occlusion buffer on the other hand could well be a simpler and better solution than shrinking (less bug inducing hopefully), and is well worth investigating, even if it adds some runtime cost. 🤔 This could later remove the need for jittering. I didn't have time to investigate this, but maybe another contributor will in the future. Either way, I suspect jittering can likely do the job as a stop-gap until someone comes up with something better (bear in mind this problem has existed for > 3 years 😨 ). It is a small change and relatively simple to add (and remove if we eventually come up with a better approach). |
54dec21
to
0f6f872
Compare
Due to the low resolution of the occlusion buffer, small gaps between occluders can be closed and incorrectly occlude instances which should show through the gaps. To ameliorate this problem, this PR jitters the occlusion buffer over time, making it more likely an instance will be seen through a gap. This is used in conjunction with an occlusion timer per instance, to prevent instances flickering on and off rapidly.
0f6f872
to
691854d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code looks good, I'd love for this to get shipped alongside the Embree upgrade.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for your response. I agree, it makes sense to move forward with this.
Shrinking meshes can be done quite safely/effectively when we generate them. But indeed, shrinking manually created meshes comes with tradeoffs and is worth investigation.
Thanks! |
A bit late to this, but I imagine the true answer is that it could be optionally supported on F+/mobile if someone were to actually implement it :) |
See Calinou's response above. Doing the occlusions on the GPU comes with a bunch of tradeoffs we don't want to accept. The main one being, it requires a 2 frame delay. The CPU version is super fast and works really well, so there is no point in having a GPU version that doesn't work as well. Eventually we will add a GPU-driven renderer (where almost all rendering takes place on the GPU). For that renderer, occlusion will be done purely on the GPU. |
I wonder however how the jittering will affect latency of the culling. Can't that also fail to immediately detect something is no longer occluded, resulting in random pop-in delay? |
You could theoretically get e.g. a frame of delay if an AABB was a tiny fraction in view and it happened to be jittered in the other direction, but I've not seen this showing up in practice. This is why the jitter pattern goes to opposite corners BTW, to prevent this occurring on more than a single frame (if it occurs at all). |
Due to the low resolution of the occlusion buffer, small gaps between occluders can be closed and incorrectly occlude instances which should show through the gaps. To ameliorate this problem, this PR jitters the occlusion buffer over time, making it more likely an instance will be seen through a gap. This is used in conjunction with an occlusion timer per instance, to prevent instances flickering on and off rapidly.
Helps address #53288
Actually this PR is very effective at combating what is a difficult problem, with the only cost being a slight increase in margins for instances falsely shown when they should be occluded.
2023-12-14.09-41-45.mp4
This can be switched on and off with project setting, which defaults to true:
rendering/occlusion_culling/jitter_projection
When off, the jittering and temporal "stickiness" will be turned off to give the same as legacy behaviour.
Discussion
While I was working on software rasterizer for 3.x, I was pondering whether this could be used for raster occlusion, which reminded me of #53288 which is the Achilles heel of raster occlusion at low resolution.
I hadn't thought of this originally when filing the bug, but it occurred to me that one way of dealing with the problem is to simply jitter the occlusion camera. Providing the jitter is approximately on the scale of pixels of the buffer, then it increases the likelihood that instances will be seen through narrow gaps, which is exactly what we want. There is almost no cost to the technique (jittering is almost free, and the timers are cheap).
On the downside, this will make instances visible slightly further around occluded objects (as this is essentially how it works), however in most cases objects are expected to be occluded behind walls etc, so the cost may not be much in reality. Additionally, some cost is justifiable in order to prevent the artifacts in #53288, which may currently make raster occlusion unusable in some situations.
Jitter scale
Jitter scale is currently such that the jitter is over approximately a pixel in the occlusion buffer. This approximately effectively increases the resolution of the occlusion buffer. The smaller the intervals within, the higher the resolution, but we would need to jitter over a larger number of frames.
Another alternative is to randomize the jitter, but this makes it difficult to calculate a deterministic instance timeout that prevents the possibility of flashing with a non-moving camera, so this option has been rejected for now.
Notes