Where is my frustum culling broken?

Started by
6 comments, last by _WeirdCat_ 9 years, 1 month ago
I'm trying to implement frustum culling, however I can't figure out where/how it's breaking.

Frustum culling on
How it should look

It's almost like it's rendering the opposite of what is expected, however that's not constantly the case. It's highly dependent on the view rotation and position.

Frustum code

Frustum viewFrustum(renderContext.projectionMatrix()*renderContext.viewMatrix());
Frustum::Frustum(const glm::mat4& matrix)
{
    // Extract the frustum faces from the projection*view matrix
    // http://web.archive.org/web/20120531231005/http://crazyjoke.free.fr/doc/3D/plane%20extraction.pdf
    mPlanes[Face::LEFT].n.x = matrix[3][0] + matrix[0][0];
    mPlanes[Face::LEFT].n.y = matrix[3][1] + matrix[1][0];
    mPlanes[Face::LEFT].n.y = matrix[3][2] + matrix[2][0];
    mPlanes[Face::LEFT].d = matrix[3][3] + matrix[3][0];

    mPlanes[Face::RIGHT].n.x = matrix[3][0] - matrix[0][0];
    mPlanes[Face::RIGHT].n.y = matrix[3][1] - matrix[1][0];
    mPlanes[Face::RIGHT].n.y = matrix[3][2] - matrix[2][0];
    mPlanes[Face::RIGHT].d = matrix[3][3] - matrix[3][0];

    mPlanes[Face::DOWN].n.x = matrix[3][0] + matrix[0][1];
    mPlanes[Face::DOWN].n.y = matrix[3][1] + matrix[1][1];
    mPlanes[Face::DOWN].n.y = matrix[3][2] + matrix[2][1];
    mPlanes[Face::DOWN].d = matrix[3][3] + matrix[3][1];

    mPlanes[Face::UP].n.x = matrix[3][0] - matrix[0][1];
    mPlanes[Face::UP].n.y = matrix[3][1] - matrix[1][1];
    mPlanes[Face::UP].n.y = matrix[3][2] - matrix[2][1];
    mPlanes[Face::UP].d = matrix[3][3] - matrix[3][1];

    mPlanes[Face::FAR].n.x = matrix[3][0] - matrix[0][2];
    mPlanes[Face::FAR].n.y = matrix[3][1] - matrix[1][2];
    mPlanes[Face::FAR].n.y = matrix[3][2] - matrix[2][2];
    mPlanes[Face::FAR].d = matrix[3][3] - matrix[3][2];

    mPlanes[Face::NEAR].n.x = matrix[3][0] + matrix[0][2];
    mPlanes[Face::NEAR].n.y = matrix[3][1] + matrix[1][2];
    mPlanes[Face::NEAR].n.y = matrix[3][2] + matrix[2][2];
    mPlanes[Face::NEAR].d = matrix[3][3] + matrix[3][2];

    for (int i = 0; i < 6; i++)
    {
        mPlanes[i].normalize();
    }
}

bool Frustum::sphereIntersects(const glm::vec3& pos, float radius)
{
    // cheack against each plane
    for (int i = 0; i < 6; i++)
    {
        if (glm::dot(mPlanes[i].n, pos) + mPlanes[i].d + radius <= 0)
        {
            return false;
        }
    }

    return true;
}
Where I check each point

            glm::vec4 point = renderContext.projectionMatrix()*renderContext.viewMatrix() * 
                glm::vec4((float)x*World::CHUNK_SIZE, (float)c*World::CHUNK_SIZE, (float)z*World::CHUNK_SIZE, 0);

            
            if (!viewFrustum.sphereIntersects(glm::vec3(point.x, point.y, point.z), 16.f))
            {
                continue;
            }
Advertisement
Could be a lot of things. Some suggestions:
- debug to check if the sphere center/ world positions and radius are ok
(maybe temporarily use a higher/ static radius and see what happens)
- render just 1 or a few cubes/ objects and see what happens (is it in the frustum/ checking code or maybe in the object transformations)

Crealysm game & engine development: http://www.crealysm.com

Looking for a passionate, disciplined and structured producer? PM me

EDIT: Looks like I had some errors in my matrix calculation for the frustum. My current issue is still if I rotate the camera upward (+y) it culls what should be visible. If the camera is horizontal (y ~= 0) or if the camera is rotated downward (-y) it renders as expected.

Latest code: https://github.com/rcapote/Survivox/tree/master/src

Original text:

I did some more research and testing, still no success but here is what I have gathered. I also fixed the issue where I was setting y twice o.o

