glDepthRange Madness!

I am building an engine that can live in very large areas. To deal with the limited precesion of the depth buffer, I’ve been partitioning it using glDepthRange

Something along these lines

glDepthRange(0.5,1);
gluPerspective (90, w/h, 6000, 60000);
//Draw Distant Things

glDepthRange(0,0.5)
gluPerspective (90, w/h, 0.5, 6000);
// Draw close things

I am still getting some problems up close to things (intersecting object flickering).

I am guessing that the driver implements this by writing to different parts of the memory locations in the depth buffer, ie.

write 0x0000ffff, instead of 0xffffffff
for the max value in the range 0 … 0.5,
and
write 0xffff0000 instead of 0xffffffff
for the max value in the range 0.5 … 1

This will effectively reduce the accuracy of each partition of the depth buffer, and althought this is better than not having partitioning, it doesn’t give the accuracy that I want.

Does anyone have a suggestion of a better way to do this? Can I use multiple depth buffers instead of partitioning my current one?

Jamie

OpenGL does not partition the depth buffer that way. If you think about it for values besides 0.5, it wouldn’t even be possible to partition the depth buffer that way.

The spec for glDepthRange says that the near clip plane maps to zNear and the far clip plane maps to zFar. What it actually does is treat the depth buffer as a fixed-point number in the range from 0 to 1 inclusive. The sub-range that maps to your znear / zfar values is the range that the depth buffer uses.

Thinking about it a bit more, even if OpenGL did somehow manage to partition your depth buffer that way, your precision would be worse in both ranges, except maybe right at the start of your far range.

The traditional way to stop z-fighting is to push the near clipping plane out as far as possible. You could also try partitioning your z-buffer more, but you will have to be careful about geometry that crosses the boundary between regions.

Would you be better off removing the calls to glDepthRange() and clear the depth buffer between drawing the far objects and drawing the near objects?

You also have to consider the precision of your depth buffer (ie. How many bits?)

An lastly can you draw your near objects with your near plane further out than 0.5?

Originally posted by Coriolis:
[b]OpenGL does not partition the depth buffer that way. If you think about it for values besides 0.5, it wouldn’t even be possible to partition the depth buffer that way.

The spec for glDepthRange says that the near clip plane maps to zNear and the far clip plane maps to zFar. What it actually does is treat the depth buffer as a fixed-point number in the range from 0 to 1 inclusive. The sub-range that maps to your znear / zfar values is the range that the depth buffer uses.[/b]

Doesn’t this amount to the same thing?

So when an object is in the furthest part of the close partition(0.5 … 6000), it’s value of 6000 would be mapped to 0.5 instead of the 1.0 value that it would get if there were no partitioning. These values are then mapped to the buffers integer (if it is an integer buffer). In the case of a gforce it would be half the value of the 24 bit buffer depth.

Either way the partitioning will give you less accuracy than if there had been no partitioning.

I tested this by making really small partitions, and seeing that the depth artifacts got worse as the partitions got smaller.

I’m also thinking of drawing far objects, clearing the depth buffer, then drawing the close objects. Like you said though, there will be problems in the boundrys.

Most depth buffers are 24 bits. The other 8 bits are reserved for the stencil buffer. In this setup, a depth value in the 0-1 range would get multiplied by (2^24 - 1) and then rounded to the nearest int before being stored in the depth buffer (the actual rounding scheme may differ in hardware, but it doesn’t matter too much). So, a depth value of 0.5 maps to 0x007FFFFF or 0x00800000, depending on rounding. A depth value of 0 maps to 0x00000000, and a depth value of 1 maps to 0x00FFFFFF. So, depth range from 0 to 0.5 would use 0x00000000 to 0x007FFFFF, and from 0.5 to 1 would use 0x00800000 to 0x00FFFFFF (again, the exact boundaries may be off by 1 due to rounding).

Each range gets effectively 23 bits of precision. If it was the partitioning scheme you suggested, each range would get only 12 bits of precision, a very different value. Also, under your suggested scheme, you can only partition the depth buffer into fractions of the number of depth buffer bits (ie, in a multiple of 1/24). If you divide the depth buffer into 24 regions, each region would get only a single bit of precision. Under the scheme actually used, each region gets about 19.4 bits of precision, which again is much better.

I bet if you change the split point from 6000 to 1208 your problems will mostly disappear.

The normalized depth value in the range [-1, 1] of a given distance into the screen is given by:
(f + n) / (f - n) - 1/z * 2 f n / (f - n),
where f = far clip plane distance, n = near clip plane distance, and z = distance into the screen.

If you want to map that to the range [A, B], the equation becomes:
D = A + f / (f - n) * (B - A) * (1 - n / z)
If a is the value you give for the near depth range and b is the value you give for the far depth range, and you have S bits in your depth buffer, then A = a * (2^S - 1), B = b * (2^S - 1), and D = the actual integer value stored in the depth buffer.

You can simplify these equations to find the minimum and maximum distinguishable depth values for any combination of near clip plane, far clip plane, and depth buffer precision. In other words, this is how far apart two pixels have to be in screen depth for them to be guaranteed to have different depth values.

The minimum value for minimum distance occurs when D = A + 1, ie just past the near clip plane. The minimum separation at the near clip plane for distinguishable depth values is:
n / [ f/(f-n) * (B-A) - 1 ]

