视锥体裁剪问题

4
所以,我正在尝试实现视锥体剔除。这里的问题是,在我能够做到这一点之前,我需要理解一些东西。
首先,是平面交叉:
我的理解是,可以通过3个点来定义一个平面; 让我们称它们为p0、p1和p2。
鉴于此,我们知道可以如下计算平面的法向量:
(伪代码)
vec3 edge0 = p1 - p0;
vec3 edge1 = p2 - p0;

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

现在,假设我们有一个函数,它基本上告诉我们一个给定的点是否以某种方式“穿越”平面。 (更多伪代码)
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. 
}

这样做的逻辑是,由于视锥包含六个分离的平面(近、远、左、上、右、下),我们希望获取每个平面,并将它们"传递"给crossesPlane()函数,对于一个单独的点进行测试(一个点,六个测试)。如果这六个调用crossesPlane()中的任意一个返回false,那么我们想要剔除该点,从而使该点被锥截除,当然该点也不会被渲染。 问题
  • 这是一个正确的裁剪视锥体的方法吗?
  • 如果是的话,是否可以采取给定多边形的任意顶点列表,并对使用此方法测试每个顶点,以此来有效地实现呢?
  • 虽然AABB可以用来代替多边形/网格进行裁剪测试,但它们在这种情况下仍然常用吗,它们仍然被认为是"通用"的去到方法吗?
注意

如果在此过程中D3D和OpenGL之间有任何值得提及的实现差异,那肯定是值得赞赏的。


通常我们在检查平截头体平面之前进行同种变换,这样比较就是针对1、-1等。 - Mark Ping
1
@MarkPing:对于测试单个顶点是正确的,但对于像AABB或边界球这样的形状不适用,因为这些形状在齐次空间中不再是矩形或球形。因此,据我所知,在视图空间中进行测试更为常见。 - cdoubleplusgood
3个回答

3
  • 是的,这基本上是在裁剪视锥体内点的正确方法。
  • 不,对每个顶点单独执行此测试并不是裁剪任意多边形的有效方法。考虑一个与视锥体相交的单个非常大的三角形的情况:它的所有顶点可能都在视锥体外面,但是该三角形仍会与视锥体相交并应该被渲染。
  • AABB可以用于裁剪视锥体,并且通常是一个不错的选择,尽管您仍然需要处理AABB的所有顶点都在视锥体外部但是又与视锥体相交的情况。包围球使得内部/外部测试有些更简单,但往往对其所包含对象的限制要更宽松。然而,这通常是一个合理的折衷方案。

2
你的方法基本上是正确的。
通常情况下,使用AABB或边界球而不是测试任意形状的每个顶点。
然而,对于AABB,存在这样的情况:所有角落都在视锥体外面,但是盒子仍然与视锥体相交。一种保守的解决方案是只有当所有角落都在至少一个平面的外侧时才拒绝盒子。
对于AABB,有一个常见的优化方法:对于视锥体的每个平面,您只需要检查“最近”的和“最远”的角落,而不是所有6个角落。关于此内容和视锥体剔除的一般信息,这是一个很好的资源:
http://www.lighthouse3d.com/tutorials/view-frustum-culling/

编辑: 这里还有一篇文章,介绍如何找到不完全位于一个平面的外侧但仍未与视锥体相交的AABB:
http://www.iquilezles.org/www/articles/frustumcorrect/frustumcorrect.htm


0

概述:

定义立方体顶点和边缘索引 这是一个最小化的工作示例,改编自基本的learnopengl教程

#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;
}

控制台每帧打印被剔除的OBB数量: 视锥体剔除示例

另外,我发现有一点不对,需要在计算cameraRight时更新cameraUpglm::vec3 cameraRight = glm::normalize(glm::cross(cameraFront, up)); cameraUp = glm::normalize(glm::cross(cameraRight, cameraFront)); - got here

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接