I’ve got to hand it to Psyonix, they’ve managed to hook me with Rocket League more than any other game in a long while. Rocket League looks gorgeous running on a decent PC, yet still runs perfectly fine on my laptop with integrated graphics. The fact that I can play the game practically anywhere in quick bursts has made the game infinitely more replayable. It looking good when I’m on a proper PC is just a bonus.
However, much like with any game, with enough time you’ll come across little graphical quirks that haven’t been accounted for, or are hard to work around. Often times you won’t ever notice the issue unless you do very specific sets of actions, such as with this interesting bug.
The game ball, which is much closer to the camera, is being rendered behind the prop ball that’s part of this arena’s background. This is not the only part of this map that has this rendering issue, in fact there are a number of background elements that have the same bug.
I was only able to find examples of the bug on replays for Neo Tokyo, and it isn’t just a case of that map being broken, since normally you’d see the ball rendered correctly.
The bug only happens when you pause the replay, and skip forwards or backwards in time. So what’s something that only happens after skipping time, that doesn’t happen when paused?
After skipping time, the ball plays a forming animation. It is only once that is complete that it starts reflecting the arena, and casts a shadow. So now we know the bug is with this pre-formed version of the ball, or at least that it’s relevant.
Now, it’s important to note that it’s not exactly strange for Rocket League to render things in an order different to their actual positions in the world, such as with this goal animation…
This reaper goal animation is unique, and it’s not because it’s the only one I cringe at everytime I see it. Unlike every other scoring animation (that I can tell), this one renders on top of everything, and actually goes below the floor.
The way it is rendered means it also appears in front of player vehicles, in a similar fashion to the issues we see with the ball on Neo Tokyo.
In this instance, it’s to be able to exaggerate the size and motion of the animation, without worrying about it sinking below the ground — the cars failing to show in front is merely a side-effect — but this does show that the Rocket League developers don’t necessarily render everything in order.
So by pulling up a graphics analyzer tool (I’ll be using Intel GPA), we can start to have a look at where it goes wrong in Neo Tokyo.
I’ve captured this frame of the bug using Intel GPA and extracted the different passes that go into making a frame like this.
The first major pass includes most of the geometry in the scene, and lacks a lot of shading and effects. Let’s call this the main pass.
The second major pass has shading effects, some additional background graphics, clouds and the ball. Let’s call this the detail pass. This pass has transparency, and renders on top of the main pass.
Now let’s have a look at the main pass and detail pass for a frame where the ball is shown correctly, like the one we saw earlier.
And we can immediately see a difference: the ball is showing up in the main pass, and not the detail pass in the correct version, which tells us the problem involves something that differs between these passes.
There are two differences, and both are related to each other. The detail pass is transparent, and does not affect the depth buffer.
In order to produce certain effects, games keep what is called a depth buffer: an image that stores the distance a given pixel is from the camera. This might be used to create a depth of field effect, by blurring parts of the frame differently based on their distance from the camera.
Another use for these depth buffers is depth testing, where you use that information to tell if one object is in front of another. The Reaper and the game world have their own depth buffers, so combining them and some passes with a small Python script, we can see what The Reaper would look like with depth testing.
Transparent objects can’t and shouldn’t affect this buffer: After all, if you have a window that affects this buffer in front of a players view, the game would think that faraway objects seen through it are right in front of the camera.
Here’s the visual horror that is the depth buffer for the correctly rendered frame.
At the cost of any semblance of accuracy, I’ve gone ahead and adjusted it to be easier to look at.
The brighter the pixel, the further away from the camera it is. Since the ball is dark, it is relatively close to the camera compared to the background. So when a depth test is performed for the background elements, it will correctly determine that there’s a ball in front of it for those pixels.
And here’s the depth buffer (also adjusted) for the bugged version.
When the depth test is performed on this version, it has no way of knowing there’s a ball between it and the camera, and so ends up being drawn on top.1
The only reason the depth test causes a problem here, and not with other objects in the scene is that these specific elements are being rendered after the detail pass, the one containing the ball in the bugged version. Since the detail pass ends up on top of most of the arena’s geometry, it can mostly go unnoticed.
While I can only speculate, I believe it’s done this way to prevent the atmospheric effects or clouds from obstructing these background elements, given that they’re supposed to be bright and stick out. The side-effect just happens to be that they’ll render in front of anything that doesn’t affect the depth buffer.
I’ve also had it suggested that they need to be rendered specially because they’re the parts of the background that are animated, which is also entirely possible.
Regardless of the why, since we know how this happens, it makes it very easy to find other instances where this bug will occur: whenever transparency is involved. Like, say, the transparent goalposts from the Autumn Update…
And as to be expected, it does in fact have this rendering bug.
So what is the lesson we learn from this? Bugs like this are neat to deconstruct, but they do show that the way we render things can and will have side-effects. Most of the time they’re invisible, only showing themselves in contrived situations, or they’re so invisible in the moment-to-moment game that we fail to notice them.
In this instance, Psyonix could probably fix the issue with the ball by using the version that’s part of the world proper right up until the materialize animation actually starts. But the transparency issue as a whole would still exist, it would just be less noticeable; then we wouldn’t have been able to deconstruct why this happens.
That’s kinda the beauty of rendering bugs. They teach us a little about how everything works under the hood2, or at least get a sense of the compromises that allow for something to work as efficiently as possible.
Now, originally that was going to be the end of the post. After all, the culprit of the bug has been discovered, and it’s been tied up neatly into a point about side-effects and discovering the inner workings of a system.
But what happened here is I actually fell into a bit of a trap. I was so invested in my search, that I didn’t step back a little and ask a very simple question.
Does this bug still happen if you change graphical settings?
Only once I had a support ticket open with Psyonix, and the support person (thanks Robin!) asked if I’d messed with any of my graphical options, did I consider checking to see if it made any difference.
It did.
So here’s a quick lesson 2 for this post. Remember to look broader. Don’t think that just because you’ve found your culprit, that you’re done searching. Maybe it only happens on certain configurations, or you haven’t considered all the conditions required for something to happen.
By not doing that simple check, I failed to make note of an important condition for this bug to happen: that High Quality Shaders must be turned on. And if I was a developer on a game like this, there might have been a few false starts on trying to submit a fix.
That being said, the bug still happens with one background element when High Quality Shaders are off…
And so, as there are more elements at play than I could ever be able to uncover as an outside observer, I must throw in the towel here.
Thanks for reading.