zeboidlund
zeboidlund

Reputation: 10137

View frustum culling questions

So, I'm trying to implement frustum culling. The thing here is that before I can do that, I need to understand some things.

First of which, is plane intersection:

My understanding is that a plane can be defined via 3 points; let's call them p0, p1, and p2.

Given that, we know that the plane's normal can be computed as follows:

(psuedo-code)

vec3 edge0 = p1 - p0;
vec3 edge1 = p2 - p0;

vec3 normal = normalize( cross( edge0, edg1 ) ) // => edge1 X edge2 / length( edge1 X edge2 );

Now, let's say we have a function which basically tells us whether or not a given point "crosses" the plane in some way.

(moar pseudo-code)

bool crossesPlane( vec3 plane[3], vec3 point )
{
    vec3 normal = getNormal( plane ); // perform same operations as described above
    float D = dot( -normal, plane[ 0 ] ); // plane[ 0 ] is arbitrary - could just as well be the 2nd or 3rd point in the array

    float dist = dot(normal, point) + D; 

    return dist >= 0; // dist < 0 means we're on the opposite side of the plane's normal. 
}

The logical reasoning behind this is that, since the view-frustum contains six separate planes ( near, far, left, up, right, bottom ), we'd want to grab each of these six planes and essentially "pass" them to the crossesPlane() function, for an individual point (one point, six tests for that point).

If one of these six calls to crossesPlane() returns false, then we want to cull the point in question, thus effectively resulting in the point being culled by the frustum, and of course the point won't be rendered.

Questions

Note

If there are any implementation differences worth mentioning between D3D and OpenGL for this, it would be certainly appreciated.

Upvotes: 4

Views: 7584

Answers (3)

jackw11111
jackw11111

Reputation: 1547

Outline:

defining indexes of vertices and edges of a cube Here is a minimal working example adapted from a basic learnopengl tutorial.

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
#include <vector>

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <learnopengl/shader_m.h>

#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void mouse_callback(GLFWwindow* window, double xpos, double ypos);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
void processInput(GLFWwindow *window);

// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

// camera
glm::vec3 cameraPos   = glm::vec3(0.0f, 0.0f, 3.0f);
glm::vec3 cameraFront = glm::vec3(0.0f, 0.0f, -1.0f);
glm::vec3 cameraUp    = glm::vec3(0.0f, 1.0f, 0.0f);
glm::mat4 projection;
glm::mat4 view;

bool firstMouse = true;
float yaw   = -90.0f;   // yaw is initialized to -90.0 degrees since a yaw of 0.0 results in a direction vector pointing to the right so we initially rotate a bit to the left.
float pitch =  0.0f;
float lastX =  800.0f / 2.0;
float lastY =  600.0 / 2.0;
float fov   =  45.0f;

// timing
float deltaTime = 0.0f; // time between current frame and last frame
float lastFrame = 0.0f;


float pointPlaneDistance(const glm::vec3 &point, const glm::vec3 &planePosition, const glm::vec3 &planeNormal);

bool isMinkowskiFace(const glm::vec3 &a, const glm::vec3 &b, const glm::vec3 &b_x_a, const glm::vec3 &c, const glm::vec3 &d, const glm::vec3 &d_x_c);

class Edge {
public:
    glm::vec3 v0;
    glm::vec3 v1;
    std::vector<int> face_indices;
    Edge(const glm::vec3 &v0, const glm::vec3 &v1);
};

class Face {
public:
    std::vector<glm::vec3> vertices;
    glm::vec3 position;
    glm::vec3 normal;
    Face(const glm::vec3 &v0, const glm::vec3 &v1, const glm::vec3 &v2);
};

class Body {
public:
    glm::vec3 cm_position;
    std::vector<glm::vec3> vertices;
    std::vector<Edge> edges;
    std::vector<Face> faces;

    Body();

    int update(const std::vector<glm::vec3> &vertices);
};

class Query {
public:
    float max_seperation;
    std::vector<int> max_index;
    glm::vec3 best_axis;
    int type;
    Query(float max_seperation, std::vector<int> best_index, glm::vec3 best_axis) {
        this->max_seperation = max_seperation;
        this->max_index = max_index;
        this->best_axis = best_axis;
    }
};

std::vector<Query> SAT(const Body &hullA, const Body &hullB);

