From 1c0ee1315f5d4860ec5eccfe309408cfa58be3a1 Mon Sep 17 00:00:00 2001 From: EmaMaker Date: Sun, 17 Sep 2023 10:40:55 +0200 Subject: [PATCH] renderer: render scene to a texture And then render the texture onto a quad that fills the screen The screen-filled quad upon which the texture is rendered must never be rendered in wireframe. Rendering in wireframe only makes sense when rendering the world on the texture --- include/renderer.hpp | 5 +- intervalmap.hpp | 143 +++++++++++++++++++++++++++++++++++++++++ shaders/shader-quad.fs | 10 +++ shaders/shader-quad.vs | 12 ++++ src/main.cpp | 6 +- src/renderer.cpp | 101 +++++++++++++++++++++++++++-- 6 files changed, 266 insertions(+), 11 deletions(-) create mode 100644 intervalmap.hpp create mode 100644 shaders/shader-quad.fs create mode 100644 shaders/shader-quad.vs diff --git a/include/renderer.hpp b/include/renderer.hpp index f8a7f69..7ade5b6 100644 --- a/include/renderer.hpp +++ b/include/renderer.hpp @@ -11,8 +11,9 @@ namespace renderer{ typedef oneapi::tbb::concurrent_unordered_set RenderSet; - void init(); - void render(); + void init(GLFWwindow* window); + void render(GLFWwindow* window); + void framebuffer_size_callback(GLFWwindow *window, int width, int height); void destroy(); Shader* getRenderShader(); RenderSet& getChunksToRender(); diff --git a/intervalmap.hpp b/intervalmap.hpp new file mode 100644 index 0000000..a5fa73f --- /dev/null +++ b/intervalmap.hpp @@ -0,0 +1,143 @@ +#ifndef INTERVALMAP_H +#define INTERVALMAP_H + +#include +#include //std::prev +#include // std::numeric_limits +#include //std::shared_ptr +#include + +template +class IntervalMap +{ + +public: + ~IntervalMap(){ + treemap.clear(); + } + + void insert(K start, K end, V value) + { + if (start >= end) + return; + + // The entry just before the end index + auto tmp = treemap.upper_bound(end); + auto end_prev_entry = tmp == treemap.end() ? tmp : --tmp; + auto added_end = treemap.end(); + + // If it doesn't exist (empty map) + if(end_prev_entry == treemap.end()){ + V v{}; + added_end = treemap.insert_or_assign(treemap.begin(), end, v); + } + // Or if it has value different from the insertion + else if(end_prev_entry->second != value) + // Add it back at the end + added_end = treemap.insert_or_assign(end_prev_entry, end, end_prev_entry->second); + + // The entry just before the start index + tmp = treemap.upper_bound(start); + auto start_prev_entry = tmp == treemap.end() ? tmp : --tmp; + auto added_start = treemap.end(); + // If it has value different from the insertion + if(start_prev_entry == treemap.end() || start_prev_entry->second != value) + // Add the start node of the insertion + added_start = treemap.insert_or_assign(start_prev_entry, start, value); + + // Delete everything else inside + // map.erase(start, end) deletes every node with key in the range [start, end) + + // The key to start deleting from is the key after the start node we added + // (We don't want to delete a node we just added) + auto del_start = added_start == treemap.end() ? std::next(start_prev_entry) : std::next(added_start); + auto del_end = added_end == treemap.end() ? end_prev_entry : added_end; + auto del_end_next = std::next(del_end); + + // If the node after the end is of the same type of the end, delete it + // We cannot just expand del_end (++del_end) otherwise interval limits get messed up + if(del_end != treemap.end() && del_end_next != treemap.end() && del_end->second == + del_end_next->second) treemap.erase(del_end_next); + + // Delete everything in between + if(del_start != treemap.end() && (del_end==treemap.end() || del_start->first < + del_end->first)) treemap.erase(del_start, del_end); + } + + void remove(K at) + { + treemap.erase(at); + } + + auto at(K index) + { + const auto tmp = treemap.lower_bound(index); + const auto r = tmp != treemap.begin() && tmp->first!=index ? std::prev(tmp) : tmp; + return r; + } + + void print() + { + for (auto i = treemap.begin(); i != treemap.end(); i++) + std::cout << i->first << ": " << (int)(i->second) << "\n"; + if(!treemap.empty()) std::cout << "end key: " << std::prev(treemap.end())->first << "\n"; + } + + std::unique_ptr toArray(int *length) + { + if (treemap.empty()) + { + *length = 0; + return nullptr; + } + + const auto &end = std::prev(treemap.end()); + *length = end->first; + if(*length == 0) return nullptr; + + std::unique_ptr arr(new V[*length]); + auto start = treemap.begin(); + for (auto i = std::next(treemap.begin()); i != treemap.end(); i++) + { + for (int k = start->first; k < i->first; k++) + arr[k] = start->second; + start = i; + } + + return arr; + } + + void fromArray(V *arr, int length) + { + treemap.clear(); + + if (length == 0) + return; + + V prev = arr[0]; + unsigned int prev_start = 0; + for (unsigned int i = 1; i < length; i++) + { + if (prev != arr[i]) + { + insert(prev_start, i, prev); + prev_start = i; + } + prev = arr[i]; + } + insert(prev_start, length, prev); + } + + auto begin(){ + return treemap.begin(); + } + + auto end(){ + return treemap.end(); + } + +private: + std::map treemap{}; +}; + +#endif diff --git a/shaders/shader-quad.fs b/shaders/shader-quad.fs new file mode 100644 index 0000000..c4dc9a6 --- /dev/null +++ b/shaders/shader-quad.fs @@ -0,0 +1,10 @@ +#version 330 core + +in vec2 TexCoord; +out vec4 FragColor; + +uniform sampler2D renderTex; + +void main(){ + FragColor = texture(renderTex, TexCoord); +} diff --git a/shaders/shader-quad.vs b/shaders/shader-quad.vs new file mode 100644 index 0000000..9292fc2 --- /dev/null +++ b/shaders/shader-quad.vs @@ -0,0 +1,12 @@ +#version 330 core + +layout (location = 0) in vec3 aPos; +layout (location = 1) in vec2 aTexCoord; + +out vec2 TexCoord; + +void main() +{ + TexCoord = aTexCoord; + gl_Position = vec4(aPos, 1.0); +} diff --git a/src/main.cpp b/src/main.cpp index ad75478..ea1172f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -57,7 +57,6 @@ int main() glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); glfwSetCursorPosCallback(window, mouse_callback); - glEnable(GL_DEPTH_TEST); //glEnable(GL_FRAMEBUFFER_SRGB); //gamma correction done in fragment shader //glEnable(GL_CULL_FACE); //GL_BACK GL_CCW by default @@ -70,7 +69,7 @@ int main() } SpaceFilling::initLUT(); chunkmanager::init(); - renderer::init(); + renderer::init(window); while (!glfwWindowShouldClose(window)) { @@ -100,7 +99,7 @@ int main() if(glfwGetTime() - lastBlockPick > 0.1) blockpick = false; // Render pass - renderer::render(); + renderer::render(window); // Swap buffers to avoid tearing glfwSwapBuffers(window); @@ -122,6 +121,7 @@ void framebuffer_size_callback(GLFWwindow *window, int width, int height) { glViewport(0, 0, width, height); theCamera.viewPortCallBack(window, width, height); + renderer::framebuffer_size_callback(window, width, height); } void mouse_callback(GLFWwindow *window, double xpos, double ypos) diff --git a/src/renderer.cpp b/src/renderer.cpp index f5b084c..9c27a65 100644 --- a/src/renderer.cpp +++ b/src/renderer.cpp @@ -13,18 +13,73 @@ namespace renderer{ oneapi::tbb::concurrent_vector render_todelete; oneapi::tbb::concurrent_queue MeshDataQueue; - Shader* theShader; + Shader* theShader, *quadShader; GLuint chunkTexture; Shader* getRenderShader() { return theShader; } RenderSet& getChunksToRender(){ return chunks_torender; } oneapi::tbb::concurrent_queue& getMeshDataQueue(){ return MeshDataQueue; } + GLuint renderTexFrameBuffer, renderTex, renderTexDepthBuffer, quadVAO, quadVBO; + int screenWidth, screenHeight; - void init(){ + + void init(GLFWwindow* window){ + // Setup rendering + // We will render the image to a texture, then display the texture on a quad that fills the + // entire screen. + // This makes it easy to capture screenshots or apply filters to the final image (e.g. + // over-impress HUD elements like a crosshair) + glfwGetWindowSize(window, &screenWidth, &screenHeight); + + glGenFramebuffers(1, &renderTexFrameBuffer); + glBindFramebuffer(GL_FRAMEBUFFER, renderTexFrameBuffer); + + // Depth buffer + glGenRenderbuffers(1, &renderTexDepthBuffer); + glBindRenderbuffer(GL_RENDERBUFFER, renderTexDepthBuffer); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, screenWidth, screenHeight); //Support up to + //full-hd for now + // Attach it to the frame buffer + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, + renderTexDepthBuffer); + // Create texture to render to + // The texture we're going to render to + glGenTextures(1, &renderTex); + glBindTexture(GL_TEXTURE_2D, renderTex); + glTexImage2D(GL_TEXTURE_2D, 0,GL_RGB, screenWidth, screenHeight, 0,GL_RGB, GL_UNSIGNED_BYTE, 0); // Support + // up to + // full-hd + // for now + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + // Set the texture as a render attachment for the framebuffer + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, renderTex, 0); + + // Create the quad to render the texture to + float vertices[] = { + -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, + -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, + 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, + 1.0f, 1.0f, 0.0f, 1.0f, 1.0f + }; + glGenBuffers(1, &quadVBO); + glGenVertexArrays(1, &quadVAO); + glBindVertexArray(quadVAO); + glBindBuffer(GL_ARRAY_BUFFER, quadVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void*)0); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5*sizeof(float), (void*)(3*sizeof(float))); + glEnableVertexAttribArray(0); + glEnableVertexAttribArray(1); + glBindVertexArray(0); + + // Rendering of the world // Create Shader theShader = new Shader{"shaders/shader-texture.gs", "shaders/shader-texture.vs", "shaders/shader-texture.fs"}; + quadShader = new Shader{nullptr, "shaders/shader-quad.vs", "shaders/shader-quad.fs"}; + // Block textures // Create 3d array texture constexpr int layerCount = 5; glGenTextures(1, &chunkTexture); @@ -49,7 +104,18 @@ namespace renderer{ glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_WRAP_T,GL_REPEAT); } - void render(){ + + void render(GLFWwindow* window){ + // Bind the frame buffer to render to the texture + glBindFramebuffer(GL_FRAMEBUFFER, renderTexFrameBuffer); + glEnable(GL_DEPTH_TEST); + if(wireframe) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + else glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + + // Clear the screen + glClearColor(0.431f, 0.694f, 1.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + int total{0}, toGpu{0}; glm::vec4 frustumPlanes[6]; theCamera.getFrustumPlanes(frustumPlanes, true); @@ -65,9 +131,6 @@ namespace renderer{ chunkmesher::getMeshDataQueue().push(m); } - if(wireframe) glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); - else glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); - for(auto& c : chunks_torender){ float dist = glm::distance(c->getPosition(), cameraChunkPos); if(dist <= static_cast(RENDER_DISTANCE)){ @@ -139,6 +202,32 @@ namespace renderer{ chunkmanager::getDeleteVector().push(c); } render_todelete.clear(); + + + // Now to render the quad, with the texture on top + // Switch to the default frame buffer + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glClearColor(0.431f, 0.694f, 1.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + + glBindVertexArray(quadVAO); + glDisable(GL_DEPTH_TEST); + glBindTexture(GL_TEXTURE_2D, renderTex); + quadShader->use(); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glBindVertexArray(0); + } + + void framebuffer_size_callback(GLFWwindow *window, int width, int height){ + screenWidth = width; + screenHeight = height; + + glBindTexture(GL_TEXTURE_2D, renderTex); + glTexImage2D(GL_TEXTURE_2D, 0,GL_RGB, screenWidth, screenHeight, 0,GL_RGB, GL_UNSIGNED_BYTE, 0); // Support + + glBindRenderbuffer(GL_RENDERBUFFER, renderTexDepthBuffer); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, screenWidth, screenHeight); //Support up to } void destroy(){