[Add] Text rendering.
This commit is contained in:
parent
c3ebd0e501
commit
f86015736d
BIN
assets/fonts/hack/Hack-Bold.ttf
Normal file
BIN
assets/fonts/hack/Hack-Bold.ttf
Normal file
Binary file not shown.
BIN
assets/fonts/hack/Hack-BoldItalic.ttf
Normal file
BIN
assets/fonts/hack/Hack-BoldItalic.ttf
Normal file
Binary file not shown.
BIN
assets/fonts/hack/Hack-Italic.ttf
Normal file
BIN
assets/fonts/hack/Hack-Italic.ttf
Normal file
Binary file not shown.
BIN
assets/fonts/hack/Hack-Regular.ttf
Normal file
BIN
assets/fonts/hack/Hack-Regular.ttf
Normal file
Binary file not shown.
11
assets/shaders/text.frag
Normal file
11
assets/shaders/text.frag
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#version 330 core
|
||||||
|
in vec2 TexCoords;
|
||||||
|
out vec4 color;
|
||||||
|
|
||||||
|
uniform sampler2D text;
|
||||||
|
uniform vec3 textColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r);
|
||||||
|
color = vec4(textColor, 1.0) * sampled;
|
||||||
|
}
|
||||||
11
assets/shaders/text.vert
Normal file
11
assets/shaders/text.vert
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
#version 330 core
|
||||||
|
layout(location = 0) in vec4 vertex;
|
||||||
|
|
||||||
|
out vec2 TexCoords;
|
||||||
|
|
||||||
|
uniform mat4 projection;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = projection * vec4(vertex.xy, 0.0, 1.0);
|
||||||
|
TexCoords = vertex.zw;
|
||||||
|
}
|
||||||
@ -7,5 +7,6 @@ add_executable(bettolac
|
|||||||
find_package(SDL3 REQUIRED)
|
find_package(SDL3 REQUIRED)
|
||||||
find_package(GLEW REQUIRED)
|
find_package(GLEW REQUIRED)
|
||||||
find_package(OpenGL REQUIRED)
|
find_package(OpenGL REQUIRED)
|
||||||
|
find_package(Freetype REQUIRED)
|
||||||
|
|
||||||
target_link_libraries(bettolac PRIVATE bettola SDL3::SDL3 GLEW::glew OpenGL::GL)
|
target_link_libraries(bettolac PRIVATE bettola SDL3::SDL3 GLEW::glew OpenGL::GL Freetype::Freetype)
|
||||||
|
|||||||
88
client/src/gfx/shader.cpp
Normal file
88
client/src/gfx/shader.cpp
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
#include <cstdio>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <GL/glew.h>
|
||||||
|
#include "shader.h"
|
||||||
|
|
||||||
|
/* Read whole file. */
|
||||||
|
char* read_file(const char* path) {
|
||||||
|
FILE* file = fopen(path, "rb");
|
||||||
|
if(!file) {
|
||||||
|
printf("Failed to open file: %s\n", path);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
fseek(file, 0, SEEK_END);
|
||||||
|
long len = ftell(file);
|
||||||
|
fseek(file, 0, SEEK_SET);
|
||||||
|
char* buffer = (char*)malloc(len+1);
|
||||||
|
fread(buffer, 1, len, file);
|
||||||
|
fclose(file);
|
||||||
|
buffer[len] = '\0';
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
Shader::Shader(const char* vert_path, const char* frag_path) {
|
||||||
|
char* vert_source = read_file(vert_path);
|
||||||
|
char* frag_source = read_file(frag_path);
|
||||||
|
if(!vert_source || !frag_source) {
|
||||||
|
printf("Failed to read shader files.\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int vert, frag;
|
||||||
|
vert = glCreateShader(GL_VERTEX_SHADER);
|
||||||
|
glShaderSource(vert, 1, &vert_source, NULL);
|
||||||
|
glCompileShader(vert);
|
||||||
|
_check_compile_errors(vert, "VERTEX");
|
||||||
|
|
||||||
|
frag = glCreateShader(GL_FRAGMENT_SHADER);
|
||||||
|
glShaderSource(frag, 1, &frag_source, NULL);
|
||||||
|
glCompileShader(frag);
|
||||||
|
_check_compile_errors(frag, "FRAGMENT");
|
||||||
|
|
||||||
|
id = glCreateProgram();
|
||||||
|
glAttachShader(id, vert);
|
||||||
|
glAttachShader(id, frag);
|
||||||
|
glLinkProgram(id);
|
||||||
|
_check_compile_errors(id, "PROGRAM");
|
||||||
|
|
||||||
|
glDeleteShader(vert);
|
||||||
|
glDeleteShader(frag);
|
||||||
|
free(vert_source);
|
||||||
|
free(frag_source);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shader::use(void) {
|
||||||
|
glUseProgram(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shader::set_mat4(const char* name, const float* value) {
|
||||||
|
glUniformMatrix4fv(glGetUniformLocation(id, name), 1, GL_FALSE, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shader::set_vec3(const char* name, float v1, float v2, float v3) {
|
||||||
|
glUniform3f(glGetUniformLocation(id, name), v1, v2, v3);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shader::set_int(const char* name, int value) {
|
||||||
|
glUniform1i(glGetUniformLocation(id, name), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Shader::_check_compile_errors(unsigned int shader_id, const char* type) {
|
||||||
|
int success;
|
||||||
|
char info_log[1024];
|
||||||
|
if(strcmp(type, "PROGRAM") != 0) {
|
||||||
|
glGetShaderiv(shader_id, GL_COMPILE_STATUS, &success);
|
||||||
|
if(!success) {
|
||||||
|
glGetShaderInfoLog(shader_id, 1024, NULL, info_log);
|
||||||
|
printf("Shader compilation error (%s):\n%s\n", type, info_log);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
glGetProgramiv(shader_id, GL_LINK_STATUS, &success);
|
||||||
|
if(!success) {
|
||||||
|
glGetProgramInfoLog(shader_id, 1024, NULL, info_log);
|
||||||
|
printf("Shader linking error (%s):\n%s\n", type, info_log);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
16
client/src/gfx/shader.h
Normal file
16
client/src/gfx/shader.h
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
class Shader {
|
||||||
|
public:
|
||||||
|
unsigned int id;
|
||||||
|
|
||||||
|
Shader(const char* vertex_path, const char* fragment_path);
|
||||||
|
|
||||||
|
void use(void);
|
||||||
|
void set_mat4(const char* name, const float* value);
|
||||||
|
void set_vec3(const char* name, float v1, float v2, float v3);
|
||||||
|
void set_int(const char* name, int value);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void _check_compile_errors(unsigned int shader, const char* type);
|
||||||
|
};
|
||||||
141
client/src/gfx/txt_renderer.cpp
Normal file
141
client/src/gfx/txt_renderer.cpp
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
#include <cstdio>
|
||||||
|
#include <GL/glew.h>
|
||||||
|
#include <SDL3/SDL_render.h>
|
||||||
|
#include <freetype/freetype.h>
|
||||||
|
|
||||||
|
#include "txt_renderer.h"
|
||||||
|
|
||||||
|
/* Not sure we'll need a whole math lib? Basic ortho for now. */
|
||||||
|
void ortho(float* mat, float left, float right, float bottom, float top,
|
||||||
|
float near, float far) {
|
||||||
|
|
||||||
|
mat[0] = 2.0f / (right - left);
|
||||||
|
mat[4] = 0.0f;
|
||||||
|
mat[8] = 0.0f;
|
||||||
|
mat[12] = -(right + left) / (right - left);
|
||||||
|
|
||||||
|
mat[1] = 0.0f;
|
||||||
|
mat[5] = 2.0f / (top - bottom);
|
||||||
|
mat[9] = 0.0f;
|
||||||
|
mat[13] = -(top + bottom) / (top - bottom);
|
||||||
|
|
||||||
|
mat[2] = 0.0f;
|
||||||
|
mat[6] = 0.0f;
|
||||||
|
mat[10] = -2.0f / (far - near);
|
||||||
|
mat[14] = -(far + near) / (far - near);
|
||||||
|
|
||||||
|
mat[3] = 0.0f;
|
||||||
|
mat[7] = 0.0f;
|
||||||
|
mat[11] = 0.0f;
|
||||||
|
mat[15] = 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextRenderer::TextRenderer(unsigned int screen_width, unsigned int screen_height) {
|
||||||
|
/* Create projection matrix. */
|
||||||
|
ortho(_projecton, 0.0f, (float)screen_width, 0.0f, (float)screen_height, -1.0f, 1.0f);
|
||||||
|
|
||||||
|
/* Load shader. */
|
||||||
|
_txt_shader = new Shader("assets/shaders/text.vert",
|
||||||
|
"assets/shaders/text.frag");
|
||||||
|
_txt_shader->use();
|
||||||
|
_txt_shader->set_mat4("projection", _projecton);
|
||||||
|
|
||||||
|
/* Configure VAO/VBO for texture coords. */
|
||||||
|
glGenVertexArrays(1, &_vao);
|
||||||
|
glGenBuffers(1, &_vbo);
|
||||||
|
glBindVertexArray(_vao);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, _vbo);
|
||||||
|
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
|
||||||
|
glEnableVertexAttribArray(0);
|
||||||
|
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(float), 0);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||||
|
glBindVertexArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
TextRenderer::~TextRenderer(void) {
|
||||||
|
delete _txt_shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextRenderer::load_font(const char* font_path, unsigned int font_size) {
|
||||||
|
_chars.clear();
|
||||||
|
FT_Library ft;
|
||||||
|
if(FT_Init_FreeType(&ft)) {
|
||||||
|
printf("Could not init FreeType Library\n");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FT_Face face;
|
||||||
|
if(FT_New_Face(ft, font_path, 0, &face)) {
|
||||||
|
printf("Failed to load font: %s\n", font_path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FT_Set_Pixel_Sizes(face, 0, font_size);
|
||||||
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||||
|
|
||||||
|
for(unsigned char c = 0; c < 128; c++) {
|
||||||
|
if(FT_Load_Char(face, c, FT_LOAD_RENDER)) {
|
||||||
|
printf("Failed to load Glyph for char %c\n", c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned int texture;
|
||||||
|
glGenTextures(1, &texture);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, texture);
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, face->glyph->bitmap.width,
|
||||||
|
face->glyph->bitmap.rows, 0, GL_RED, GL_UNSIGNED_BYTE,
|
||||||
|
face->glyph->bitmap.buffer);
|
||||||
|
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
|
||||||
|
character ch = {
|
||||||
|
texture,
|
||||||
|
{ (int)face->glyph->bitmap.width, (int)face->glyph->bitmap.rows },
|
||||||
|
{ face->glyph->bitmap_left, face->glyph->bitmap_top },
|
||||||
|
(unsigned int)face->glyph->advance.x
|
||||||
|
};
|
||||||
|
_chars.insert(std::pair<char, character>(c, ch));
|
||||||
|
}
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
|
||||||
|
FT_Done_Face(face);
|
||||||
|
FT_Done_FreeType(ft);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextRenderer::render_text(const char* text, float x, float y, float scale,
|
||||||
|
float color[3]) {
|
||||||
|
_txt_shader->use();
|
||||||
|
_txt_shader->set_vec3("textColor", color[0], color[1], color[2]);
|
||||||
|
glActiveTexture(GL_TEXTURE0);
|
||||||
|
glBindVertexArray(_vao);
|
||||||
|
|
||||||
|
for(const char* p = text; *p; p++) {
|
||||||
|
character ch = _chars[*p];
|
||||||
|
|
||||||
|
float xpos = x + ch.bearing[0] * scale;
|
||||||
|
float ypos = y - (ch.size[1] - ch.bearing[1]) * scale;
|
||||||
|
float w = ch.size[0] * scale;
|
||||||
|
float h = ch.size[1] * scale;
|
||||||
|
|
||||||
|
float vertices[6][4] = {
|
||||||
|
{ xpos, ypos + h, 0.0f, 0.0f },
|
||||||
|
{ xpos, ypos, 0.0f, 1.0f },
|
||||||
|
{ xpos + w, ypos, 1.0f, 1.0f },
|
||||||
|
{ xpos, ypos + h, 0.0f, 0.0f },
|
||||||
|
{ xpos + w, ypos, 1.0f, 1.0f },
|
||||||
|
{ xpos + w, ypos + h, 1.0f, 0.0f }
|
||||||
|
};
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, ch.texture_id);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, _vbo);
|
||||||
|
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||||
|
glDrawArrays(GL_TRIANGLES, 0, 6);
|
||||||
|
x += (ch.advance >> 6) * scale;
|
||||||
|
}
|
||||||
|
glBindVertexArray(0);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
}
|
||||||
30
client/src/gfx/txt_renderer.h
Normal file
30
client/src/gfx/txt_renderer.h
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <ft2build.h>
|
||||||
|
#include FT_FREETYPE_H
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
#include "shader.h"
|
||||||
|
|
||||||
|
/* State of a single charactrer glyph. */
|
||||||
|
struct character {
|
||||||
|
unsigned int texture_id;
|
||||||
|
int size[2];
|
||||||
|
int bearing[2];
|
||||||
|
unsigned int advance;
|
||||||
|
};
|
||||||
|
|
||||||
|
class TextRenderer {
|
||||||
|
public:
|
||||||
|
TextRenderer(unsigned int screen_width, unsigned int screen_height);
|
||||||
|
~TextRenderer(void);
|
||||||
|
|
||||||
|
void load_font(const char* font_path, unsigned int font_size);
|
||||||
|
void render_text(const char* text, float x, float y, float scale, float color[3]);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Shader* _txt_shader;
|
||||||
|
unsigned int _vao, _vbo;
|
||||||
|
std::map<char, character> _chars;
|
||||||
|
float _projecton[16];
|
||||||
|
};
|
||||||
@ -1,11 +1,10 @@
|
|||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
|
#include "gfx/txt_renderer.h"
|
||||||
#include <GL/glew.h>
|
#include <GL/glew.h>
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <SDL3/SDL_error.h>
|
|
||||||
#include <SDL3/SDL_events.h>
|
|
||||||
#include <SDL3/SDL_oldnames.h>
|
|
||||||
#include <SDL3/SDL_video.h>
|
|
||||||
|
|
||||||
|
const int SCREEN_WIDTH = 1280;
|
||||||
|
const int SCREEN_HEIGHT = 720;
|
||||||
int main(int argc, char** argv) {
|
int main(int argc, char** argv) {
|
||||||
/* Init SDL. */
|
/* Init SDL. */
|
||||||
if(!SDL_Init(SDL_INIT_VIDEO)) {
|
if(!SDL_Init(SDL_INIT_VIDEO)) {
|
||||||
@ -21,8 +20,8 @@ int main(int argc, char** argv) {
|
|||||||
/* Create a window. */
|
/* Create a window. */
|
||||||
SDL_Window* window = SDL_CreateWindow(
|
SDL_Window* window = SDL_CreateWindow(
|
||||||
"Bettola Client",
|
"Bettola Client",
|
||||||
1280,
|
SCREEN_WIDTH,
|
||||||
720,
|
SCREEN_HEIGHT,
|
||||||
SDL_WINDOW_OPENGL
|
SDL_WINDOW_OPENGL
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -46,7 +45,13 @@ int main(int argc, char** argv) {
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("SDL/OpenGL initialisation succes.\n");
|
/* Configure OpenGL. */
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
|
/* Init text renderer. */
|
||||||
|
TextRenderer* txt_render_instance = new TextRenderer(SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||||
|
txt_render_instance->load_font("assets/fonts/hack/Hack-Regular.ttf", 24);
|
||||||
|
|
||||||
bool running = true;
|
bool running = true;
|
||||||
while(running) {
|
while(running) {
|
||||||
@ -58,15 +63,21 @@ int main(int argc, char** argv) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Rendering. */
|
/* Clear screen. */
|
||||||
glClearColor(0.1f, 0.1f, 0.1, 1.0f);
|
glClearColor(0.1f, 0.1f, 0.1, 1.0f);
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
|
/* Render text. */
|
||||||
|
float white[] = { 1.0f, 1.0f, 1.0f };
|
||||||
|
txt_render_instance->render_text("Hello World", 25.0f, SCREEN_HEIGHT - 50.0f,
|
||||||
|
1.0f, white);
|
||||||
|
|
||||||
/* It's really odd to call it SwapWindow now, rather than SwapBuffer. */
|
/* It's really odd to call it SwapWindow now, rather than SwapBuffer. */
|
||||||
SDL_GL_SwapWindow(window);
|
SDL_GL_SwapWindow(window);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Cleanup. */
|
/* Cleanup. */
|
||||||
|
delete txt_render_instance;
|
||||||
SDL_GL_DestroyContext(context);
|
SDL_GL_DestroyContext(context);
|
||||||
SDL_DestroyWindow(window);
|
SDL_DestroyWindow(window);
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user