Multiplying the point by the proj*view before testing it against the frustum is incorrect. According to the examples I've seen, I should be able to just pass in the world position of the test point...I think. I've removed that from my code.

It seems as if the problem is that coordinate spaces are incorrect, such that the point I'm testing against is different from where it's getting rendered. The culling seems to happens with a small area near the origin, instead of the position that the meshes are being rendered at. I can't pin-point this, however. The position I'm rendering and the position I'm testing are exactly the same, save the proj*view transform. My guess would be that multiplying the test point against the matrix would solve the issue...but it only exacerbates it. I've tried increasing the testing positions by several magnitudes, virtually no difference in results.

Another symptom is that if I look "downward" (towards -y) there is no culling, however looking up causes aggressive culling. This only happens from one direction (+x, +z, looking down -z). If I move to the other end and look down +z...the opposite is true: looking up causes no culling, while looking down causes aggressive culling.

Updated code:

Frustum::Frustum(const glm::mat4& matrix)
{

    
    // Extract the frustum faces from the projection*view matrix
    // http://web.archive.org/web/20120531231005/http://crazyjoke.free.fr/doc/3D/plane%20extraction.pdf
    // GLM is column major
    mPlanes[Face::LEFT].n.x = matrix[0][3] + matrix[0][0];
    mPlanes[Face::LEFT].n.y = matrix[1][3] + matrix[1][0];
    mPlanes[Face::LEFT].n.z = matrix[2][3] + matrix[2][0];
    mPlanes[Face::LEFT].d = matrix[3][3] + matrix[3][0];

    mPlanes[Face::RIGHT].n.x = matrix[0][3] - matrix[0][0];
    mPlanes[Face::RIGHT].n.y = matrix[1][3] - matrix[1][0];
    mPlanes[Face::RIGHT].n.z = matrix[2][3] - matrix[2][0];
    mPlanes[Face::RIGHT].d = matrix[3][3] - matrix[3][0];

    mPlanes[Face::DOWN].n.x = matrix[0][3] + matrix[0][1];
    mPlanes[Face::DOWN].n.y = matrix[1][3] + matrix[1][1];
    mPlanes[Face::DOWN].n.z = matrix[2][3] + matrix[2][1];
    mPlanes[Face::DOWN].d = matrix[3][3] + matrix[3][1];

    mPlanes[Face::UP].n.x = matrix[0][3] - matrix[0][1];
    mPlanes[Face::UP].n.y = matrix[1][3] - matrix[1][1];
    mPlanes[Face::UP].n.z = matrix[2][3] - matrix[2][1];
    mPlanes[Face::UP].d = matrix[3][3] - matrix[3][1];

    mPlanes[Face::FAR].n.x = matrix[0][3] - matrix[0][2];
    mPlanes[Face::FAR].n.y = matrix[1][3] - matrix[1][2];
    mPlanes[Face::FAR].n.z = matrix[2][3] - matrix[2][2];
    mPlanes[Face::FAR].d = matrix[3][3] - matrix[3][2];

    mPlanes[Face::NEAR].n.x = matrix[0][3] + matrix[0][2];
    mPlanes[Face::NEAR].n.y = matrix[1][3] + matrix[1][2];
    mPlanes[Face::NEAR].n.z = matrix[2][3] + matrix[2][2];
    mPlanes[Face::NEAR].d = matrix[3][3] + matrix[3][2];

    for (int i = 0; i < 6; i++)
    {
        mPlanes[i].normalize();
        
    }
}

bool Frustum::sphereIntersects(const glm::vec3& pos, float radius)
{
    // cheack against each plane
    for (int i = 0; i < 6; i++)
    {
        
        if (glm::dot(pos, mPlanes[i].n) + mPlanes[i].d + radius <= 0.f)
        {
            return false;
        }
    }

    return true;
}
            glm::vec4 point = /*renderContext.projectionMatrix()*renderContext.viewMatrix() */
                glm::vec4((float)x*World::CHUNK_SIZE, 
                          (float)c*World::CHUNK_SIZE, 
                          (float)z*World::CHUNK_SIZE, 1);

            
            if (!viewFrustum.sphereIntersects(glm::vec3(point.x, point.y, point.z), 16.f))
            {
                continue;
            }

i would suggest using npc coordinates

like (model * view * projection ) * actual vertex

then scale it up to 0..1 by multiplying the result vertex by 0.5 and and adding 0.5 to it

and etc. (everything outside range 0..1 is not in the frustum)

here is the code i use i cannot say if its purely valid but anyway it doesnt fix the problem in 100% coz if you see something that has vertices outside the view frustumbut its viisble it will tell you that you dont see it etc