float pointPlaneDistance(const glm::vec3 &point, const glm::vec3 &planePosition, const glm::vec3 &planeNormal) {
    return glm::dot(point - planePosition, planeNormal);
}

bool isMinkowskiFace(const glm::vec3 &a, const glm::vec3 &b, const glm::vec3 &b_x_a, const glm::vec3 &c, const glm::vec3 &d, const glm::vec3 &d_x_c) {
    float cba = glm::dot(c, b_x_a);
    float dba = glm::dot(d, b_x_a);
    float adc = glm::dot(a, d_x_c);
    float bdc = glm::dot(b, d_x_c);
    return cba * dba < 0.0f && adc * bdc < 0.0f && cba * bdc > 0.0f;
}

Face::Face(const glm::vec3 &v0, const glm::vec3 &v1, const glm::vec3 &v2) {
    this->vertices = {v0, v1, v2};
    this->position = v0;
    this->normal = glm::normalize(glm::cross(v0 - v2, v1 - v0));
}

Edge::Edge(const glm::vec3 &v0, const glm::vec3 &v1) {
    this->v0 = v0;
    this->v1 = v1;
}

Body::Body() {
        this->vertices = {};
        this->edges = {};
        this->faces = {};
        this->cm_position = glm::vec3(0,0,0);
}
// in world space
int Body::update(const std::vector<glm::vec3> &v) {

    this->faces = {
        Face(v.at(0), v.at(1), v.at(2)),
        Face(v.at(4), v.at(5), v.at(1)),
        Face(v.at(1), v.at(5), v.at(6)),
        Face(v.at(3), v.at(2), v.at(6)),
        Face(v.at(7), v.at(4), v.at(0)), 
        Face(v.at(7), v.at(6), v.at(5)),                      
    };

    this->edges = {
        Edge(v.at(0), v.at(1)),
        Edge(v.at(1), v.at(2)),
        Edge(v.at(2), v.at(3)),
        Edge(v.at(3), v.at(0)),
        Edge(v.at(4), v.at(5)),
        Edge(v.at(5), v.at(6)),
        Edge(v.at(6), v.at(7)),
        Edge(v.at(7), v.at(4)), 
        Edge(v.at(4), v.at(0)),
        Edge(v.at(5), v.at(1)),
        Edge(v.at(6), v.at(2)),
        Edge(v.at(7), v.at(3)),
    };

    this->edges[0].face_indices = {0, 1};
    this->edges[1].face_indices = {0, 2};
    this->edges[2].face_indices = {0, 3};
    this->edges[3].face_indices = {0, 4};
    this->edges[4].face_indices = {1, 5};
    this->edges[5].face_indices = {2, 5};
    this->edges[6].face_indices = {3, 5};
    this->edges[7].face_indices = {4, 5};
    this->edges[8].face_indices = {1, 4};
    this->edges[9].face_indices = {1, 2};
    this->edges[10].face_indices = {2, 3};
    this->edges[11].face_indices = {3, 4};

    this->vertices = v;

    return 0;

}

// find furthest along n
glm::vec3 get_support(const std::vector<glm::vec3> &vertices, glm::vec3 n) {
    glm::vec3 _v;
    float _d = -FLT_MAX;
    for (unsigned int i = 0; i < vertices.size(); i++) {
        glm::vec3 v = vertices.at(i);
        float d = glm::dot(v, n);
        if ( d > _d) {
            _d = d;
            _v = v;
        }
    }
    return _v;
}

Query query_face_directions(const Body &hullA, const Body &hullB) {
    float max_seperation = -FLT_MAX;
    std::vector<int> max_index;
    max_index.push_back(-1);
    max_index.push_back(-1);
    glm::vec3 best_axis;
    for (int i = 0; i < hullA.faces.size(); i++) {
        Face f = hullA.faces.at(i);
        glm::vec3 support_point = get_support(hullB.vertices, f.normal * -1.0f);
        float dist = pointPlaneDistance(support_point, f.position, f.normal);
        if (dist > max_seperation) {
            max_index = {i, -1};
            max_seperation = dist;
            best_axis = f.normal;
        }
    }
    return Query(max_seperation, max_index, best_axis);
}


