Rendering Updates - April 2017

This would definitely explain why I was never able to reproduce complaints about clipping. Does this mean Mac will remain at 0.5 now instead of the closer plane before this update?

This is amazing!

With the float point errors no longer showing up ~12k studs from origin, near infinite randomly generated maps will become a lot more viable!

1 Like

Hey @zeuxcg I’ve noticed an issue with terrain water recently, I suspect it may have came from these updates it’s a fairly big issue. When underwater you can’t see the surface of the water anymore so for example if there is a boat floating on water it looks like it’s hovering in blue fog.

Issue:

Here’s an old photo I found of the water somewhere:

As you can see something has gone wrong with the surface of the water.

1 Like

That’s definitely a regression; we’ll take a look, thanks!

3 Likes

Can you tell us how the z-fighting fixes work? Are you using a logarithmic depth buffer?

4 Likes

Hi @opplo, this will be fixed in an upcoming release. I’ll post here when the fix is live.

2 Likes

We don’t use a logarithmic depth buffer (it’s not supported natively by GPUs; you can emulate this with shaders but doing so either requires a reasonably high level of tessellation of all objects, which we don’t have and don’t want, or having pixel shader output the depth value, which breaks some important GPU optimizations).

The way standard rasterization pipeline with perspective camera works with depth is:

  • Depth is a 0…1 value stored in a 24-bit fixed point value; this means at any point on this scale your precision is 2^-24
  • Depth is computed from camera-space depth (let’s call it Z) using approximately this equation: 1 - znear / z (this equation ignores zfar but for typical zfar/znear it’s not very important). Note that this is 0 when z = znear, approximately 1 where z is big (zfar).

Because of this inverse mapping you have a property where the depth is mostly distributed near the camera. When z = znear depth is 0; when z = znear * 2 depth is 0.5 - so half of your depth range is right in front of the camera (in Roblox this is 0.5…1 studs before the update). Next doubling gives you half of the remaining range or 25% of the full range (1…2 studs); etc. You can see how for large z values you get smaller and smaller depth ranges in 0…1; since precision is always 2^-24 this means the further the point is from the camera, the larger the “error region” (the region where other points map to the same depth value) is in studs, hence z-fighting.

This is also why we can’t really give you near plane for tuning - notice that changing near plane shifts the distribution dramatically - making it 0.1 instead of 0.5 means your “high precision” regions shrink 5x, so if you had Z-fighting 1000 studs away from the camera now you have it 200 studs away.

What we did was two things - switch to a floating-point depth buffer and reverse the Z mapping. I’ll talk briefly about both.

Floating-point depth buffer stores a standard 32-bit depth value (well, 31-bit since sign bit isn’t used). Floating point representation is basically 2^e*m, where m is a 24-bit number between 1 and 2 (I am ignoring a lot of details here, no nitpicking please!). This means that in any region between two consecutive powers of two floating point numbers have the same precision which is 2^e * 2^-24. Around 1 your precision is still 2^-24, but it gets better when you get closer to 0 - when you’re in 0.5…1 it’s 2^-25, when you’re in 0.25…0.5 it’s 2^-26 etc.

So the first step is to use floating point depth. This gives us even more precision really close to camera (where depth is almost 0), and the same amount (24 bits) of precision far away. This alone isn’t interesting - we already had lots of precision close by and not much precision far away, so what’s the point?

Well, the point is that now we can flip the distribution around - instead of using 1 - zn / z, we’ll use zn / z.

With a fixed point distribution this doesn’t help - you have 2^-24 precision everywhere so it’s still get progressively worse the further away you go. But with a floating point distribution, the larger Z gets, the smaller depth becomes but this is precisely where floating point precision distribution shines.

If z is between zn and zn * 2, depth is between 1 and 0.5, with 2^-24 depth precision
If z is between zn * 2 and zn * 4, depth is between 0.5 and 0.25, with 2^-25 depth precision
etc.

Basically your depth precision for z between 2^k and 2^(k+1) is 2^-24 * 2^-k, which means that in terms of world-space units - z - it’s pretty much constant and also very high. And note how in the closest region it’s still not worse than it was - 2^-24. This means that z fighting behavior is uniform across the entire range (nit-pickers: I’m omitting details, it slightly fluctuates). Yay!

The fact that the precision of rendering improves compared to where it used to be if you’re far away from the origin is due to how projective transform works with floating point - basically some internal rasterization computations lose way less precision with the new transform. I’ll leave a description of this for some other time :slight_smile:

30 Likes

I’ve learned more from this post than from being in classes all day. Cheers.

4 Likes

Understood a lot less of that then I hoped I would :frowning:

2 Likes

Hi @opplo, the fix for this is live now.

2 Likes

I just noticed. Thank you very much! Welcome to the forum btw :slight_smile:

2 Likes

Thanks :slight_smile:

2 Likes

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.