code...






t4dpoint<T> operator*(const t3dpoint<T> p) const
{
t4dpoint<T> vertexPos;
vertexPos.x = p.x;
vertexPos.y = p.y;
vertexPos.z = p.z;
vertexPos.w = 1.0;

t4dpoint<T> vertexClip;
t4dpoint<T> matrow;

matrow.x = m[0]; matrow.y = m[1]; matrow.z = m[2]; matrow.w = m[3];
vertexClip.x = dp4(matrow, vertexPos);

matrow.x = m[4]; matrow.y = m[5]; matrow.z = m[6]; matrow.w = m[7];
vertexClip.y = dp4(matrow, vertexPos);

matrow.x = m[8]; matrow.y = m[9]; matrow.z = m[10]; matrow.w = m[11];
vertexClip.z = dp4(matrow, vertexPos);

matrow.x = m[12]; matrow.y = m[13]; matrow.z = m[14]; matrow.w = m[15];
vertexClip.w = dp4(matrow, vertexPos);

vertexClip.x = vertexClip.x / vertexClip.w;
vertexClip.y = vertexClip.y / vertexClip.w;
vertexClip.z = vertexClip.z / vertexClip.w;


return vertexClip;
}




template <class type> type __fastcall dp4(t4dpoint<type> vVector1, t4dpoint<type> vVector2)
{
return ( (vVector1.x * vVector2.x) + (vVector1.y * vVector2.y) + (vVector1.z * vVector2.z) + (vVector1.w * vVector2.w) );
}
Interesting....that seemed to have fixed all my problems. Resulting code:


            glm::vec4 point = renderContext.projectionMatrix()*renderContext.viewMatrix() *
                glm::vec4((float)x*World::CHUNK_SIZE+8, 
                          (float)c*World::CHUNK_SIZE-8, 
                          (float)z*World::CHUNK_SIZE+8, 1);

            point = (glm::normalize(point)) + glm::vec4(0.5f);

            Frustum viewFrustum(renderContext.projectionMatrix()*renderContext.viewMatrix()*transform);

            if (viewFrustum.sphereIntersects(glm::vec3(point.x, point.y, point.z), 14.f))

i dont know how you managed to write that code and its not like what i wrote anyway if it works i am happy :P

Screenshots

Yea, I have no idea what I was doing. It's working, most of the meshes are being culled, but I think there are too many false positives. For example, I'm looking at a section that should only be rendering 4 meshes...but it's rendering around ~20. That's out of 200 meshes in the scene, so it's a significant improvement. That 14.f is the minimum radius such that the culling isn't visible on the screen.

Basically I took what you said about multiplying the point by the matrices, bringing it down to 0...1 range and checking if it's within that range. That was an improvement on what I had before. It only rendered where the camera of looking, which was much better than what I had before...however it was only rendering a small number of meshes in that direction...and none of the meshes that were in the peripheral vision.

So I just started poking around, changing values, moving stuff around...until I got the above which seems to be working decently enough, but I don't think it's strictly correct. I'm still open to alternative fixes

well to be honest, calculating these frustum planes is a little bit nasty i cant remember how exactly to do this but all you need is to have fov angle aspect ratio, and front right and up directions

by aspect ratio you calculate far plane height (and width is calculated by arctg(dinstance from camera to farplane/ far_plane_width_div2) = fov/2 or was it arcctg i cant remember

then you multiply it by aspect or divide in the matter how did you make aspect w/h or h/w

you then use vector math to multiple front up right vectors by proper lengths and you make planes out of it

then additionally you will have to face all these planes inside or outside frustum then to actually find that case with sphere center outside of the frustum you will have to i think firstly check if sphere distance rom a planes is less than sphere radius and then you will have to make additional test to see if (if sphere is in one side for all planes except that testing plane) sphere is actually in the frusutm 'cone'

this first code of yours has the same bug like that mvp * vertex code that means you are using different 'modelspace' where 14 is not the disance thats a real nasty stuff i am not aware of it surely can be made but it requires few more computations because if sphere is in straight distance from camera (lets say X ) and another is in X2 then calculated distances from left frustum plane will differ so even when both are with distance from 7 this doesnt say if they will have both distance -0.3 from the left plane but wait i dont know iam just thinking out loud they could have such distance both but i dont know.

so you are not calculating frustum planes in worldspace only in mvp space which is wrong (for that first code)

oh and if you manage to write that what i just said i think you will have to use only ViewMatrix (without any translation- just simple rotation matrix) (since camera position is a shared vertex for all frustum planes)

This topic is closed to new replies.

Advertisement