Query query_edge_directions(const Body &hullA, const Body &hullB) {
    float max_seperation = -FLT_MAX;
    std::vector<int> max_index;
    max_index.push_back(-1);
    max_index.push_back(-1);
    glm::vec3 best_axis;

    for (int i = 0; i < hullA.edges.size(); i++) {
        Edge edge_a = hullA.edges.at(i);
        glm::vec3 edge_a_n1 = hullA.faces[edge_a.face_indices[0]].normal;
        glm::vec3 edge_a_n2 = hullA.faces[edge_a.face_indices[1]].normal;

        for (int j = 0; j < hullB.edges.size(); j++) {
            Edge edge_b = hullB.edges.at(j);
            glm::vec3 edge_b_n1 = hullB.faces[edge_b.face_indices[0]].normal;
            glm::vec3 edge_b_n2 = hullB.faces[edge_b.face_indices[1]].normal;
            // negate last two values for minkowski difference
            bool builds_face = isMinkowskiFace(edge_a_n1, edge_a_n2, glm::cross(edge_a_n1, edge_a_n2), edge_b_n1 * -1.0f, edge_b_n2 * -1.0f, glm::cross(edge_b_n1 * -1.0f, edge_b_n2 * -1.0f));

            if (!builds_face) {
                continue;
            }
            glm::vec3 axis = glm::normalize(glm::cross(edge_a.v1 - edge_a.v0, edge_b.v1 - edge_b.v0));
            // check edges arent parallel
            if (glm::length(axis) < 0.0001f) {
                continue;
            }
            // check normal is pointing away from A
            if (glm::dot(axis, edge_a.v0 - hullA.cm_position) < 0.0f) {
                axis = axis * -1.0f;
            }

            float dist1 = pointPlaneDistance(edge_b.v0, edge_a.v0, axis);
            float dist2 = pointPlaneDistance(edge_b.v1, edge_a.v0, axis);
            float dist;
            if (dist1 > dist2) {
                dist = dist1;
            }
            else {
                dist = dist2;
            }

            // keep largest penetration
            if (max_seperation == -FLT_MAX || dist > max_seperation) {
                max_index = {i,j};
                max_seperation = dist;
                best_axis = axis;
            }

        }
    }
    return Query(max_seperation, max_index, best_axis);
}


std::vector<Query> SAT(const Body &hullA, const Body &hullB) {

    // and the cross product of the edges if they build a face on the minkowski
    Query edge_query = query_edge_directions(hullA, hullB);
    if (edge_query.max_seperation > 0.0f) {
        edge_query.type = 0;
        return {edge_query};
    }
    // test all normals of hull_a as axes
    Query face_query_a = query_face_directions(hullA, hullB);
    if (face_query_a.max_seperation > 0.0f) {
        face_query_a.type = 1;
        return {face_query_a};
    }

    // and all normals of hull_b as axes
    Query face_query_b = query_face_directions(hullA, hullB);
    if (face_query_b.max_seperation > 0.0f) {
        face_query_b.type = 2;
        return {face_query_b};
    }

    // return queries with smallest penetration
    bool face_contact_a = face_query_a.max_seperation > edge_query.max_seperation;
    bool face_contact_b = face_query_b.max_seperation > edge_query.max_seperation;
    if (face_contact_a && face_contact_b) {
        face_query_a.type = 1;
        face_query_b.type = 2;
        return {face_query_a, face_query_b};
    } else {
        edge_query.type = 0;
        return {edge_query};
    }
}

