[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(GLEW 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 "gfx/txt_renderer.h"
|
||||
#include <GL/glew.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) {
|
||||
/* Init SDL. */
|
||||
if(!SDL_Init(SDL_INIT_VIDEO)) {
|
||||
@ -21,8 +20,8 @@ int main(int argc, char** argv) {
|
||||
/* Create a window. */
|
||||
SDL_Window* window = SDL_CreateWindow(
|
||||
"Bettola Client",
|
||||
1280,
|
||||
720,
|
||||
SCREEN_WIDTH,
|
||||
SCREEN_HEIGHT,
|
||||
SDL_WINDOW_OPENGL
|
||||
);
|
||||
|
||||
@ -46,7 +45,13 @@ int main(int argc, char** argv) {
|
||||
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;
|
||||
while(running) {
|
||||
@ -58,15 +63,21 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Rendering. */
|
||||
/* Clear screen. */
|
||||
glClearColor(0.1f, 0.1f, 0.1, 1.0f);
|
||||
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. */
|
||||
SDL_GL_SwapWindow(window);
|
||||
}
|
||||
|
||||
/* Cleanup. */
|
||||
delete txt_render_instance;
|
||||
SDL_GL_DestroyContext(context);
|
||||
SDL_DestroyWindow(window);
|
||||
SDL_Quit();
|
||||
|
||||
Loading…
Reference in New Issue
Block a user