From 54dd372518bc17ac340ecaee3d725aeeaf62bd5b Mon Sep 17 00:00:00 2001 From: Joey de Vries Date: Thu, 2 Sep 2021 15:27:27 +0200 Subject: [PATCH] Add source files for scene (graph + frustum culling) guest article. --- includes/learnopengl/entity.h | 479 ++++++++++++++++++ .../1.scene/1.scene_graph/1.model_loading.fs | 11 + .../1.scene/1.scene_graph/1.model_loading.vs | 16 + .../1.scene/1.scene_graph/scene_graph.cpp | 227 +++++++++ .../2.frustum_culling/1.model_loading.fs | 11 + .../2.frustum_culling/1.model_loading.vs | 16 + .../2.frustum_culling/frustum_culling.cpp | 236 +++++++++ 7 files changed, 996 insertions(+) create mode 100644 includes/learnopengl/entity.h create mode 100644 src/8.guest/2021/1.scene/1.scene_graph/1.model_loading.fs create mode 100644 src/8.guest/2021/1.scene/1.scene_graph/1.model_loading.vs create mode 100644 src/8.guest/2021/1.scene/1.scene_graph/scene_graph.cpp create mode 100644 src/8.guest/2021/1.scene/2.frustum_culling/1.model_loading.fs create mode 100644 src/8.guest/2021/1.scene/2.frustum_culling/1.model_loading.vs create mode 100644 src/8.guest/2021/1.scene/2.frustum_culling/frustum_culling.cpp diff --git a/includes/learnopengl/entity.h b/includes/learnopengl/entity.h new file mode 100644 index 0000000..cd9e266 --- /dev/null +++ b/includes/learnopengl/entity.h @@ -0,0 +1,479 @@ +#ifndef ENTITY_H +#define ENTITY_H + +#include //glm::mat4 +#include //std::list +#include //std::array +#include //std::unique_ptr + +class Transform +{ +protected: + //Local space information + glm::vec3 m_pos = { 0.0f, 0.0f, 0.0f }; + glm::vec3 m_eulerRot = { 0.0f, 0.0f, 0.0f }; //In degrees + glm::vec3 m_scale = { 1.0f, 1.0f, 1.0f }; + + //Global space informaiton concatenate in matrix + glm::mat4 m_modelMatrix = glm::mat4(1.0f); + + //Dirty flag + bool m_isDirty = true; + +protected: + glm::mat4 getLocalModelMatrix() + { + const glm::mat4 transformX = glm::rotate(glm::mat4(1.0f), glm::radians(m_eulerRot.x), glm::vec3(1.0f, 0.0f, 0.0f)); + const glm::mat4 transformY = glm::rotate(glm::mat4(1.0f), glm::radians(m_eulerRot.y), glm::vec3(0.0f, 1.0f, 0.0f)); + const glm::mat4 transformZ = glm::rotate(glm::mat4(1.0f), glm::radians(m_eulerRot.z), glm::vec3(0.0f, 0.0f, 1.0f)); + + // Y * X * Z + const glm::mat4 roationMatrix = transformY * transformX * transformZ; + + // translation * rotation * scale (also know as TRS matrix) + return glm::translate(glm::mat4(1.0f), m_pos) * roationMatrix * glm::scale(glm::mat4(1.0f), m_scale); + } +public: + + void computeModelMatrix() + { + m_modelMatrix = getLocalModelMatrix(); + } + + void computeModelMatrix(const glm::mat4& parentGlobalModelMatrix) + { + m_modelMatrix = parentGlobalModelMatrix * getLocalModelMatrix(); + } + + void setLocalPosition(const glm::vec3& newPosition) + { + m_pos = newPosition; + m_isDirty = true; + } + + void setLocalRotation(const glm::vec3& newRotation) + { + m_eulerRot = newRotation; + m_isDirty = true; + } + + void setLocalScale(const glm::vec3& newScale) + { + m_scale = newScale; + m_isDirty = true; + } + + const glm::vec3& getGlobalPosition() const + { + return m_modelMatrix[3]; + } + + const glm::vec3& getLocalPosition() const + { + return m_pos; + } + + const glm::vec3& getLocalRotation() const + { + return m_eulerRot; + } + + const glm::vec3& getLocalScale() const + { + return m_scale; + } + + const glm::mat4& getModelMatrix() const + { + return m_modelMatrix; + } + + glm::vec3 getRight() const + { + return m_modelMatrix[0]; + } + + + glm::vec3 getUp() const + { + return m_modelMatrix[1]; + } + + glm::vec3 getBackward() const + { + return m_modelMatrix[2]; + } + + glm::vec3 getForward() const + { + return -m_modelMatrix[2]; + } + + glm::vec3 getGlobalScale() const + { + return { glm::length(getRight()), glm::length(getUp()), glm::length(getBackward()) }; + } + + bool isDirty() const + { + return m_isDirty; + } +}; + +struct Plan +{ + glm::vec3 normal = { 0.f, 1.f, 0.f }; // unit vector + float distance = 0.f; // Distance with origin + + Plan() = default; + + Plan(const glm::vec3& p1, const glm::vec3& norm) + : normal(glm::normalize(norm)), + distance(glm::dot(normal, p1)) + {} + + float getSignedDistanceToPlan(const glm::vec3& point) const + { + return glm::dot(normal, point) - distance; + } +}; + +struct Frustum +{ + Plan topFace; + Plan bottomFace; + + Plan rightFace; + Plan leftFace; + + Plan farFace; + Plan nearFace; +}; + +struct BoundingVolume +{ + virtual bool isOnFrustum(const Frustum& camFrustum, const Transform& transform) const = 0; + + virtual bool isOnOrForwardPlan(const Plan& plan) const = 0; + + bool isOnFrustum(const Frustum& camFrustum) const + { + return (isOnOrForwardPlan(camFrustum.leftFace) && + isOnOrForwardPlan(camFrustum.rightFace) && + isOnOrForwardPlan(camFrustum.topFace) && + isOnOrForwardPlan(camFrustum.bottomFace) && + isOnOrForwardPlan(camFrustum.nearFace) && + isOnOrForwardPlan(camFrustum.farFace)); + }; +}; + +struct Sphere : public BoundingVolume +{ + glm::vec3 center{ 0.f, 0.f, 0.f }; + float radius{ 0.f }; + + Sphere(const glm::vec3& inCenter, float inRadius) + : BoundingVolume{}, center{ inCenter }, radius{ inRadius } + {} + + bool isOnOrForwardPlan(const Plan& plan) const final + { + return plan.getSignedDistanceToPlan(center) > -radius; + } + + bool isOnFrustum(const Frustum& camFrustum, const Transform& transform) const final + { + //Get global scale thanks to our transform + const glm::vec3 globalScale = transform.getGlobalScale(); + + //Get our global center with process it with the global model matrix of our transform + const glm::vec3 globalCenter{ transform.getModelMatrix() * glm::vec4(center, 1.f) }; + + //To wrap correctly our shape, we need the maximum scale scalar. + const float maxScale = std::max(std::max(globalScale.x, globalScale.y), globalScale.z); + + //Max scale is assuming for the diameter. So, we need the half to apply it to our radius + Sphere globalSphere(globalCenter, radius * (maxScale * 0.5f)); + + //Check Firstly the result that have the most chance to faillure to avoid to call all functions. + return (globalSphere.isOnOrForwardPlan(camFrustum.leftFace) && + globalSphere.isOnOrForwardPlan(camFrustum.rightFace) && + globalSphere.isOnOrForwardPlan(camFrustum.farFace) && + globalSphere.isOnOrForwardPlan(camFrustum.nearFace) && + globalSphere.isOnOrForwardPlan(camFrustum.topFace) && + globalSphere.isOnOrForwardPlan(camFrustum.bottomFace)); + }; +}; + +struct SquareAABB : public BoundingVolume +{ + glm::vec3 center{ 0.f, 0.f, 0.f }; + float extent{ 0.f }; + + SquareAABB(const glm::vec3& inCenter, float inExtent) + : BoundingVolume{}, center{ inCenter }, extent{ inExtent } + {} + + bool isOnOrForwardPlan(const Plan& plan) const final + { + // Compute the projection interval radius of b onto L(t) = b.c + t * p.n + const float r = extent * (std::abs(plan.normal.x) + std::abs(plan.normal.y) + std::abs(plan.normal.z)); + return -r <= plan.getSignedDistanceToPlan(center); + } + + bool isOnFrustum(const Frustum& camFrustum, const Transform& transform) const final + { + //Get global scale thanks to our transform + const glm::vec3 globalCenter{ transform.getModelMatrix() * glm::vec4(center, 1.f) }; + + // Scaled orientation + const glm::vec3 right = transform.getRight() * extent; + const glm::vec3 up = transform.getUp() * extent; + const glm::vec3 forward = transform.getForward() * extent; + + const float newIi = std::abs(glm::dot(glm::vec3{ 1.f, 0.f, 0.f }, right)) + + std::abs(glm::dot(glm::vec3{ 1.f, 0.f, 0.f }, up)) + + std::abs(glm::dot(glm::vec3{ 1.f, 0.f, 0.f }, forward)); + + const float newIj = std::abs(glm::dot(glm::vec3{ 0.f, 1.f, 0.f }, right)) + + std::abs(glm::dot(glm::vec3{ 0.f, 1.f, 0.f }, up)) + + std::abs(glm::dot(glm::vec3{ 0.f, 1.f, 0.f }, forward)); + + const float newIk = std::abs(glm::dot(glm::vec3{ 0.f, 0.f, 1.f }, right)) + + std::abs(glm::dot(glm::vec3{ 0.f, 0.f, 1.f }, up)) + + std::abs(glm::dot(glm::vec3{ 0.f, 0.f, 1.f }, forward)); + + const SquareAABB globalAABB(globalCenter, std::max(std::max(newIi, newIj), newIk)); + + return (globalAABB.isOnOrForwardPlan(camFrustum.leftFace) && + globalAABB.isOnOrForwardPlan(camFrustum.rightFace) && + globalAABB.isOnOrForwardPlan(camFrustum.topFace) && + globalAABB.isOnOrForwardPlan(camFrustum.bottomFace) && + globalAABB.isOnOrForwardPlan(camFrustum.nearFace) && + globalAABB.isOnOrForwardPlan(camFrustum.farFace)); + }; +}; + +struct AABB : public BoundingVolume +{ + glm::vec3 center{ 0.f, 0.f, 0.f }; + glm::vec3 extents{ 0.f, 0.f, 0.f }; + + AABB(const glm::vec3& min, const glm::vec3& max) + : BoundingVolume{}, center{ (max + min) * 0.5f }, extents{ max.x - center.x, max.y - center.y, max.z - center.z } + {} + + AABB(const glm::vec3& inCenter, float iI, float iJ, float iK) + : BoundingVolume{}, center{ inCenter }, extents{ iI, iJ, iK } + {} + + std::array getVertice() const + { + std::array vertice; + vertice[0] = { center.x - extents.x, center.y - extents.y, center.z - extents.z }; + vertice[1] = { center.x + extents.x, center.y - extents.y, center.z - extents.z }; + vertice[2] = { center.x - extents.x, center.y + extents.y, center.z - extents.z }; + vertice[3] = { center.x + extents.x, center.y + extents.y, center.z - extents.z }; + vertice[4] = { center.x - extents.x, center.y - extents.y, center.z + extents.z }; + vertice[5] = { center.x + extents.x, center.y - extents.y, center.z + extents.z }; + vertice[6] = { center.x - extents.x, center.y + extents.y, center.z + extents.z }; + vertice[7] = { center.x + extents.x, center.y + extents.y, center.z + extents.z }; + return vertice; + } + + //see https://gdbooks.gitbooks.io/3dcollisions/content/Chapter2/static_aabb_plan.html + bool isOnOrForwardPlan(const Plan& plan) const final + { + // Compute the projection interval radius of b onto L(t) = b.c + t * p.n + const float r = extents.x * std::abs(plan.normal.x) + extents.y * std::abs(plan.normal.y) + + extents.z * std::abs(plan.normal.z); + + return -r <= plan.getSignedDistanceToPlan(center); + } + + bool isOnFrustum(const Frustum& camFrustum, const Transform& transform) const final + { + //Get global scale thanks to our transform + const glm::vec3 globalCenter{ transform.getModelMatrix() * glm::vec4(center, 1.f) }; + + // Scaled orientation + const glm::vec3 right = transform.getRight() * extents.x; + const glm::vec3 up = transform.getUp() * extents.y; + const glm::vec3 forward = transform.getForward() * extents.z; + + const float newIi = std::abs(glm::dot(glm::vec3{ 1.f, 0.f, 0.f }, right)) + + std::abs(glm::dot(glm::vec3{ 1.f, 0.f, 0.f }, up)) + + std::abs(glm::dot(glm::vec3{ 1.f, 0.f, 0.f }, forward)); + + const float newIj = std::abs(glm::dot(glm::vec3{ 0.f, 1.f, 0.f }, right)) + + std::abs(glm::dot(glm::vec3{ 0.f, 1.f, 0.f }, up)) + + std::abs(glm::dot(glm::vec3{ 0.f, 1.f, 0.f }, forward)); + + const float newIk = std::abs(glm::dot(glm::vec3{ 0.f, 0.f, 1.f }, right)) + + std::abs(glm::dot(glm::vec3{ 0.f, 0.f, 1.f }, up)) + + std::abs(glm::dot(glm::vec3{ 0.f, 0.f, 1.f }, forward)); + + const AABB globalAABB(globalCenter, newIi, newIj, newIk); + + return (globalAABB.isOnOrForwardPlan(camFrustum.leftFace) && + globalAABB.isOnOrForwardPlan(camFrustum.rightFace) && + globalAABB.isOnOrForwardPlan(camFrustum.topFace) && + globalAABB.isOnOrForwardPlan(camFrustum.bottomFace) && + globalAABB.isOnOrForwardPlan(camFrustum.nearFace) && + globalAABB.isOnOrForwardPlan(camFrustum.farFace)); + }; +}; + +Frustum createFrustumFromCamera(const Camera& cam, float aspect, float fovY, float zNear, float zFar) +{ + Frustum frustum; + const float halfVSide = zFar * tanf(fovY * .5f); + const float halfHSide = halfVSide * aspect; + const glm::vec3 frontMultFar = zFar * cam.Front; + + frustum.nearFace = { cam.Position + zNear * cam.Front, cam.Front }; + frustum.farFace = { cam.Position + frontMultFar, -cam.Front }; + frustum.rightFace = { cam.Position, glm::cross(cam.Up, frontMultFar + cam.Right * halfHSide) }; + frustum.leftFace = { cam.Position, glm::cross(frontMultFar - cam.Right * halfHSide, cam.Up) }; + frustum.topFace = { cam.Position, glm::cross(cam.Right, frontMultFar - cam.Up * halfVSide) }; + frustum.bottomFace = { cam.Position, glm::cross(frontMultFar + cam.Up * halfVSide, cam.Right) }; + + return frustum; +} + +AABB generateAABB(const Model& model) +{ + glm::vec3 minAABB = glm::vec3(std::numeric_limits::max()); + glm::vec3 maxAABB = glm::vec3(std::numeric_limits::min()); + for (auto&& mesh : model.meshes) + { + for (auto&& vertex : mesh.vertices) + { + minAABB.x = std::min(minAABB.x, vertex.Position.x); + minAABB.y = std::min(minAABB.y, vertex.Position.y); + minAABB.z = std::min(minAABB.z, vertex.Position.z); + + maxAABB.x = std::max(maxAABB.x, vertex.Position.x); + maxAABB.y = std::max(maxAABB.y, vertex.Position.y); + maxAABB.z = std::max(maxAABB.z, vertex.Position.z); + } + } + return AABB(minAABB, maxAABB); +} + +Sphere generateSphereBV(const Model& model) +{ + glm::vec3 minAABB = glm::vec3(std::numeric_limits::max()); + glm::vec3 maxAABB = glm::vec3(std::numeric_limits::min()); + for (auto&& mesh : model.meshes) + { + for (auto&& vertex : mesh.vertices) + { + minAABB.x = std::min(minAABB.x, vertex.Position.x); + minAABB.y = std::min(minAABB.y, vertex.Position.y); + minAABB.z = std::min(minAABB.z, vertex.Position.z); + + maxAABB.x = std::max(maxAABB.x, vertex.Position.x); + maxAABB.y = std::max(maxAABB.y, vertex.Position.y); + maxAABB.z = std::max(maxAABB.z, vertex.Position.z); + } + } + + return Sphere((maxAABB + minAABB) * 0.5f, glm::length(minAABB - maxAABB)); +} + +class Entity +{ +public: + //Scene graph + std::list> children; + Entity* parent = nullptr; + + //Space information + Transform transform; + + Model* pModel = nullptr; + std::unique_ptr boundingVolume; + + + // constructor, expects a filepath to a 3D model. + Entity(Model& model) : pModel{ &model } + { + boundingVolume = std::make_unique(generateAABB(model)); + //boundingVolume = std::make_unique(generateSphereBV(model)); + } + + AABB getGlobalAABB() + { + //Get global scale thanks to our transform + const glm::vec3 globalCenter{ transform.getModelMatrix() * glm::vec4(boundingVolume->center, 1.f) }; + + // Scaled orientation + const glm::vec3 right = transform.getRight() * boundingVolume->extents.x; + const glm::vec3 up = transform.getUp() * boundingVolume->extents.y; + const glm::vec3 forward = transform.getForward() * boundingVolume->extents.z; + + const float newIi = std::abs(glm::dot(glm::vec3{ 1.f, 0.f, 0.f }, right)) + + std::abs(glm::dot(glm::vec3{ 1.f, 0.f, 0.f }, up)) + + std::abs(glm::dot(glm::vec3{ 1.f, 0.f, 0.f }, forward)); + + const float newIj = std::abs(glm::dot(glm::vec3{ 0.f, 1.f, 0.f }, right)) + + std::abs(glm::dot(glm::vec3{ 0.f, 1.f, 0.f }, up)) + + std::abs(glm::dot(glm::vec3{ 0.f, 1.f, 0.f }, forward)); + + const float newIk = std::abs(glm::dot(glm::vec3{ 0.f, 0.f, 1.f }, right)) + + std::abs(glm::dot(glm::vec3{ 0.f, 0.f, 1.f }, up)) + + std::abs(glm::dot(glm::vec3{ 0.f, 0.f, 1.f }, forward)); + + return AABB(globalCenter, newIi, newIj, newIk); + } + + //Add child. Argument input is argument of any constructor that you create. By default you can use the default constructor and don't put argument input. + template + void addChild(TArgs&... args) + { + children.emplace_back(std::make_unique(args...)); + children.back()->parent = this; + } + + //Update transform if it was changed + void updateSelfAndChild() + { + if (!transform.isDirty()) + return; + + forceUpdateSelfAndChild(); + } + + //Force update of transform even if local space don't change + void forceUpdateSelfAndChild() + { + if (parent) + transform.computeModelMatrix(parent->transform.getModelMatrix()); + else + transform.computeModelMatrix(); + + for (auto&& child : children) + { + child->forceUpdateSelfAndChild(); + } + } + + + void drawSelfAndChild(const Frustum& frustum, Shader& ourShader, unsigned int& display, unsigned int& total) + { + if (boundingVolume->isOnFrustum(frustum, transform)) + { + ourShader.setMat4("model", transform.getModelMatrix()); + pModel->Draw(ourShader); + display++; + } + total++; + + for (auto&& child : children) + { + child->drawSelfAndChild(frustum, ourShader, display, total); + } + } +}; +#endif diff --git a/src/8.guest/2021/1.scene/1.scene_graph/1.model_loading.fs b/src/8.guest/2021/1.scene/1.scene_graph/1.model_loading.fs new file mode 100644 index 0000000..9da78b1 --- /dev/null +++ b/src/8.guest/2021/1.scene/1.scene_graph/1.model_loading.fs @@ -0,0 +1,11 @@ +#version 330 core +out vec4 FragColor; + +in vec2 TexCoords; + +uniform sampler2D texture_diffuse1; + +void main() +{ + FragColor = texture(texture_diffuse1, TexCoords); +} \ No newline at end of file diff --git a/src/8.guest/2021/1.scene/1.scene_graph/1.model_loading.vs b/src/8.guest/2021/1.scene/1.scene_graph/1.model_loading.vs new file mode 100644 index 0000000..253b79e --- /dev/null +++ b/src/8.guest/2021/1.scene/1.scene_graph/1.model_loading.vs @@ -0,0 +1,16 @@ +#version 330 core +layout (location = 0) in vec3 aPos; +layout (location = 1) in vec3 aNormal; +layout (location = 2) in vec2 aTexCoords; + +out vec2 TexCoords; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; + +void main() +{ + TexCoords = aTexCoords; + gl_Position = projection * view * model * vec4(aPos, 1.0); +} \ No newline at end of file diff --git a/src/8.guest/2021/1.scene/1.scene_graph/scene_graph.cpp b/src/8.guest/2021/1.scene/1.scene_graph/scene_graph.cpp new file mode 100644 index 0000000..313d70e --- /dev/null +++ b/src/8.guest/2021/1.scene/1.scene_graph/scene_graph.cpp @@ -0,0 +1,227 @@ +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifndef ENTITY_H +#define ENTITY_H + +#include //std::list +#include //std::unique_ptr + +class Entity : public Model +{ +public: + list> children; + Entity* parent; +}; +#endif + + +#include + +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 +Camera camera(glm::vec3(0.0f, 0.0f, 3.0f)); +float lastX = SCR_WIDTH / 2.0f; +float lastY = SCR_HEIGHT / 2.0f; +bool firstMouse = true; + +// timing +float deltaTime = 0.0f; +float lastFrame = 0.0f; + +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; + } + + // tell stb_image.h to flip loaded texture's on the y-axis (before loading model). + stbi_set_flip_vertically_on_load(true); + + // configure global opengl state + // ----------------------------- + glEnable(GL_DEPTH_TEST); + + // build and compile shaders + // ------------------------- + Shader ourShader("1.model_loading.vs", "1.model_loading.fs"); + + // load entities + // ----------- + Entity ourEntity(FileSystem::getPath("resources/objects/planet/planet.obj")); + ourEntity.transform.setLocalPosition({ 10, 0, 0 }); + const float scale = 0.75; + ourEntity.transform.setLocalScale({ scale, scale, scale }); + + { + Entity* lastEntity = &ourEntity; + + for (unsigned int i = 0; i < 10; ++i) + { + lastEntity->addChild(FileSystem::getPath("resources/objects/planet/planet.obj")); + lastEntity = lastEntity->children.back().get(); + + //Set tranform values + lastEntity->transform.setLocalPosition({ 10, 0, 0 }); + lastEntity->transform.setLocalScale({ scale, scale, scale }); + } + } + ourEntity.updateSelfAndChild(); + + // draw in wireframe + //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + + // render loop + // ----------- + while (!glfwWindowShouldClose(window)) + { + // per-frame time logic + // -------------------- + float currentFrame = glfwGetTime(); + deltaTime = currentFrame - lastFrame; + lastFrame = currentFrame; + + // input + // ----- + processInput(window); + + // render + // ------ + glClearColor(0.05f, 0.05f, 0.05f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // don't forget to enable shader before setting uniforms + ourShader.use(); + + // view/projection transformations + glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); + glm::mat4 view = camera.GetViewMatrix(); + ourShader.setMat4("projection", projection); + ourShader.setMat4("view", view); + + // draw our scene graph + Entity* lastEntity = &ourEntity; + while (lastEntity->children.size()) + { + ourShader.setMat4("model", lastEntity->transform.getModelMatrix()); + lastEntity->Draw(ourShader); + lastEntity = lastEntity->children.back().get(); + } + + ourEntity.transform.setLocalRotation({ 0.f, ourEntity.transform.getLocalRotation().y + 20 * deltaTime, 0.f }); + ourEntity.updateSelfAndChild(); + + // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.) + // ------------------------------------------------------------------------------- + glfwSwapBuffers(window); + glfwPollEvents(); + } + + // 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); + + if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) + camera.ProcessKeyboard(FORWARD, deltaTime); + if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) + camera.ProcessKeyboard(BACKWARD, deltaTime); + if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) + camera.ProcessKeyboard(LEFT, deltaTime); + if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) + camera.ProcessKeyboard(RIGHT, deltaTime); +} + +// 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 xpos, double ypos) +{ + 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; + + camera.ProcessMouseMovement(xoffset, yoffset); +} + +// glfw: whenever the mouse scroll wheel scrolls, this callback is called +// ---------------------------------------------------------------------- +void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) +{ + camera.ProcessMouseScroll(yoffset); +} diff --git a/src/8.guest/2021/1.scene/2.frustum_culling/1.model_loading.fs b/src/8.guest/2021/1.scene/2.frustum_culling/1.model_loading.fs new file mode 100644 index 0000000..9da78b1 --- /dev/null +++ b/src/8.guest/2021/1.scene/2.frustum_culling/1.model_loading.fs @@ -0,0 +1,11 @@ +#version 330 core +out vec4 FragColor; + +in vec2 TexCoords; + +uniform sampler2D texture_diffuse1; + +void main() +{ + FragColor = texture(texture_diffuse1, TexCoords); +} \ No newline at end of file diff --git a/src/8.guest/2021/1.scene/2.frustum_culling/1.model_loading.vs b/src/8.guest/2021/1.scene/2.frustum_culling/1.model_loading.vs new file mode 100644 index 0000000..253b79e --- /dev/null +++ b/src/8.guest/2021/1.scene/2.frustum_culling/1.model_loading.vs @@ -0,0 +1,16 @@ +#version 330 core +layout (location = 0) in vec3 aPos; +layout (location = 1) in vec3 aNormal; +layout (location = 2) in vec2 aTexCoords; + +out vec2 TexCoords; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; + +void main() +{ + TexCoords = aTexCoords; + gl_Position = projection * view * model * vec4(aPos, 1.0); +} \ No newline at end of file diff --git a/src/8.guest/2021/1.scene/2.frustum_culling/frustum_culling.cpp b/src/8.guest/2021/1.scene/2.frustum_culling/frustum_culling.cpp new file mode 100644 index 0000000..fc83ad7 --- /dev/null +++ b/src/8.guest/2021/1.scene/2.frustum_culling/frustum_culling.cpp @@ -0,0 +1,236 @@ +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifndef ENTITY_H +#define ENTITY_H + +#include //std::list +#include //std::unique_ptr + +class Entity : public Model +{ +public: + list> children; + Entity* parent; +}; +#endif + + +#include + +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 +Camera camera(glm::vec3(0.0f, 10.0f, 0.0f)); +Camera cameraSpy(glm::vec3(0.0f, 10.0f, 0.f)); +float lastX = SCR_WIDTH / 2.0f; +float lastY = SCR_HEIGHT / 2.0f; +bool firstMouse = true; + +// timing +float deltaTime = 0.0f; +float lastFrame = 0.0f; + +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; + } + + // tell stb_image.h to flip loaded texture's on the y-axis (before loading model). + stbi_set_flip_vertically_on_load(true); + + // configure global opengl state + // ----------------------------- + glEnable(GL_DEPTH_TEST); + + camera.MovementSpeed = 20.f; + + // build and compile shaders + // ------------------------- + Shader ourShader("1.model_loading.vs", "1.model_loading.fs"); + + // load entities + // ----------- + Model model(FileSystem::getPath("resources/objects/planet/planet.obj")); + Entity ourEntity(model); + ourEntity.transform.setLocalPosition({ 0, 0, 0 }); + const float scale = 1.0; + ourEntity.transform.setLocalScale({ scale, scale, scale }); + + { + Entity* lastEntity = &ourEntity; + + for (unsigned int x = 0; x < 20; ++x) + { + for (unsigned int z = 0; z < 20; ++z) + { + ourEntity.addChild(model); + lastEntity = ourEntity.children.back().get(); + + //Set tranform values + lastEntity->transform.setLocalPosition({ x * 10.f - 100.f, 0.f, z * 10.f - 100.f }); + } + } + } + ourEntity.updateSelfAndChild(); + + // draw in wireframe + //glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + + // render loop + // ----------- + while (!glfwWindowShouldClose(window)) + { + // per-frame time logic + // -------------------- + float currentFrame = glfwGetTime(); + deltaTime = currentFrame - lastFrame; + lastFrame = currentFrame; + + // input + // ----- + processInput(window); + + // render + // ------ + glClearColor(0.05f, 0.05f, 0.05f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // don't forget to enable shader before setting uniforms + ourShader.use(); + + // view/projection transformations + glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 100.0f); + const Frustum camFrustum = createFrustumFromCamera(camera, (float)SCR_WIDTH / (float)SCR_HEIGHT, glm::radians(camera.Zoom), 0.1f, 100.0f); + + cameraSpy.ProcessMouseMovement(2, 0); + //static float acc = 0; + //acc += deltaTime * 0.0001; + //cameraSpy.Position = { cos(acc) * 10, 0.f, sin(acc) * 10 }; + glm::mat4 view = camera.GetViewMatrix(); + + ourShader.setMat4("projection", projection); + ourShader.setMat4("view", view); + + // draw our scene graph + unsigned int total = 0, display = 0; + ourEntity.drawSelfAndChild(camFrustum, ourShader, display, total); + std::cout << "Total process in CPU : " << total << " / Total send to GPU : " << display << std::endl; + + //ourEntity.transform.setLocalRotation({ 0.f, ourEntity.transform.getLocalRotation().y + 20 * deltaTime, 0.f }); + ourEntity.updateSelfAndChild(); + + // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.) + // ------------------------------------------------------------------------------- + glfwSwapBuffers(window); + glfwPollEvents(); + } + + // 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); + + if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) + camera.ProcessKeyboard(FORWARD, deltaTime); + if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) + camera.ProcessKeyboard(BACKWARD, deltaTime); + if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) + camera.ProcessKeyboard(LEFT, deltaTime); + if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) + camera.ProcessKeyboard(RIGHT, deltaTime); +} + +// 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 xpos, double ypos) +{ + 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; + + camera.ProcessMouseMovement(xoffset, yoffset); +} + +// glfw: whenever the mouse scroll wheel scrolls, this callback is called +// ---------------------------------------------------------------------- +void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) +{ + camera.ProcessMouseScroll(yoffset); +}