int main()
{
    // glfw: initialize and configure
    // ------------------------------
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

    // glfw window creation
    // --------------------
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    glfwSetCursorPosCallback(window, mouse_callback);
    glfwSetScrollCallback(window, scroll_callback);

    // tell GLFW to capture our mouse
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED);

    // glad: load all OpenGL function pointers
    // ---------------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    // configure global opengl state
    // -----------------------------
    glEnable(GL_DEPTH_TEST);

    // build and compile our shader zprogram
    // ------------------------------------
    Shader ourShader("7.3.camera.vs", "7.3.camera.fs");

    // set up vertex data (and buffer(s)) and configure vertex attributes
    // ------------------------------------------------------------------
    float vertices[] = {
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
         0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
         0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
        -0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,

        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
         0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
         0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
         0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
         0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
         0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
         0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,

        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
         0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
         0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
        -0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
        -0.5f,  0.5f, -0.5f,  0.0f, 1.0f
    };
    // world space positions of our cubes
    glm::vec3 cubePositions[] = {
        glm::vec3( 0.0f,  0.0f,  0.0f),
        glm::vec3( 2.0f,  5.0f, -15.0f),
        glm::vec3(-1.5f, -2.2f, -2.5f),
        glm::vec3(-3.8f, -2.0f, -12.3f),
        glm::vec3( 2.4f, -0.4f, -3.5f),
        glm::vec3(-1.7f,  3.0f, -7.5f),
        glm::vec3( 1.3f, -2.0f, -2.5f),
        glm::vec3( 1.5f,  2.0f, -2.5f),
        glm::vec3( 1.5f,  0.2f, -1.5f),
        glm::vec3(-1.3f,  1.0f, -1.5f)
    };
    unsigned int VBO, VAO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);

    glBindVertexArray(VAO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // texture coord attribute
    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);


    // load and create a texture 
    // -------------------------
    unsigned int texture1, texture2;
    // texture 1
    // ---------
    glGenTextures(1, &texture1);
    glBindTexture(GL_TEXTURE_2D, texture1);
    // set the texture wrapping parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    // set texture filtering parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // load image, create texture and generate mipmaps
    int width, height, nrChannels;
    stbi_set_flip_vertically_on_load(true); // tell stb_image.h to flip loaded texture's on the y-axis.
    //unsigned char *data = stbi_load(FileSystem::getPath("resources/textures/container.jpg").c_str(), &width, &height, &nrChannels, 0);
    unsigned char *data = stbi_load("resources/textures/container.jpg", &width, &height, &nrChannels, 0);

    if (data)
    {
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);
    }
    else
    {
        std::cout << "Failed to load texture" << std::endl;
    }
    stbi_image_free(data);
    // texture 2
    // ---------
    glGenTextures(1, &texture2);
    glBindTexture(GL_TEXTURE_2D, texture2);
    // set the texture wrapping parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    // set texture filtering parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    // load image, create texture and generate mipmaps
    //data = stbi_load(FileSystem::getPath("resources/textures/awesomeface.png").c_str(), &width, &height, &nrChannels, 0);
    data = stbi_load("resources/textures/awesomeface.png", &width, &height, &nrChannels, 0);

    if (data)
    {
        // note that the awesomeface.png has transparency and thus an alpha channel, so make sure to tell OpenGL the data type is of GL_RGBA
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
        glGenerateMipmap(GL_TEXTURE_2D);
    }
    else
    {
        std::cout << "Failed to load texture" << std::endl;
    }
    stbi_image_free(data);

    // tell opengl for each sampler to which texture unit it belongs to (only has to be done once)
    // -------------------------------------------------------------------------------------------
    ourShader.use();
    ourShader.setInt("texture1", 0);
    ourShader.setInt("texture2", 1);

    // render loop
    // -----------
    while (!glfwWindowShouldClose(window))
    {
        // per-frame time logic
        // --------------------
        float currentFrame = static_cast<float>(glfwGetTime());
        deltaTime = currentFrame - lastFrame;
        lastFrame = currentFrame;

        // input
        // -----
        processInput(window);

        // render
        // ------
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 

        // bind textures on corresponding texture units
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, texture1);
        glActiveTexture(GL_TEXTURE1);
        glBindTexture(GL_TEXTURE_2D, texture2);

        // activate shader
        ourShader.use();

        // pass projection matrix to shader (note that in this case it could change every frame)
        projection = glm::perspective(glm::radians(fov), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f);
        ourShader.setMat4("projection", projection);

        // camera/view transformation
        view = glm::lookAt(cameraPos, cameraPos + cameraFront, cameraUp);
        ourShader.setMat4("view", view);

        float nearDist = 0.1f;
        float farDist = 100.0f;

        // calculate frustum data per frame (lighthouse3d.com tutorial for reference)
        float ar = (float)SCR_WIDTH / (float)SCR_HEIGHT;
        float Hnear = 2 * tan(glm::radians(fov/2)) * nearDist;
        float Wnear = Hnear * ar;
        float Hfar = 2 * tan(glm::radians(fov/2)) * farDist;
        float Wfar = Hfar * ar; 

        glm::vec3 Cnear = cameraPos + glm::normalize(cameraFront) * nearDist;
        glm::vec3 Cfar = cameraPos + glm::normalize(cameraFront) * farDist;
        glm::vec3 cameraRight = glm::cross(cameraFront, cameraUp);
        glm::vec3 topRightFar = Cfar + (cameraUp * (Hfar / 2)) + (cameraRight * (Wfar / 2));
        glm::vec3 bottomRightFar = Cfar - (cameraUp * (Hfar / 2)) + (cameraRight * (Wfar / 2));

        glm::vec3 topLeftFar =  Cfar + (cameraUp * (Hfar / 2)) - (cameraRight * (Wfar / 2));
        glm::vec3 bottomLeftFar =  Cfar - (cameraUp * (Hfar / 2)) - (cameraRight * (Wfar / 2));

        glm::vec3 topRightNear = Cnear + (cameraUp * (Hnear / 2)) + (cameraRight * (Wnear / 2));
        glm::vec3 topLeftNear =  Cnear + (cameraUp * (Hnear / 2)) - (cameraRight * (Wnear / 2));

        glm::vec3 bottomLeftNear = Cnear - (cameraUp * (Hnear /2)) - (cameraRight * (Wnear / 2));
        glm::vec3 bottomRightNear = Cnear - (cameraUp * (Hnear /2)) + (cameraRight * (Wnear / 2));

        glm::vec3 aux = glm::normalize((Cnear + cameraRight * (float)(Wnear / 2)) - cameraPos);
        glm::vec3 rightNormal = glm::normalize(glm::cross(aux, cameraUp));
        aux = glm::normalize((Cnear - cameraRight * (float)(Wnear / 2)) - cameraPos);
        glm::vec3 leftNormal = glm::normalize(glm::cross(aux, cameraUp));

        aux = glm::normalize((Cnear + cameraUp * (float)(Hnear / 2)) - cameraPos);
        glm::vec3 topNormal =  glm::normalize(glm::cross(aux, cameraRight));

        aux = glm::normalize((Cnear - cameraUp * (float)(Hnear / 2)) - cameraPos);
        glm::vec3 bottomNormal =  glm::normalize(glm::cross(aux, cameraRight));

        glm::vec3 backNormal = cameraFront;
        glm::vec3 frontNormal = -1.0f * cameraFront;

        Body frustum_body;

        frustum_body.update({topLeftNear, bottomLeftNear, bottomRightNear, topRightNear, topLeftFar, bottomLeftFar, bottomRightFar, topRightFar});

        // render boxes
        glBindVertexArray(VAO);
        int meshesCulled = 0;
        for (unsigned int i = 0; i < 10; i++)
        {

            // calculate the model matrix for each object and pass it to shader before drawing
            glm::mat4 model = glm::mat4(1.0f); // make sure to initialize matrix to identity matrix first
            model = glm::translate(model, cubePositions[i]);
            float angle = 20.0f * i;
            model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));

            Body box_body;

            std::vector<float> aabb = {-0.5f, -0.5f, -0.5f, 0.5f, 0.5f, 0.5f};
            glm::vec3 _min = glm::vec3(aabb[0], aabb[1], aabb[2]);
            glm::vec3 _max = glm::vec3(aabb[3], aabb[4], aabb[5]);

            glm::vec3 v5 = glm::vec3(model * glm::vec4(_min.x, _min.y, _min.z, 1.0f));
            glm::vec3 v3 = glm::vec3(model * glm::vec4(_max.x, _max.y, _max.z, 1.0f));
            glm::vec3 v4 = glm::vec3(model * glm::vec4(_min.x, _max.y, _min.z, 1.0f));
            glm::vec3 v6 = glm::vec3(model * glm::vec4(_max.x, _min.y, _min.z, 1.0f));
            glm::vec3 v7 = glm::vec3(model * glm::vec4(_max.x, _max.y, _min.z, 1.0f));
            glm::vec3 v2 = glm::vec3(model * glm::vec4(_max.x, _min.y, _max.z, 1.0f));
            glm::vec3 v0 = glm::vec3(model * glm::vec4(_min.x, _max.y, _max.z, 1.0f));
            glm::vec3 v1 = glm::vec3(model * glm::vec4(_min.x, _min.y, _max.z, 1.0f));
            std::vector<glm::vec3> v = {v0, v1, v2, v3, v4, v5, v6, v7};
            box_body.update(v);

            box_body.cm_position = cubePositions[i];

            std::vector<Query> queries = SAT(box_body, frustum_body);
            float max_seperation = -FLT_MAX;
            for (unsigned int i = 0; i < queries.size(); i++) {
                Query query = queries[i];
                if (query.max_seperation > max_seperation) {
                    max_seperation = query.max_seperation;
                }
            }

            if (max_seperation > 0.0f) {
                // mesh is culled
                meshesCulled += 1;
            } else {

                ourShader.setMat4("model", model);
                glDrawArrays(GL_TRIANGLES, 0, 36);
            }
        }

        std::cout << 10 - meshesCulled << " of 10 meshes drawn!" << std::endl;

        // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        // -------------------------------------------------------------------------------
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // optional: de-allocate all resources once they've outlived their purpose:
    // ------------------------------------------------------------------------
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);

    // glfw: terminate, clearing all previously allocated GLFW resources.
    // ------------------------------------------------------------------
    glfwTerminate();
    return 0;
}

// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);

    float cameraSpeed = static_cast<float>(2.5 * deltaTime);
    if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS)
        cameraPos += cameraSpeed * cameraFront;
    if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS)
        cameraPos -= cameraSpeed * cameraFront;
    if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS)
        cameraPos -= glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
    if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS)
        cameraPos += glm::normalize(glm::cross(cameraFront, cameraUp)) * cameraSpeed;
}

// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    // make sure the viewport matches the new window dimensions; note that width and 
    // height will be significantly larger than specified on retina displays.
    glViewport(0, 0, width, height);
}

// glfw: whenever the mouse moves, this callback is called
// -------------------------------------------------------
void mouse_callback(GLFWwindow* window, double xposIn, double yposIn)
{
    float xpos = static_cast<float>(xposIn);
    float ypos = static_cast<float>(yposIn);

    if (firstMouse)
    {
        lastX = xpos;
        lastY = ypos;
        firstMouse = false;
    }

    float xoffset = xpos - lastX;
    float yoffset = lastY - ypos; // reversed since y-coordinates go from bottom to top
    lastX = xpos;
    lastY = ypos;

    float sensitivity = 0.1f; // change this value to your liking
    xoffset *= sensitivity;
    yoffset *= sensitivity;

    yaw += xoffset;
    pitch += yoffset;

    // make sure that when pitch is out of bounds, screen doesn't get flipped
    if (pitch > 89.0f)
        pitch = 89.0f;
    if (pitch < -89.0f)
        pitch = -89.0f;

    glm::vec3 front;
    front.x = cos(glm::radians(yaw)) * cos(glm::radians(pitch));
    front.y = sin(glm::radians(pitch));
    front.z = sin(glm::radians(yaw)) * cos(glm::radians(pitch));
    cameraFront = glm::normalize(front);
}

