• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
  • entries
    232
  • comments
    1462
  • views
    953200

Tip of the day: logarithmic zbuffer artifacts fix

Sign in to follow this  
Followers 0
Ysaneya

7939 views

Logarithmic zbuffer artifacts fix

In cameni's Journal of Lethargic Programmers, I've been very interested by his idea about using a logarithmic zbuffer.

Unfortunately, his idea comes with a couple of very annoying artifacts, due to the linear interpolation of the logarithm (non-linear) based formula. It particularly shows on thin or huge triangles where one or more vertices fall off the edges of the screen. As cameni explains himself in his journal, basically for negative Z values, the triangles tend to pop in/out randomly.

It was suggested to keep a high tesselation of the scene to avoid the problem, or to use geometry shaders to automatically tesselate the geometry.

I'm proposing a solution that is much more simple and that works on pixel shaders 2.0+: simply generate the correct Z value at the pixel shader level.

In the vertex shader, just use an interpolator to pass the vertex position in clip space (GLSL) (here I'm using tex coord interpolator #6):


void main()
{
vec4 vertexPosClip = gl_ModelViewProjectionMatrix * gl_Vertex;
gl_Position = vertexPosClip;
gl_TexCoord[6] = vertexPosClip;
}

Then you override the depth value in the pixel shader:


void main()
{
gl_FragColor = ...
const float C = 1.0;
const float far = 1000000000.0;
const float offset = 1.0;
gl_FragDepth = (log(C * gl_TexCoord[6].z + offset) / log(C * far + offset));
}

Note that as cameni indicated before, the 1/log(C*far+1.0) can be optimized as a constant. You're only really paying the price for a mad and a log.

Quality-wise, I've found that solution to work perfectly: no artifacts at all. In fact, I went so far as testing a city with centimeter to meter details seen from thousands of kilometers away using a very very small field-of-view to simulate zooming. I'm amazed by the quality I got. It's almost magical. ZBuffer precision problems will become a thing of the past, even when using large scales such as needed for a planetary engine.

There's a performance hit due to the fact that fast-Z is disabled, but to be honnest in my tests I haven't seen a difference in the framerate. Plus, tesselating the scene more or using geometry shaders would very likely cost even more performance than that.

I've also found that to control the znear clipping and reduce/remove it, you simply have to adjust the "offset" constant in the code above. Cameni used a value of 1.0, but with a value of 2.0 in my setup scene, it moved the znear clipping to a few centimeters.

Results

Settings of the test:
- znear = 1.0 inch
- zfar = 39370.0 * 100000.0 inches = 100K kilometers
- camera is at 205 kilometers from the scene and uses a field-of-view of 0.01?
- zbuffer = 24 bits

Normal zbuffer:

http://www.infinity-universe.com/Infinity/Media/Misc/zbufflogoff.jpg


Logarithmic zbuffer:
http://www.infinity-universe.com/Infinity/Media/Misc/zbufflogon.jpg

Future works

Could that trick be used to increase precision of shadow maps ?

0
Sign in to follow this  
Followers 0


13 Comments


Cool, I see I was unnecessarily afraid of the performance hit from disabled fast-z [rolleyes]

Btw. the log-z equation actually utilizes only half the z-buffer range because z/w will lie in [0,1] range and not [-1,1] as OpenGL would like. But like you've said - it's almost magical anyway [smile]
We are also thinking about using it for logarithmic shadow maps, see Logarithmic Perspective Shadow Maps

Thanks!
0

Share this comment


Link to comment
Quote:
Original post by cameni
Cool, I see I was unnecessarily afraid of the performance hit from disabled fast-z [rolleyes]


Yeah. But of course it depends on the application (if the bottleneck is somewhere else, for example bandwidth, it won't show up) and the hardware, so I hesitate to say that it's no problem in general.

I need to do some more serious testing in a "real" scene. Unfortunately my engine isn't in a stage to do this test right now (need to re-enable a lot of the pipeline), but I'm confident the benefits will outweight the costs (if any).

Quote:
Original post by cameni
Btw. the log-z equation actually utilizes only half the z-buffer range because z/w will lie in [0,1] range and not [-1,1] as OpenGL would like. But like you've said - it's almost magical anyway [smile]


True, I forgot that. Couldn't it be solved by doing z = z * 2 - 1 at the end ? The w shouldn't come into play as we're already in the pixel shader.

Quote:
Original post by cameni
We are also thinking about using it for logarithmic shadow maps, see Logarithmic Perspective Shadow Maps


Cool, I'll have a look, thanks.
0

Share this comment


Link to comment
Quote:
Original post by Ysaneya
Quote:
Original post by cameni
Btw. the log-z equation actually utilizes only half the z-buffer range because z/w will lie in [0,1] range and not [-1,1] as OpenGL would like.
True, I forgot that. Couldn't it be solved by doing z = z * 2 - 1 at the end ? The w shouldn't come into play as we're already in the pixel shader.
Actually you've got it right - the [-1,1] range has to be output from vertex shaders, but gl_FragDepth has range [0,1].
0

Share this comment


Link to comment
Yeah, I realized that too (and I tested to make sure, the [0-1] range in the pixel shader is indeed correct :)).
0

Share this comment


Link to comment
By writting to the Z buffer in the pixel shader you lose Hierarchical Z which means more bandwidth usage.

This can get a notorious on bigger screen resolutions and the more pixels are covered by non-overlapping triangles.

You also lose Early Z, which is a shame if you do a lot of overdraw and you were decreasing fillrate by rendering front to back. You lose that advantage.
Early Z is not automatic, so you won't see a difference if you weren't already doing it. Early-Z is great when you're GPU-bound, but makes you more CPU-bound.

So, if you don't have big screen resolutions, bandwidth-limited, don't consume lot of fillrate, and/or do a lot of overdraw, writting to the Z buffer won't hit performance.

Cheers
Dark Sylinc

More info here
0

Share this comment


Link to comment
Yep, I mentionned that in the journal. It's not necessarily a problem though, as it's quite application dependent, for example if your bottleneck isn't in bandwidth. In my tests I haven't been able to notice a drop in framerate using the trick, and that was on a scene with decent overdraw, half a million polys and a pretty intensive pixel shader. But that was on a Radeon HD 4890, so I haven't tested that behavior on other cards/scenes.
0

Share this comment


Link to comment
For applications with significant overdraw losing the hierarchical z can be very painful. (I'd have to say also typically I'd prefer to call log per vertex rather than per pixel)

Perhaps something like this could be done in the vertex shader?


signZ = sign(z);
z = signZ * (log(C * signZ * z + offset) / log(C * far + offset));


Essentially for negative z values inverting the graph given by positive Z values.
Cheers,
Martin
0

Share this comment


Link to comment
Quote:
Original post by Martin
Essentially for negative z values inverting the graph given by positive Z values.
Cheers, Martin


Nope, I already tried that, to symmetrize the function ( with success ) or to use alternative functions, but the problem still remains, you get various popping of triangles as soon as some vertices as behind the camera.
0

Share this comment


Link to comment
Quote:
Original post by Martin
Perhaps something like this could be done in the vertex shader?
signZ = sign(z);

z = signZ * (log(C * signZ * z + offset) / log(C * far + offset));
Essentially for negative z values inverting the graph given by positive Z values

The problem is not so much in the values of Z being negative - lower C linearizes the function well enough so that this would not be problem. Problem is that rasterizer interpolates Z/W and 1/W linearly and computes per-pixel z by dividing the two values. It does this to obtain perspective correct values. The value written to Z-buffer could be a different thing from this all, but actually it's taken from this.
Anyway, the problem is that we are pre-multiplying Z with values of W at the vertices, to get rid of inherent 1/W. Basically then, even when the logarithmic function is linear enough in +-200m range, we are also linearly interpolating between values of W. But since the rasterizer interpolates 1/W, the corresponding values of W are quite different there.

I'm currently using the pixel shader fix only for objects that are close enough to pass through the near plane
0

Share this comment


Link to comment
Hello..

* I don't really understand this log trick with Z: vertex shader has 32bit float precision calculation, z-buffer also has 32-bit precision. If, after matrix multiplication z value already lost necessary precision, doing log here just increases rounding error & nothing more. At the same time, if resulting z value still has enough precision then z-buffer perfectly will store the resulting value. So where is the win here?

* Lack of fast-z optimization appears because you're doing logarithm in pixel shader, right? It causes a confusion for me, because I didn't even imagine that logarithm is being done in pixel shader. What was a reason of using log in PS?

* About multiplying on 'w' - if you don't need w value (you're trying to compensate for the division) then just make it equal to '1', don't need to multiply on 'w' then (& you may also avoid w = 0 suspicious situations).

0

Share this comment


Link to comment
* We are talking about Z-buffer storing integers, not floats. For floating-point Z-buffer there would be no problem, since floats are basically integers with logarithmic part (exponent) [smile]

* Yes, fast-Z problem arises when you use the logarithm in pixel shader, what is done to suppress the artifacts appearing when polygons cross the near plane. You may use the logarithm in pixel shader only for objects crossing the plane, though.

* If you mess with W you will affect perspective correct texturing ...
0

Share this comment


Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now