306 lines
11 KiB
C++
306 lines
11 KiB
C++
#include "renderer.hpp"
|
|
|
|
#include <oneapi/tbb/concurrent_vector.h>
|
|
#include <oneapi/tbb/concurrent_queue.h>
|
|
|
|
#include "chunkmanager.hpp"
|
|
#include "chunkmesher.hpp"
|
|
#include "debugwindow.hpp"
|
|
#include "globals.hpp"
|
|
#include "stb_image.h"
|
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
|
#include "stb_image_write.h"
|
|
|
|
namespace renderer{
|
|
RenderSet chunks_torender;
|
|
oneapi::tbb::concurrent_vector<Chunk::Chunk*> render_todelete;
|
|
oneapi::tbb::concurrent_queue<chunkmesher::MeshData*> MeshDataQueue;
|
|
|
|
Shader* theShader, *quadShader;
|
|
GLuint chunkTexture;
|
|
|
|
Shader* getRenderShader() { return theShader; }
|
|
RenderSet& getChunksToRender(){ return chunks_torender; }
|
|
oneapi::tbb::concurrent_queue<chunkmesher::MeshData*>& getMeshDataQueue(){ return MeshDataQueue; }
|
|
|
|
GLuint renderTexFrameBuffer, renderTex, renderTexDepthBuffer, quadVAO, quadVBO;
|
|
int screenWidth, screenHeight;
|
|
|
|
int crosshair_type{0};
|
|
bool wireframe{false};
|
|
|
|
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);
|
|
glBindTexture(GL_TEXTURE_2D_ARRAY, chunkTexture);
|
|
|
|
int width, height, nrChannels;
|
|
unsigned char *texels = stbi_load("textures/cobblestone.png", &width, &height, &nrChannels, 0);
|
|
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, width, height, layerCount, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
|
|
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, texels);
|
|
unsigned char *texels1 = stbi_load("textures/dirt.png", &width, &height, &nrChannels, 0);
|
|
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 1, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, texels1);
|
|
unsigned char *texels2 = stbi_load("textures/grass_top.png", &width, &height, &nrChannels, 0);
|
|
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 2, width, height, 1, GL_RGB, GL_UNSIGNED_BYTE, texels2);
|
|
unsigned char *texels3 = stbi_load("textures/wood.png", &width, &height, &nrChannels, 0);
|
|
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 3, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, texels3);
|
|
unsigned char *texels4 = stbi_load("textures/leaves.png", &width, &height, &nrChannels, 0);
|
|
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 4, width, height, 1, GL_RGBA, GL_UNSIGNED_BYTE, texels4);
|
|
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_WRAP_S,GL_REPEAT);
|
|
glTexParameteri(GL_TEXTURE_2D_ARRAY,GL_TEXTURE_WRAP_T,GL_REPEAT);
|
|
|
|
debug::window::set_parameter("crosshair_type_return", &crosshair_type);
|
|
debug::window::set_parameter("wireframe_return", &wireframe);
|
|
}
|
|
|
|
|
|
void render(){
|
|
// Bind the frame buffer to render to the texture
|
|
glBindFramebuffer(GL_FRAMEBUFFER, renderTexFrameBuffer);
|
|
glViewport(0, 0, screenWidth, screenHeight);
|
|
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);
|
|
|
|
/* UPDATE IMGUI */
|
|
debug::window::prerender();
|
|
|
|
/* RENDER THE WORLD TO TEXTURE */
|
|
int total{0}, toGpu{0}, oof{0}, vertices{0};
|
|
glm::vec4 frustumPlanes[6];
|
|
theCamera.getFrustumPlanes(frustumPlanes, true);
|
|
glm::vec3 cameraPos = theCamera.getPos();
|
|
glm::vec3 cameraChunkPos = cameraPos / static_cast<float>(CHUNK_SIZE);
|
|
|
|
theShader->use();
|
|
theShader->setVec3("viewPos", cameraPos);
|
|
|
|
chunkmesher::MeshData* m;
|
|
while(MeshDataQueue.try_pop(m)){
|
|
chunkmesher::sendtogpu(m);
|
|
chunkmesher::getMeshDataQueue().push(m);
|
|
}
|
|
|
|
for(auto& c : chunks_torender){
|
|
float dist = glm::distance(c->getPosition(), cameraChunkPos);
|
|
if(dist <= static_cast<float>(RENDER_DISTANCE)){
|
|
if(!c->getState(Chunk::CHUNK_STATE_MESH_LOADED)) continue;
|
|
|
|
// Increase total vertex count
|
|
vertices += c->numVertices;
|
|
|
|
// reset out-of-vision and unload flags
|
|
c->setState(Chunk::CHUNK_STATE_OUTOFVISION, false);
|
|
c->setState(Chunk::CHUNK_STATE_UNLOADED, false);
|
|
|
|
// Perform frustum culling and eventually render
|
|
glm::vec3 chunk = c->getPosition();
|
|
glm::vec4 chunkW = glm::vec4(chunk.x*static_cast<float>(CHUNK_SIZE), chunk.y*static_cast<float>(CHUNK_SIZE), chunk.z*static_cast<float>(CHUNK_SIZE),1.0);
|
|
glm::mat4 model = glm::translate(glm::mat4(1.0), ((float)CHUNK_SIZE) * chunk);
|
|
|
|
// Check if all the corners of the chunk are outside any of the planes
|
|
// TODO (?) implement frustum culling as per (Inigo Quilez)[https://iquilezles.org/articles/frustumcorrect/], and check each
|
|
// plane against each corner of the chunk
|
|
bool out=false;
|
|
int a{0};
|
|
for(int p = 0; p < 6; p++){
|
|
a = 0;
|
|
for(int i = 0; i < 8; i++) a += glm::dot(frustumPlanes[p], glm::vec4(chunkW.x + ((float)(i & 1))*CHUNK_SIZE, chunkW.y
|
|
+ ((float)((i & 2) >> 1))*CHUNK_SIZE, chunkW.z + ((float)((i & 4) >> 2))*CHUNK_SIZE, 1.0)) < 0.0;
|
|
|
|
if(a==8){
|
|
out=true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!out)
|
|
{
|
|
if(c->numVertices > 0)
|
|
{
|
|
theShader->setMat4("model", model);
|
|
theShader->setMat4("view", theCamera.getView());
|
|
theShader->setMat4("projection", theCamera.getProjection());
|
|
|
|
glBindVertexArray(c->VAO);
|
|
glDrawArrays(GL_POINTS, 0, c->numVertices);
|
|
glBindVertexArray(0);
|
|
|
|
toGpu++;
|
|
}
|
|
}
|
|
}else{
|
|
// When the chunk is outside render distance
|
|
|
|
if(c->getState(Chunk::CHUNK_STATE_OUTOFVISION)){
|
|
oof++;
|
|
if(glfwGetTime() - c->unload_timer > UNLOAD_TIMEOUT){
|
|
// If chunk was already out and enough time has passed
|
|
// Mark the chunk to be unloaded
|
|
// And mark is to be removed from the render set
|
|
render_todelete.push_back(c);
|
|
}
|
|
} else{
|
|
// Mark has out of vision and annotate when it started
|
|
c->setState(Chunk::CHUNK_STATE_OUTOFVISION, true);
|
|
c->setState(Chunk::CHUNK_STATE_UNLOADED, false);
|
|
c->unload_timer = glfwGetTime();
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
total = chunks_torender.size();
|
|
debug::window::set_parameter("render_chunks_total", total);
|
|
debug::window::set_parameter("render_chunks_rendered", toGpu);
|
|
debug::window::set_parameter("render_chunks_culled", total-toGpu);
|
|
debug::window::set_parameter("render_chunks_oof", oof);
|
|
debug::window::set_parameter("render_chunks_deleted", (int) (render_todelete.size()));
|
|
debug::window::set_parameter("render_chunks_vertices", vertices);
|
|
|
|
for(auto& c : render_todelete){
|
|
// we can get away with unsafe erase as access to the container is only done by this
|
|
// thread
|
|
c->deleteBuffers();
|
|
chunks_torender.unsafe_erase(c);
|
|
chunkmanager::getDeleteVector().push(c);
|
|
}
|
|
render_todelete.clear();
|
|
|
|
/* DISPLAY TEXTURE ON A QUAD THAT FILLS THE SCREEN */
|
|
// 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();
|
|
quadShader->setInt("screenWidth", screenWidth);
|
|
quadShader->setInt("screenHeight", screenHeight);
|
|
quadShader->setInt("crosshairType", crosshair_type);
|
|
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
|
glBindVertexArray(0);
|
|
|
|
debug::window::render();
|
|
}
|
|
|
|
void framebuffer_size_callback(GLFWwindow *window, int width, int height){
|
|
resize_framebuffer(width, height);
|
|
}
|
|
|
|
void resize_framebuffer(int width, int height){
|
|
screenWidth = width;
|
|
screenHeight = height;
|
|
|
|
theCamera.viewPortCallBack(nullptr, width, height);
|
|
|
|
glBindFramebuffer(GL_FRAMEBUFFER, renderTexFrameBuffer);
|
|
glBindTexture(GL_TEXTURE_2D, renderTex);
|
|
glTexImage2D(GL_TEXTURE_2D, 0,GL_RGB, width, height, 0,GL_RGB, GL_UNSIGNED_BYTE, 0); // Support
|
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, renderTex, 0);
|
|
|
|
glBindRenderbuffer(GL_RENDERBUFFER, renderTexDepthBuffer);
|
|
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height); //Support up to
|
|
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER,
|
|
renderTexDepthBuffer);
|
|
}
|
|
|
|
void saveScreenshot(bool forceFullHD){
|
|
int old_screenWidth = screenWidth;
|
|
int old_screenHeight = screenHeight;
|
|
|
|
if(forceFullHD){
|
|
resize_framebuffer(1920, 1080);
|
|
// Do a render pass
|
|
render();
|
|
}
|
|
|
|
// Bind the render frame buffer
|
|
glBindFramebuffer(GL_FRAMEBUFFER, renderTexFrameBuffer);
|
|
glPixelStorei(GL_PACK_ALIGNMENT, 1);
|
|
// Save the framebuffer in a byte array
|
|
GLubyte data[screenWidth*screenHeight*3];
|
|
glReadPixels(0, 0, screenWidth, screenHeight, GL_RGB, GL_UNSIGNED_BYTE, data);
|
|
// Save the byte array onto a texture
|
|
stbi_flip_vertically_on_write(1);
|
|
stbi_write_png(forceFullHD ? "screenshot_fullhd.png" : "screenshot.png", screenWidth,
|
|
screenHeight, 3, data, screenWidth*3);
|
|
|
|
if(forceFullHD) resize_framebuffer(old_screenWidth, old_screenHeight);
|
|
}
|
|
|
|
void destroy(){
|
|
delete theShader;
|
|
delete quadShader;
|
|
}
|
|
|
|
|
|
};
|