bettola/src/graphics/renderer.cpp

398 lines
13 KiB
C++

#include <GL/glew.h>
#include <climits>
#include <cstdio>
#include <vector>
#include <cmath>
#include "bettola/noise/fast_noise_lite.h"
#include "game/player.h"
#include "game/world.h"
#include "graphics/camera.h"
#include <SDL3/SDL_timer.h>
#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<float> sky_vertices;
std::vector<unsigned int> 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<float> 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<RemotePlayer>& 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;
}