// glfw: whenever the mouse scroll wheel scrolls, this callback is called
// ----------------------------------------------------------------------
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
    fov -= (float)yoffset;
    if (fov < 1.0f)
        fov = 1.0f;
    if (fov > 45.0f)
        fov = 45.0f;
}

The console prints how many OBB's were culled each frame: frustum culling example

Upvotes: 0

cdoubleplusgood
cdoubleplusgood

Reputation: 1369

Your approach is generally correct.
Usually AABBs or bounding spheres are used instead of testing each vertex of an arbitrary shape.
However, for AABBs there are situations where all corners are outside the frustum, but the box still intersects the frustum. A conservative solution is to reject only boxes if all corners are on the outer side of at least one plane.
There is one common optimization for AABBs: For each plane of the frustum, you only need to check the "closest" and the "farthest" corner instead of all 6 corners. A great resource for this and frustum culling in general is this:
http://www.lighthouse3d.com/tutorials/view-frustum-culling/

Edit: Here is one more article how to find AABBs that are not completely one the outer side of one plane, but still do not intersect the frustum:
http://www.iquilezles.org/www/articles/frustumcorrect/frustumcorrect.htm

Upvotes: 2

mattnewport
mattnewport

Reputation: 14057

  • Yes, this is essentially the correct approach for frustum culling points.
  • No, performing this test on each vertex individually is not an effective way of culling an arbitrary polygon. Consider the case of a single very large triangle that intersects the frustum: all of it's vertices may be outside the frustum but the triangle still intersects the frustum and should be rendered.
  • AABBs can be used for frustum culling and are often a good choice, although you still have to handle the case of an AABB having all of it's vertices outside the frustum but intersecting the frustum. Bounding spheres make the inside / outside test somewhat simpler but tend to have looser bounds around the objects they contain. This is often a reasonable tradeoff however.

Upvotes: 3

Related Questions