#include #include #include #include #include #include "bettola/noise/fast_noise_lite.h" #include "game/player.h" #include "game/world.h" #include "graphics/camera.h" #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif #include "renderer.h" #include "bettola/math/mat4.h" #define GL_CHECK_ERROR() \ do { \ GLenum err = glGetError(); \ if(err != GL_NO_ERROR) { \ fprintf(stderr, "OpenGL error at %s:%d: %s\n", __FILE__, __LINE__, \ (const char*)glewGetErrorString(err)); \ } \ } while(0) Renderer::Renderer(void) : _vao(0), _vbo(0), _sky_vao(0), _sky_vbo(0), _sky_ebo(0), _cloud_vao(0), _cloud_vbo(0), _cloud_texture(0), _sky_indices_count(0) {} Renderer::~Renderer(void) { if(_vao != 0) { glDeleteVertexArrays(1, &_vao); } if(_vbo != 0) { glDeleteBuffers(1, &_vbo); } if(_sky_vao != 0) { glDeleteVertexArrays(1, &_sky_vao); } if(_sky_vbo != 0) { glDeleteBuffers(1, &_sky_vbo); } if(_sky_ebo != 0) { glDeleteBuffers(1, &_sky_ebo); } if(_cloud_texture != 0) { glDeleteTextures(1, &_cloud_texture); } if(_cloud_vao != 0) { glDeleteVertexArrays(1, &_cloud_vao); } } bool Renderer::init(int screen_width, int screen_height) { glewExperimental = GL_TRUE; GLenum glew_error = glewInit(); if(glew_error != GLEW_OK) { fprintf(stderr, "Failed to init GLEW! %s\n", glewGetErrorString(glew_error)); return false; } if(!_init_shaders()) { return false; } if(!_sky_shader.load_from_files("assets/shaders/sky.vert", "assets/shaders/sky.frag")) { fprintf(stderr, "Failed to load sky shaders\n"); return false; } if(!_cloud_shader.load_from_files("assets/shaders/cloud.vert", "assets/shaders/cloud.frag")) { fprintf(stderr, "Failed to load cloud shaders\n"); return false; } if(!_init_textures()) { fprintf(stderr, "Failed to init textures\n"); return false; } // Definitive, correct 3D cube vertices float vertices[] = { /* Positions. */ /* Normals. */ -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, 0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, -0.5f, 0.5f, -0.5f, 0.0f, 0.0f, -1.0f, -0.5f, -0.5f, -0.5f, 0.0f, 0.0f, -1.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, 0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, -0.5f, 0.5f, 0.5f, 0.0f, 0.0f, 1.0f, -0.5f, -0.5f, 0.5f, 0.0f, 0.0f, 1.0f, -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, -0.5f, 0.5f, -0.5f, -1.0f, 0.0f, 0.0f, -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, -0.5f, -0.5f, -0.5f, -1.0f, 0.0f, 0.0f, -0.5f, -0.5f, 0.5f, -1.0f, 0.0f, 0.0f, -0.5f, 0.5f, 0.5f, -1.0f, 0.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.5f, 0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.5f, -0.5f, -0.5f, 1.0f, 0.0f, 0.0f, 0.5f, -0.5f, 0.5f, 1.0f, 0.0f, 0.0f, 0.5f, 0.5f, 0.5f, 1.0f, 0.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, 0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, -0.5f, -0.5f, 0.5f, 0.0f, -1.0f, 0.0f, -0.5f, -0.5f, -0.5f, 0.0f, -1.0f, 0.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, -0.5f, 0.5f, 0.5f, 0.0f, 1.0f, 0.0f, -0.5f, 0.5f, -0.5f, 0.0f, 1.0f, 0.0f }; 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, 6*sizeof(float), (void*)0); glEnableVertexAttribArray(0); /* Normal attribute. */ glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6*sizeof(float), (void*)(3*sizeof(float))); glEnableVertexAttribArray(1); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); /* Sky Sphere Generation. */ std::vector sky_vertices; std::vector sky_indices; const int segments = 32; const int rings = 32; for(int i = 0; i <= rings; i++) { float phi = i * M_PI / rings; for(int j = 0; j <= segments; j++) { float theta = j * 2.0f * M_PI / segments; float x = cos(theta) * sin(phi); float y = cos(phi); float z = sin(theta) * sin(phi); sky_vertices.push_back(x); sky_vertices.push_back(y); sky_vertices.push_back(z); } } for(int i = 0; i < rings; i++) { for(int j = 0; j < segments; j++) { int first = (i * (segments + 1)) + j; int second = first + segments + 1; sky_indices.push_back(first); sky_indices.push_back(second); sky_indices.push_back(first+1); sky_indices.push_back(second); sky_indices.push_back(second+1); sky_indices.push_back(first+1); } } _sky_indices_count = sky_indices.size(); glGenVertexArrays(1, &_sky_vao); glGenBuffers(1, &_sky_vbo); glGenBuffers(1, &_sky_ebo); glBindVertexArray(_sky_vao); glBindBuffer(GL_ARRAY_BUFFER, _sky_vbo); glBufferData(GL_ARRAY_BUFFER, sky_vertices.size()*sizeof(float), sky_vertices.data(), GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, _sky_ebo); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sky_indices.size()*sizeof(unsigned int), sky_indices.data(), GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); glViewport(0,0,screen_width, screen_height); GL_CHECK_ERROR(); glEnable(GL_DEPTH_TEST); /* Depth testing for 3D! */ GL_CHECK_ERROR(); return true; } bool Renderer::_init_textures(void) { _cloud_texture = _generate_cloud_texture(); if(_cloud_texture == 0) { return false; } return true; } unsigned int Renderer::_generate_cloud_texture(void) { const int width = 512; const int height = 512; std::vector buffer(width*height); FastNoiseLite noise; noise.SetNoiseType(FastNoiseLite::NoiseType_Perlin); noise.SetFractalType(FastNoiseLite::FractalType_FBm); noise.SetFractalOctaves(4); noise.SetFractalLacunarity(2.0f); noise.SetFractalGain(0.5f); noise.SetFrequency(0.05f); for(int y = 0; y < height; y++) { for(int x = 0; x < width; x++) { const float R = 1.0f; /* Major radius of the torus. */ const float r = 0.5f; /* Minor radius of the torus. */ float u = (float)x / width * 2.0f * M_PI; float v = (float)y / height * 2.0f * M_PI; float nx = (R + r * cos(v)) * cos(u); float ny = (R + r * cos(v)) * sin(u); float nz = r * sin(v); const float noise_scale = 45.0f; float noise_val = noise.GetNoise(nx * noise_scale, ny * noise_scale, nz * noise_scale); buffer[y*width+x] = (noise_val + 1.0f) / 2.0f; } } unsigned int texture_id; glGenTextures(1, &texture_id); glBindTexture(GL_TEXTURE_2D, texture_id); glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, width, height, 0, GL_RED, GL_FLOAT, buffer.data()); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); return texture_id; } void Renderer::_render_sky(const Camera& camera) { glDepthFunc(GL_LEQUAL); _sky_shader.use(); /* Remove translation from the view matrix so the skybox follows the camera. */ BettolaMath::Mat4 view = camera.get_view_matrix(); view.elements[12] = 0; view.elements[13] = 0; view.elements[14] = 0; _sky_shader.set_mat4("view", view); _sky_shader.set_mat4("projection", _projection); _sky_shader.set_vec3("u_SunPos", {0.0f, 1.0f, 0.0f}); glBindVertexArray(_sky_vao); glDrawElements(GL_TRIANGLES, _sky_indices_count, GL_UNSIGNED_INT, 0); glBindVertexArray(0); glDepthFunc(GL_LESS); /* Put depth function back to default. */ } void Renderer::_render_clouds(const Camera& camera) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_DEPTH_TEST); _cloud_shader.use(); /* Remove translation from the view matrix so clouds feel infintely far away. */ BettolaMath::Mat4 cloud_view = camera.get_view_matrix(); cloud_view.elements[12] = 0; cloud_view.elements[13] = 0; cloud_view.elements[14] = 0; _cloud_shader.set_mat4("projection", _projection); _cloud_shader.set_mat4("view", cloud_view); _cloud_shader.set_vec3("u_LightDir", {-0.5f, -1.0f, -0.5f}); _cloud_shader.set_vec3("u_SunPos", {0.0f, 1.0f, 0.0f}); glBindVertexArray(_sky_vao); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, _cloud_texture); /* Tell the shader to use texture unit 0. */ glUniform1i(glGetUniformLocation(_cloud_shader.get_id(), "u_CloudTexture"),0); /* Draw first cloud layer. */ BettolaMath::Mat4 model1 = BettolaMath::Mat4::scale(1.0f, 2.0f, 1.0f); _cloud_shader.set_mat4("model", model1); _cloud_shader.set_float("u_Time", (float)SDL_GetTicks() / 1000.0f); _cloud_shader.set_float("u_ScrollSpeed", 0.02f); glDrawElements(GL_TRIANGLES, _sky_indices_count, GL_UNSIGNED_INT, 0); /* Draw second cloud layer. */ BettolaMath::Mat4 model2 = BettolaMath::Mat4::scale(1.2f, 1.2f, 1.2f); model2 = model2.multiply(BettolaMath::Mat4::rotation(30.0f, {0.0f, 1.0f, 0.0f})); _cloud_shader.set_mat4("model", model2); _cloud_shader.set_float("u_ScrollSpeed", 0.03f); glDrawElements(GL_TRIANGLES, _sky_indices_count, GL_UNSIGNED_INT, 0); glBindVertexArray(0); glEnable(GL_DEPTH_TEST); glDisable(GL_BLEND); } void Renderer::render(const Camera& camera, const Player& player, const std::vector& remote_players, const World& world) { glClearColor(0.1f, 0.1f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); /* Need to clear depth buffer too. */ GL_CHECK_ERROR(); _projection = BettolaMath::Mat4::perspective((45.0f*M_PI)/180.0f, 800.0f/600.0f, 0.1f, 2000.0f); _render_sky(camera); _render_clouds(camera); _shader.use(); GL_CHECK_ERROR(); _shader.set_mat4("view", camera.get_view_matrix()); _shader.set_mat4(("projection"), _projection); /* Render world. */ /* Set lighting uniforms for terrain. */ _shader.set_vec3("lightColor", { 1.0f, 1.0f, 1.0f}); _shader.set_vec3("lightDir", {-0.5f, -1.0f, -0.5f}); /* From above and to the side. */ _shader.set_bool("u_IsTerrain", true); for(auto const& [pos, mesh] : world.get_chunk_meshes()) { BettolaMath::Mat4 model; _shader.set_mat4("model", model); mesh->draw(); } _shader.set_bool("u_IsTerrain", false); /* Draw the local player's cube. */ _shader.set_vec3("objectColor", {0.2f, 0.5f, 0.8f}); /* Player colour. */ const auto& player_pos = player.get_position(); BettolaMath::Mat4 trans_matrix = BettolaMath::Mat4::translation(player_pos.x, player_pos.y, player_pos.z); BettolaMath::Mat4 rot_matrix = BettolaMath::Mat4::rotation(-camera.get_yaw()-90.f, {0.0f, 1.0f, 0.0f}); BettolaMath::Mat4 model = trans_matrix.multiply(rot_matrix); _shader.set_mat4("model", model); GL_CHECK_ERROR(); glBindVertexArray(_vao); glDrawArrays(GL_TRIANGLES, 0, 36); glBindVertexArray(0); /* F.ck me! Forgot to unbind here too?!?!? */ /* Draw remote players' cube. */ for(const auto& remote_player : remote_players) { glBindVertexArray(_vao); /* bind cube VAO for each remote player. */ const auto& remote_pos = remote_player.get_position(); BettolaMath::Mat4 remote_trans = BettolaMath::Mat4::translation(remote_pos.x, remote_pos.y, remote_pos.z); BettolaMath::Mat4 remote_rot = BettolaMath::Mat4::rotation(-remote_player.get_yaw()-90.0f, {0.0f,1.0f,0.0f}); BettolaMath::Mat4 remote_model = remote_trans.multiply(remote_rot); _shader.set_mat4("model", remote_model); glDrawArrays(GL_TRIANGLES, 0, 36); glBindVertexArray(0); /* Unbind it! */ } } bool Renderer::_init_shaders(void) { if(!_shader.load_from_files("assets/shaders/simple.vert", "assets/shaders/simple.frag")) { return false; } GLint success; glGetProgramiv(_shader.get_id(), GL_LINK_STATUS, &success); if(!success) { char infoLog[512]; glGetProgramInfoLog(_shader.get_id(), 512, NULL, infoLog); fprintf(stderr, "Shader linking failed: %s\n", infoLog); return false; } return true; }