[QUOTE=aqnuep;398053]My guess is that due to precision issues or the wrong use of comparison operations your object occludes itself every even frame.
The solution is to make sure you use > vs >= as the culling criteria (as in my sample) and potentially apply a small bias (e.g. BoundingBoxDepth - EPSILON > HiZDepth) to account for any precision issues you might have in your pipe (e.g. due to using a lower precision depth buffer). Also make sure you correctly calculate the max for both the bounding box and the 4 samples from the Hi-Z image.
In addition, you might want to look into your use (or lack of use) of depth clamping and its effect on your algorithm if you’re having problems when the object intersects the near or far plane.[/QUOTE]
It seems I found the bug.
In a test stand I used just box.
I placed the camera very close in front of the box and rotated it to right through 3 degrees.
The culling vertex shader from “Mountains” demo:
#version 400 core
uniform transform {
mat4 ModelViewMatrix;
mat4 ProjectionMatrix;
mat4 InfProjectionMatrix;
mat4 MVPMatrix;
vec4 Viewport;
} Transform;
uniform vec2 Offset;
uniform vec3 Extent;
uniform sampler2D HiZBuffer;
vec4 BoundingBox[8];
subroutine int CullRoutineType(vec3 InstPos);
subroutine(CullRoutineType)
int PassThrough(vec3 InstPos) {
/* always return 1 */
return 1;
}
subroutine(CullRoutineType)
int InstanceCloudReduction(vec3 InstPos) {
/* create the bounding box of the object */
BoundingBox[0] = Transform.MVPMatrix * vec4( InstPos + vec3( Extent.x, Extent.y, Extent.z), 1.0 );
BoundingBox[1] = Transform.MVPMatrix * vec4( InstPos + vec3(-Extent.x, Extent.y, Extent.z), 1.0 );
BoundingBox[2] = Transform.MVPMatrix * vec4( InstPos + vec3( Extent.x,-Extent.y, Extent.z), 1.0 );
BoundingBox[3] = Transform.MVPMatrix * vec4( InstPos + vec3(-Extent.x,-Extent.y, Extent.z), 1.0 );
BoundingBox[4] = Transform.MVPMatrix * vec4( InstPos + vec3( Extent.x, Extent.y,-Extent.z), 1.0 );
BoundingBox[5] = Transform.MVPMatrix * vec4( InstPos + vec3(-Extent.x, Extent.y,-Extent.z), 1.0 );
BoundingBox[6] = Transform.MVPMatrix * vec4( InstPos + vec3( Extent.x,-Extent.y,-Extent.z), 1.0 );
BoundingBox[7] = Transform.MVPMatrix * vec4( InstPos + vec3(-Extent.x,-Extent.y,-Extent.z), 1.0 );
/* check how the bounding box resides regarding to the view frustum */
int outOfBound[6] = int[6]( 0, 0, 0, 0, 0, 0 );
for (int i=0; i<8; i++)
{
if ( BoundingBox[i].x > BoundingBox[i].w ) outOfBound[0]++;
if ( BoundingBox[i].x < -BoundingBox[i].w ) outOfBound[1]++;
if ( BoundingBox[i].y > BoundingBox[i].w ) outOfBound[2]++;
if ( BoundingBox[i].y < -BoundingBox[i].w ) outOfBound[3]++;
if ( BoundingBox[i].z > BoundingBox[i].w ) outOfBound[4]++;
if ( BoundingBox[i].z < -BoundingBox[i].w ) outOfBound[5]++;
}
int inFrustum = 1;
for (int i=0; i<6; i++)
if ( outOfBound[i] == 8 ) inFrustum = 0;
return inFrustum;
}
subroutine(CullRoutineType)
int HiZOcclusionCull(vec3 InstPos) {
/* first do instance cloud reduction */
if ( InstanceCloudReduction(InstPos) == 0 ) return 0;
/* perform perspective division for the bounding box */
for (int i=0; i<8; i++)
BoundingBox[i].xyz /= BoundingBox[i].w;
/* calculate screen space bounding rectangle */
vec2 BoundingRect[2];
BoundingRect[0].x = min( min( min( BoundingBox[0].x, BoundingBox[1].x ),
min( BoundingBox[2].x, BoundingBox[3].x ) ),
min( min( BoundingBox[4].x, BoundingBox[5].x ),
min( BoundingBox[6].x, BoundingBox[7].x ) ) ) / 2.0 + 0.5;
BoundingRect[0].y = min( min( min( BoundingBox[0].y, BoundingBox[1].y ),
min( BoundingBox[2].y, BoundingBox[3].y ) ),
min( min( BoundingBox[4].y, BoundingBox[5].y ),
min( BoundingBox[6].y, BoundingBox[7].y ) ) ) / 2.0 + 0.5;
BoundingRect[1].x = max( max( max( BoundingBox[0].x, BoundingBox[1].x ),
max( BoundingBox[2].x, BoundingBox[3].x ) ),
max( max( BoundingBox[4].x, BoundingBox[5].x ),
max( BoundingBox[6].x, BoundingBox[7].x ) ) ) / 2.0 + 0.5;
BoundingRect[1].y = max( max( max( BoundingBox[0].y, BoundingBox[1].y ),
max( BoundingBox[2].y, BoundingBox[3].y ) ),
max( max( BoundingBox[4].y, BoundingBox[5].y ),
max( BoundingBox[6].y, BoundingBox[7].y ) ) ) / 2.0 + 0.5;
/* then the linear depth value of the front-most point */
float InstanceDepth = min( min( min( BoundingBox[0].z, BoundingBox[1].z ),
min( BoundingBox[2].z, BoundingBox[3].z ) ),
min( min( BoundingBox[4].z, BoundingBox[5].z ),
min( BoundingBox[6].z, BoundingBox[7].z ) ) );
/* now we calculate the bounding rectangle size in viewport coordinates */
float ViewSizeX = (BoundingRect[1].x-BoundingRect[0].x) * Transform.Viewport.y;
float ViewSizeY = (BoundingRect[1].y-BoundingRect[0].y) * Transform.Viewport.z;
/* now we calculate the texture LOD used for lookup in the depth buffer texture */
float LOD = ceil( log2( max( ViewSizeX, ViewSizeY ) / 2.0 ) );
/* finally fetch the depth texture using explicit LOD lookups */
vec4 Samples;
Samples.x = textureLod( HiZBuffer, vec2(BoundingRect[0].x, BoundingRect[0].y), LOD ).x;
Samples.y = textureLod( HiZBuffer, vec2(BoundingRect[0].x, BoundingRect[1].y), LOD ).x;
Samples.z = textureLod( HiZBuffer, vec2(BoundingRect[1].x, BoundingRect[1].y), LOD ).x;
Samples.w = textureLod( HiZBuffer, vec2(BoundingRect[1].x, BoundingRect[0].y), LOD ).x;
float MaxDepth = max( max( Samples.x, Samples.y ), max( Samples.z, Samples.w ) );
/* if the instance depth is bigger than the depth in the texture discard the instance */
return ( InstanceDepth > MaxDepth ) ? 0 : 1;
}
subroutine uniform CullRoutineType CullRoutine;
layout(location = 0) in vec3 InstancePosition;
out vec3 InstPos;
flat out int Visible;
void main(void) {
InstPos.x = InstancePosition.x + Offset.x;
InstPos.y = InstancePosition.y;
InstPos.z = InstancePosition.z + Offset.y;
Visible = CullRoutine(InstPos);
}
Some values from the shader:
- BoundingBox[] after initialization:
v0 (0,33 0,87 0,03 0,03)
v1 (-0,33 0,87 -0,03 -0,02)
v2 (0,33 -0,87 0,03 0,03)
v3 (-0,33 -0,87 -0,03 -0,02)
v4 (0,29 0,87 1,02 1,03)
v5 (-0,36 0,87 0,97 0,97)
v6 (0,29 -0,87 1,02 1,03)
v7 (-0,36 -0,87 0,97 0,97)
As we can see v1 and v3 have negative value because those vertices located behind the camera.
But visible surface of the cube is in front of the camera.
- BoundingBox[] after translation:
the translation:
for (int i=0; i<8; i++)
BoundingBox[i].xyz /= BoundingBox[i].w;
BoundingBox[]:
(11,57 30,75 0,93 0,03)
(13,49 -35,83 1,08 -0,02)
(11,57 -30,75 0,93 0,03)
(13,49 35,83 1,08 -0,02)
(0,28 0,84 1,00 1,03)
(-0,37 0,89 1,00 0,97)
(0,28 -0,84 1,00 1,03)
(-0,37 -0,89 1,00 0,97)
Int the next step the algorithm select min z component of all vertices of translated BoundingBox[].
As we can see v0 and v2 have min z, but the algorithm must use v1 and v3 as we already know.
It happens because v1 and v3 have negative w component.
As I understand the Homogeneous coordinates, z component shouldn’t change the sign.
So, the fix is to make translation of BoundingBox[] in other way:
for (int i=0; i<8; i++) {
BoundingBox[i].xy /= BoundingBox[i].w;
BoundingBox[i].z /= abs(BoundingBox[i].w);
}
Now my app culling objects correctly.
Let me know please, am I right or I just have even number of bugs in my code, now?
Have a good day!