The maximum value for minimum distance occurs when D = B - 1, ie just before the far clip plane. The minimum separation at the far clip plane for distinguishable depth values is:
f / [ n/(f-n) * (B-A) - 1 ]

If you plug the values for n and f in your sample code, and assume a 24-bit depth buffer, you find that your minimum separation ranges from 5.9610^-8 to 8.59 for the near region, and it ranges from 6.4410^-4 to 0.0644 for the far region. If you use a split at 1208 instead, your minimum separation in the near region will range from 5.9610^-8 to 0.348, and in the far region it will range from 1.4110^-4 to 0.348.

The best results happen when the maximum value for minimum distinguishable distance for the near region and far regions are identical. If you solve the set of equations for where the split x should be given a near distance n, far distance f, and a depth buffer that is to be divided into two equal regions each having S unique values, you get the cubic equation:
x^3 - x^2 * [n + 2 * f / S] + x * [n * f + (n + f) * f / S] - n * f * f = 0

I plugged the values for n, f, and S of 0.5, 60000, and 0.5 * 2^24, respectively, to get the coefficients for the cubic. I then told my calculator to solve for x, and it said 1208.2725, which is close enough to 1208 for me.

[This message has been edited by Coriolis (edited 11-28-2002).]

Oops, forgot to post how to make sure you don’t get problems at the boundary. If you use a slightly smaller value for the far depth value in the near region compared to the near depth value in the far region, you will be fine. Assuming the clipper does it’s job perfectly, that is I would suggest using 0.5 - 1/S for the far depth value of the near region and 0.5 + 1/S for the near depth value of the far region, where S is the number of unique values in the depth buffer.

[This message has been edited by Coriolis (edited 11-28-2002).]

>>gluPerspective (90, w/h, 6000, 60000);
//Draw Distant Things

glDepthRange(0,0.5)
gluPerspective (90, w/h, 0.5, 6000); <<

dont know if this got posted already but

6000->60,000 = 10x differnece
0.5->6000 = 12,000x!!

yet u use 0->0.5 + 0.5->1.0

btw 0.5->6000 will have depth fighting problems in 16bit depth check the faq for an explantion

Zed, that approximation breaks down quickly when the near clip plane gets away from 0. Using that estimate, you’d want the split between regions to be at 173, but if you go through the effort of working out all the math you find that the ideal split is closer to 1208.

Originally posted by Coriolis:
[b]I bet if you change the split point from 6000 to 1208 your problems will mostly disappear.

The normalized depth value in the range [-1, 1] of a given distance into the screen is given by:
(f + n) / (f - n) - 1/z * 2 f n / (f - n),
where f = far clip plane distance, n = near clip plane distance, and z = distance into the screen.

If you want to map that to the range [A, B], the equation becomes:
D = A + f / (f - n) * (B - A) * (1 - n / z)
If a is the value you give for the near depth range and b is the value you give for the far depth range, and you have S bits in your depth buffer, then A = a * (2^S - 1), B = b * (2^S - 1), and D = the actual integer value stored in the depth buffer.

You can simplify these equations to find the minimum and maximum distinguishable depth values for any combination of near clip plane, far clip plane, and depth buffer precision. In other words, this is how far apart two pixels have to be in screen depth for them to be guaranteed to have different depth values.

The minimum value for minimum distance occurs when D = A + 1, ie just past the near clip plane. The minimum separation at the near clip plane for distinguishable depth values is:
n / [ f/(f-n) * (B-A) - 1 ]

The maximum value for minimum distance occurs when D = B - 1, ie just before the far clip plane. The minimum separation at the far clip plane for distinguishable depth values is:
f / [ n/(f-n) * (B-A) - 1 ]

If you plug the values for n and f in your sample code, and assume a 24-bit depth buffer, you find that your minimum separation ranges from 5.9610^-8 to 8.59 for the near region, and it ranges from 6.4410^-4 to 0.0644 for the far region. If you use a split at 1208 instead, your minimum separation in the near region will range from 5.9610^-8 to 0.348, and in the far region it will range from 1.4110^-4 to 0.348.

The best results happen when the maximum value for minimum distinguishable distance for the near region and far regions are identical. If you solve the set of equations for where the split x should be given a near distance n, far distance f, and a depth buffer that is to be divided into two equal regions each having S unique values, you get the cubic equation:
x^3 - x^2 * [n + 2 * f / S] + x * [n * f + (n + f) * f / S] - n * f * f = 0

I plugged the values for n, f, and S of 0.5, 60000, and 0.5 * 2^24, respectively, to get the coefficients for the cubic. I then told my calculator to solve for x, and it said 1208.2725, which is close enough to 1208 for me.

[This message has been edited by Coriolis (edited 11-28-2002).][/b]

Thanks for the well thought out explaination. I had not really thought it through. I see now that 0xffffff / 2 is not 0x000fff. Thoughtless on my part, I really did know this

I’m currently working through the math in you equations.

I do have a problem with the 1208 size though. The problem is that some objects actually are 6000 wide. The smallest ones can be around 1.

Jamie

Any objects that cross the split-plane boundary will have to be rendered twice, once in each region. There is no way around this. Making sure that no objects cross the boundary will make it so you don’t have to draw anything twice, but it just avoids the problem case .