5 ms·
Reverse Z in 3D graphics (and why it's so awesome)
- GenerocUsername 2y agoArticles about graphics and rendering without a single image should be a misdemeanor. I know it's not explicitly about any particular graphic and instead about performance, but I still think it's missing that extra pizaz
- tonetheman 2y agothis you are completely correct. just anything to show me why this is awesome instead of just the math!
- AlunAlun 2y agoI disagree. Anyone with a basic understanding of how perspective projection matrices work will understand the article, especially with the code samples and equations. (Edit: and anybody without this understanding won’t be interested in the article anyway). I thought it was great, and had a full-on “smack my forehead” moment as to why I had never realized something so simple and effective, after 20 years of programming 3D graphics!
- qingcharles 2y agoLOL. Me too. 35 years here. There's always something new to learn. This, coupled with all the fascinating new ideas on how to optimize 3D math has made me determined to go rewrite my old x86 code.
- deleted 2y ago[deleted]
- Keyframe 2y agooh, my. Back in the day, when learning graphics from books, there were some with equations and matrixes only. Imagine that! How did we learn at all in such savage environment?!
- qarl 2y agoTLDR - floating point format has a great deal of precision near 0. Normally depth maps range from -1 to 1, but if you change that to 0 to 1 (and flip it) you can utilize this extra precision.
- karmakaze 2y agoI couldn't quite connect all the dots as to why this is better. Something I do understand is summing numbers close to zero before numbers of larger magnitude away from zero produces a more accurate sum because floating-point addition isn't truly associative. My best guess is that this reverse-z convention keeps numbers at the same scale more often. I think it's important about the same scale rather than near zero because the relative precision is the same at every scale (single precision float: 24 bits stored in 23 bits with implied leading 1 bit). If the article is trying to say that numbers near zero have more relative precision because of denormalized representation of FP numbers it should call that out explicitly. Also the advantage of similar scales is for addition/subtraction there should be no advantage for multiplication/division AFAIK.
- nox101 2y agohttps://developer.nvidia.com/content/depth-precision-visualized https://developer.nvidia.com/content/depth-precision-visuali...
- djmips 2y agoThis article is very good
- nwellnhof 2y agoThis is the key paragraph: "The reason for this bunching up of values near 1.0 is down to the non linear perspective divide..."
- karmakaze 2y agoThat makes sense: many values close to 1.0 then transforming to represent that as 0.0 provides additional precision.
- deleted 2y ago[deleted]
- Ono-Sendai 2y agoI use reverse-z when I can, works great. Totally solves all z-fighting problems.
- tylerneylon 2y agoIf you’re wondering why there’s no demo graphic, it’s because this idea is meant to produce correct results (in a bug-robust way), not anything different. One thing that can go wrong in 3d graphics is z-fighting, where a scene has rendering artifacts because two objects are touching or intersect each other, and the triangles that are close to each other look bad when their z values (distance from the camera) hit machine tolerance differences. Basically the rendering has errors due to floating point limitations. The post is pointing out that the float32 format represents many, many more values near 0 than near 1. Over 99% of all values represented in [0,1] are in [0,0.5]. And when you standardize the distances, it’s common to map the farthest distance you want to 1 and the nearest to 0. But this severely restricts your effective distances for non-close objects, enough that z-fighting becomes possible. If you switch the mapping so that z=0 represents the farthest distance, then you get many, many more effective distance values for most of your rendered view distance because of the way float32 represents [0,1].
- ajconway 2y agoThere is also a technique called logarithmic depth buffer (which should be self-explanatory): https://threejs.org/examples/?q=dept#webgl_camera_logarithmicdepthbuffer https://threejs.org/examples/?q=dept#webgl_camera_logarithmi...
- rendaw 2y agoThat's a pretty stunning visualization too
- indigoabstract 2y agoI wasn't aware that logarithmic depth buffer could be implemented in WebGL since it lacks glClipControl(). It's cool that someone found a way to do it eventually (apparently by writing to gl_FragDepth).
- Const-me 2y ago> apparently by writing to gl_FragDepth If they do that, this disables early Z rejection performance optimization implemented in most GPUs. For some scenes, the performance cost of that can be huge. When rendering opaque objects in front-to-back order, early Z rejection sometimes saves many millions of pixel shader calls per frame.
- jandrese 2y agoI'm not a graphics expert, but reading the article and the long roundabout way they got back to "we're just working with the 23 bit mantissa because we've clamped down our range" makes me wonder why you couldn't just do fixed point math with the full 32 bit range instead?
- NBJack 2y agoModern GPUs aren't optimized for it. You can indeed do this in software, but this is the path hardware acceleration took.
- cmovq 2y agoIt’s actually quite common to use a 24bit fixed point format for the depth buffer, leaving 8 bits for the stencil buffer. GPUs do a lot of fixed point to float conversions for the different texture and vertex formats since memory bandwidth is more expensive than compute.
- Jasper_ 2y agoPlenty of GPUs do have special (lossless) depth compression; that's what the 24-bit depth targets are about.
- Dylan16807 2y agoOkay but now all your values are bunched up next to the far plane, between 990 and 1000? Using this kind of mapping seems inherently flawed no matter what range you pick.
- gary_0 2y agoNo, because depth values are in screen space (1/z), not world space (like 990-1000). Here are some graphs that make the benefits obvious: https://developer.nvidia.com/blog/visualizing-depth-precision/ https://developer.nvidia.com/blog/visualizing-depth-precisio...
- Dylan16807 2y agoThose graphs are inaccurate. They only show a few levels of exponent for the floating point. There is a benefit, but it's overstated by those graphs. The math is simple enough. When you calculate 1/990-ish, it comes out to about 0.001. Everything below about 0.001 is between 990 and the far plane. Now, of the floating point values between 0 and 1, what percent of them are below 0.001? It's 91.4%. Only 9% of those values are being used. And only 1/4 of floating point is between 0 and 1 to start with. Floating point is just not the right way to store these values. It makes sense to have denser values near 0, but the exponential nature of floating point does that to an extremely overkill extent.
- kazinator 2y agoThe thing to understand is that the perspective transform in homogeneous coordinates doesn't just shrink the X and Y coordinates based on Z distance, but it also shrinks the Z coordinate too! The farther some objects are from the viewer, the closer together they are bunched in Z space. Basically, the points out at infinity where parallel lines appear to converge under perspective are translated to a finite Z distance. So all of infinity is squeezed into that range. Why is Z also transformed? In order to keep the transformation affine: to keep the correctness of straight lines under the perspective transformation. If you have an arbitrarily rotated straight line in a perspective view, and you just divide the X and Y coordinates along that line by Z, the line will look just fine in the perspective view, because the viewer does not see the Z coordinate. But in fact in the perspective-transformed space (which is 3 dimensional), it will not be a straight line. Now that is fine for wireframe or even certain polygon graphics, in which we can regard the perspective as a projection from 3D to 2D and basically drop the Z coordinate entirely after dividing X and Y by it. An example of where it breaks down is when you're using a Z-buffer for the visibility calculations. If you have two polygons that intersect, and you don't handle the Z coordinate in the perspective transform, they will not correctly intersect: the line at which they intersect, calculated by your Z buffer, will not be the right one, and that will be particularly obvious under movement, appearing as a weird artifact where that intersecting line shifts around. You won't see this when you have polygons that only join at their vertices to form surfaces. The 2D transform is good enough: the endpoints of a shared edge are correctly transformed from 3D into 2D taking into account perspective, and we just render a straight line between them in 2D and all is good. In the intersecting case, there are interpolated points involved which are not represented as vertices and have not been individually transformed. That interpolation then goes wrong. Another area where this is important, I think, is correctness of texture maps. If it is wrong, then a rotating polygon with a texture map will have creeping artifacts, where the pixels of the texture don't stick to the same spot on the polygon, except near the vertices.