From ba43db84ce7e80449c505c1381b33786cc047131 Mon Sep 17 00:00:00 2001 From: talha Date: Sun, 28 Apr 2024 15:48:28 +0500 Subject: Basic text rendering is now working --- source/main.cpp | 314 +++++++++++++++++++++++++++++----- source/math.h | 32 +++- source/shaders/ui_text_shader.fs.glsl | 12 ++ source/shaders/ui_text_shader.vs.glsl | 12 ++ 4 files changed, 327 insertions(+), 43 deletions(-) create mode 100644 source/shaders/ui_text_shader.fs.glsl create mode 100644 source/shaders/ui_text_shader.vs.glsl (limited to 'source') diff --git a/source/main.cpp b/source/main.cpp index 7deb893..6df5451 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" @@ -50,6 +51,8 @@ typedef u8 b8; // =========== Shader Loading ============= +#define GL2D_UScale(x) (Vec2{(x), (x)}) + enum Texture_Filtering { TF_NEAREST, TF_LINEAR }; struct GlTexturedQuad { @@ -211,7 +214,7 @@ s32 gl_load_texture(u32 texture_id, char *path, enum Texture_Filtering filter) return 0; } -GlTexturedQuad gl_setup_textured_quad(char* texture_path, enum Texture_Filtering filter) +GlTexturedQuad gl2d_setup_textured_quad(char* texture_path, enum Texture_Filtering filter) { // rendering is clock-wise r32 quad_vertices[] = { @@ -247,11 +250,32 @@ GlTexturedQuad gl_setup_textured_quad(char* texture_path, enum Texture_Filtering return tex_quad; } -void gl_draw_tex_quad(u32 shader_program, GlTexturedQuad tex_quad) +void gl2d_draw_tex_quad(GlShader shader, GlTexturedQuad tex_quad, Vec3 position, Vec2 scale, r32 angle) { - glUseProgram(shader_program); - s32 tex_id_loc = glGetUniformLocation(shader_program, "Texture"); + glUseProgram(shader.id); + s32 tex_id_loc = glGetUniformLocation(shader.id, "Texture"); glUniform1i(tex_id_loc, 0); + + // 1. Rotation + Mat4 model = init_value4m(1.0f); + if (angle) + { + Mat4 rotation = rotation_matrix4m(angle, Vec3{0.0f, 0.0f, 1.0f}); + model = multiply4m(rotation, model); + } + // 2. Scale + if (scale.x != 1.0f || scale.y != 1.0f) + { + Mat4 scaling = scaling_matrix4m(scale.x, scale.y, 1.0f); + model = multiply4m(scaling, model); + } + // 3. Translation + Mat4 translation = translation_matrix4m(position.x, position.y, position.z); + model = multiply4m(translation, model); + + // sending model to the shader + glUniformMatrix4fv(shader.model_loc, 1, GL_TRUE, model.buffer); + glBindVertexArray(tex_quad.vao); glBindTexture(GL_TEXTURE_2D, tex_quad.texture_id); glDrawArrays(GL_TRIANGLES, 0, 6); @@ -285,20 +309,76 @@ Vec3 camera_look_around(r32 angle_pitch, r32 angle_yaw) return camera_look; } +struct TextChar { + u32 texture_id; + Vec2 size; + Vec2 bearing; + s64 advance; +}; + +struct CameraOrtho { + r32 left; + r32 right; + r32 bottom; + r32 top; + r32 near; + r32 far; + Vec2 resolution; + Vec2 dimensions; + Mat4 projection; +}; + +CameraOrtho camera_orthographic(r32 height, Vec2 resolution, r32 near, r32 far) +{ + CameraOrtho camera = { 0 }; + + r32 aspect_ratio = resolution.x / resolution.y; + r32 width = height * aspect_ratio; + + r32 left = -width/2.0f; + r32 right = width/2.0f; + + r32 bottom = -height/2.0f; + r32 top = height/2.0f; + + camera.resolution = resolution; + camera.dimensions = Vec2{width, height}; + camera.left = left; + camera.right = right; + camera.bottom = bottom; + camera.top = top; + camera.near = near; + camera.far = far; + + camera.projection = orthographic_projection4m(left, right, bottom, top, near, far); + + return camera; +} + +Vec2 camera_from_screen(CameraOrtho camera, Vec2 position) +{ + Vec2 camera_space = { 0 }; + + camera_space.x = camera.left + position.x * (camera.dimensions.x/camera.resolution.x); + camera_space.y = camera.top - position.y * (camera.dimensions.y/camera.resolution.y); + + return camera_space; +} + int main(int argc, char* argv[]) { // Load freetype - FT_Library library; + FT_Library ft_lib; FT_Face face; - if (FT_Init_FreeType(&library)) + if (FT_Init_FreeType(&ft_lib)) { printf("Error: Could not init freetype library\n"); return -1; } - FT_Error error = FT_New_Face(library, "assets/fonts/Arial.ttf", 0, &face); + FT_Error error = FT_New_Face(ft_lib, "assets/fonts/Arial.ttf", 0, &face); if (error == FT_Err_Unknown_File_Format) { printf("Error: Font Loading Failed. The font format is unsupported.\n"); @@ -310,8 +390,6 @@ int main(int argc, char* argv[]) return -1; } - FT_Set_Pixel_Sizes(face, 0, 48); - int width = 1024; int height = 768; @@ -353,7 +431,7 @@ int main(int argc, char* argv[]) } // vsync controls: 0 = OFF | 1 = ON (Default) - // SDL_GL_SetSwapInterval(0); + SDL_GL_SetSwapInterval(1); // filesystem playground stuff size_t read_count; @@ -361,20 +439,83 @@ int main(int argc, char* argv[]) tex_quad_shader.id = gl_shader_program_from_path("./source/shaders/textured_quad.vs.glsl", "./source/shaders/textured_quad.fs.glsl"); gl_shader_load_locations(&tex_quad_shader); - GlTexturedQuad tex_quad1 = gl_setup_textured_quad("./assets/smiling.png", TF_LINEAR); - GlTexturedQuad tex_quad2 = gl_setup_textured_quad("./assets/container.jpg", TF_LINEAR); + u32 ui_text_sp = gl_shader_program_from_path("./source/shaders/ui_text_shader.vs.glsl", "./source/shaders/ui_text_shader.fs.glsl"); + + GlTexturedQuad tex_quad1 = gl2d_setup_textured_quad("./assets/smiling.png", TF_LINEAR); + GlTexturedQuad tex_quad2 = gl2d_setup_textured_quad("./assets/container.jpg", TF_LINEAR); - // objects - Vec3 model_translations[] = { - Vec3{ 0.5, 0.0, 0.0}, // 0: origin square - Vec3{ -1.0, 0.0, -1.0}, // 1: plane - Vec3{ -1.0, 0.0, -0.5}, // 2: window between squares - Vec3{ 0.0, 0.0, 3.0}, // 3: window infront of origin square - Vec3{ -2.5, 0.0, -0.5}, // 4: square to the left - Vec3{ -1.0, 0.0, -1.5}, // 5: random square behind window between squares - Vec3{ -1.0, 0.0, -8.0}, // 6: reflective plane - Vec3{ -1.0, 2.0, -8.0}, // 6: refractive "window" - }; + // ==================== + // setup_text + // debug only + std::map all_text_chars; + + FT_Set_Pixel_Sizes(face, 0, 48); + + glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + // generate characters + for (unsigned char c = 0; c < 128; c++) + { + if (FT_Load_Char(face, c, FT_LOAD_RENDER)) + { + printf("Error: Freetype failed to load glyph: %c", c); + } + else + { + // generate texture + u32 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 + ); + // set texture options + 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); + + TextChar tc = { + texture, + Vec2{(r32)face->glyph->bitmap.width, (r32)face->glyph->bitmap.rows}, + Vec2{(r32)face->glyph->bitmap_left, (r32)face->glyph->bitmap_top}, + face->glyph->advance.x + }; + + all_text_chars.insert(std::pair(c, tc)); + } + + } + r32 vertices[] = { + 0.0f, 0.0f, + 0.0f, 1.0f, + 1.0f, 1.0f, + + 0.0f, 0.0f, + 1.0f, 1.0f, + 1.0f, 0.0f + }; + + u32 vao, vbo; + glGenVertexArrays(1, &vao); + glGenBuffers(1, &vbo); + glBindVertexArray(vao); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, 6*5*sizeof(r32), NULL, GL_DYNAMIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(r32), 0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(r32), (void*)(3*sizeof(r32))); + glBindBuffer(GL_ARRAY_BUFFER, 0); + glBindVertexArray(0); + // ==================== END setup_text r32 FOV = 90.0; r32 time_curr; @@ -396,13 +537,37 @@ int main(int argc, char* argv[]) Mat4 view = camera_create4m(camera_pos, camera_look, preset_up_dir); Mat4 proj = perspective4m((r32)To_Radian(90.0), (r32)width / (r32)height, 0.1f, 1000.0f); + Vec2 camera_res = {1024.0f, 768.0f}; + r32 ortho_cam_height = 2.0f; + // CameraOrtho ortho_cam = camera_orthographic(ortho_cam_height, Vec2{camera_res.x, camera_res.y}, 0.1f, 1000.0f); + Mat4 ortho_proj = orthographic_projection4m(0.0f, 1024.0f, 0.0f, 768.0f, 0.1f, 1000.0f); + + // objects + Vec3 model_translations[] = { + Vec3{ 0.0, 0.0, 0.0}, // 0: origin square + Vec3{ -1.0, 0.0, -1.0}, // 1: plane + Vec3{ -1.0, 0.0, -0.5}, // 2: window between squares + Vec3{ 0.0, 0.0, 3.0}, // 3: window infront of origin square + Vec3{ -2.5, 0.0, -0.5}, // 4: square to the left + Vec3{ -1.0, 0.0, -1.5}, // 5: random square behind window between squares + Vec3{ -1.0, 0.0, -8.0}, // 6: reflective plane + Vec3{ -1.0, 2.0, -8.0}, // 6: refractive "window" + }; s32 proj_loc; - glUniformMatrix4fv(tex_quad_shader.proj_loc, 1, GL_TRUE, proj.buffer); + glUseProgram(ui_text_sp); + proj_loc = glGetUniformLocation(ui_text_sp, "Projection"); + // glUniformMatrix4fv(proj_loc, 1, GL_TRUE, ortho_cam.projection.buffer); + glUniformMatrix4fv(proj_loc, 1, GL_TRUE, ortho_proj.buffer); + + //glUseProgram(tex_quad_shader.id); + //glUniformMatrix4fv(tex_quad_shader.proj_loc, 1, GL_TRUE, ortho_cam.projection.buffer); glEnable(GL_DEPTH_TEST); glEnable(GL_CULL_FACE); glCullFace(GL_BACK); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); u8 game_running = true; @@ -481,6 +646,7 @@ int main(int argc, char* argv[]) } break; case (SDL_MOUSEMOTION): { + #if 0 SDL_MouseMotionEvent mouse_event = ev.motion; r32 x_motion = (r32)mouse_event.xrel; r32 y_motion = (r32)mouse_event.yrel; @@ -491,6 +657,7 @@ int main(int argc, char* argv[]) camera_look = camera_look_around(angle_pitch, angle_yaw); } + #endif } break; default: { @@ -503,47 +670,107 @@ int main(int argc, char* argv[]) if (move_w) { camera_pos = add3v(camera_pos, camera_look_increment); + //model_translations[0].y += 0.25f; } if (move_s) { camera_pos = subtract3v(camera_pos, camera_look_increment); + //model_translations[0].y -= 0.25f; } if (move_a) { Vec3 camera_right = normalize3v(cross_multiply3v(preset_up_dir, camera_look)); Vec3 camera_right_scaled = scaler_multiply3v(camera_right, camera_speed_adjusted); camera_pos = add3v(camera_pos, camera_right_scaled); + //model_translations[0].x -= 0.25f; } if (move_d) { Vec3 camera_right = normalize3v(cross_multiply3v(preset_up_dir, camera_look)); Vec3 camera_right_scaled = scaler_multiply3v(camera_right, camera_speed_adjusted); camera_pos = subtract3v(camera_pos, camera_right_scaled); + //model_translations[0].x += 0.25f; } view = camera_create4m(camera_pos, add3v(camera_pos, camera_look), preset_up_dir); // object shader program stuff time_prev = time_curr; - glUniformMatrix4fv(tex_quad_shader.view_loc, 1, GL_TRUE, view.buffer); + //glUseProgram(tex_quad_shader.id); + //glUniformMatrix4fv(tex_quad_shader.view_loc, 1, GL_TRUE, view.buffer); // OUTPUT glClearColor(1.0f, 0.6f, .6f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + // glDisable(GL_BLEND); + //gl2d_draw_tex_quad(tex_quad_shader, tex_quad1, model_translations[1], GL2D_UScale(3.0f), To_Radian(-5.0f)); + //gl2d_draw_tex_quad(tex_quad_shader, tex_quad2, model_translations[0], GL2D_UScale(1.0f), 0.0f); + // gl_text_render(ui_tex_sp, // shader program + // Vec3{startx, starty, zindex}, // coords + // "hello, sailor!\nhow do you do?"); // text to render { - glUseProgram(tex_quad_shader.id); - Vec3 pos = model_translations[0]; - Mat4 translation = translation_matrix4m(pos.x, pos.y, pos.z); - glUniformMatrix4fv(tex_quad_shader.model_loc, 1, GL_TRUE, translation.buffer); - gl_draw_tex_quad(tex_quad_shader.id, tex_quad1); - } - { - glUseProgram(tex_quad_shader.id); - Vec3 pos = model_translations[1]; - Mat4 translation = translation_matrix4m(pos.x, pos.y, pos.z); - glUniformMatrix4fv(tex_quad_shader.model_loc, 1, GL_TRUE, translation.buffer); - gl_draw_tex_quad(tex_quad_shader.id, tex_quad2); + // render ui text + glUseProgram(ui_text_sp); + s32 text_color_loc = glGetUniformLocation(ui_text_sp, "TextColor"); + glUniform3f(text_color_loc, 0.0f, 0.8f, 0.0f); + glActiveTexture(GL_TEXTURE0); + + glBindVertexArray(vao); + + const char text_to_render[] = "hello, sailor!\nhow does your journey go?"; + r32 startx = 512.0f; + r32 starty = 700.0f; + r32 linex = startx; + r32 zindex = 3.0f; + r32 scale = 1.0f; + r32 max_size = 0.0f; + for (u32 i = 0; i < sizeof(text_to_render) - 1; i++) + { + char c = text_to_render[i]; + TextChar rc = all_text_chars[c]; + if (rc.size.y > max_size) + { + max_size = rc.size.y; + } + if (c == '\n') + { + linex = startx; + starty = starty - (rc.size.y * 1.5 * scale); // line_height: 1.5; + continue; + } + else if (c == ' ') + { + linex += (rc.advance >> 6) * scale; + continue; + } + r32 xpos = linex + (scale * rc.bearing.x); + r32 ypos = starty - (rc.size.y - rc.bearing.y) * scale; + + r32 w = scale * rc.size.x; + r32 h = scale * rc.size.y; + + // update VBO for each character + float vertices[6][5] = { + // z-index to render on (part of layering) + { xpos, ypos + h, -zindex, 0.0f, 0.0f }, + { xpos, ypos, -zindex, 0.0f, 1.0f }, + { xpos + w, ypos, -zindex, 1.0f, 1.0f }, + + { xpos, ypos + h, -zindex, 0.0f, 0.0f }, + { xpos + w, ypos, -zindex, 1.0f, 1.0f }, + { xpos + w, ypos + h, -zindex, 1.0f, 0.0f } + }; + + glBindTexture(GL_TEXTURE_2D, rc.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); + linex += (rc.advance >> 6) * scale; + } + r32 test = 1; } SDL_GL_SwapWindow(window); @@ -552,11 +779,14 @@ int main(int argc, char* argv[]) // opengl free calls // it is up to the renderer to free everything here // of course I have not bothered to set that up so it does not matter - glDeleteVertexArrays(1, &tex_quad1.vao); - glDeleteVertexArrays(1, &tex_quad2.vao); - glDeleteBuffers(1, &tex_quad1.vao); - glDeleteBuffers(1, &tex_quad2.vao); - + // glDeleteVertexArrays(1, &tex_quad1.vao); + // glDeleteVertexArrays(1, &tex_quad2.vao); + // glDeleteBuffers(1, &tex_quad1.vao); + // glDeleteBuffers(1, &tex_quad2.vao); + + FT_Done_Face(face); + FT_Done_FreeType(ft_lib); + // sdl free calls SDL_GL_DeleteContext(context); SDL_DestroyWindow(window); diff --git a/source/math.h b/source/math.h index fda6bf8..d2b8ed0 100644 --- a/source/math.h +++ b/source/math.h @@ -5,6 +5,7 @@ #define Square(x) ((x)*(x)) #define To_Radian(x) ((x) * PI / 180.0f) #define To_Degree(x) ((x) * 180.0f / PI) +#define Ortho_Default_H 10.0f // @notes: // I dislike the mat4 discrepancy in the raw data. It's called buffer here while everywhere else it's called @@ -341,7 +342,7 @@ Mat4 rotation_matrix4m(r32 angle_radians, Vec3 axis) // generates a 4x4 rotation return res; } -Mat4 perspective_projection_matrix4m(r32 left, r32 right, r32 bottom, r32 top, r32 near, r32 far) +Mat4 perspective_projection4m(r32 left, r32 right, r32 bottom, r32 top, r32 near, r32 far) { Mat4 res = { 0 }; @@ -377,6 +378,35 @@ Mat4 perspective4m(r32 fov, r32 aspect_ratio, r32 near, r32 far) return res; } +Mat4 orthographic_projection4m(r32 left, r32 right, r32 bottom, r32 top, r32 near, r32 far) +{ + // @todo: understand the derivation once I am done experimenting + Mat4 res = { 0 }; + res.data[0][0] = 2.0f/(right - left); res.data[0][3] = -(right + left)/(right - left); + res.data[1][1] = 2.0f/(top - bottom); res.data[1][3] = -(top + bottom)/(top - bottom); + res.data[2][2] = -2.0f/(far - near); res.data[2][3] = -(far + near)/(far - near); + res.data[3][3] = 1.0f; + + return res; +} + +// @note: takes the screen width, screen height, near and far values, and creates an orthographic projection matrix +// height: arbitrary and depend on the user/camera creator. +// aspect_ratio: width/height, this too is user defined. +// near, far: values of the near and far plane +Mat4 orthographic4m(r32 height, r32 aspect_ratio, r32 near, r32 far) +{ + r32 width = height * aspect_ratio; + + r32 left = -width/2.0f; + r32 right = width/2.0f; + + r32 bottom = -height/2.0f; + r32 top = height/2.0f; + + return orthographic_projection4m(left, right, bottom, top, near, far); +} + Mat4 lookat4m(Vec3 up, Vec3 forward, Vec3 right, Vec3 position) { /* diff --git a/source/shaders/ui_text_shader.fs.glsl b/source/shaders/ui_text_shader.fs.glsl new file mode 100644 index 0000000..d8bdcfb --- /dev/null +++ b/source/shaders/ui_text_shader.fs.glsl @@ -0,0 +1,12 @@ +#version 330 core + +in vec2 TexCoords; +uniform sampler2D Texture; +uniform vec3 TextColor; +out vec4 FragColor; + +void main() { + vec4 sampled = vec4(1.0, 1.0, 1.0, texture(Texture, TexCoords)); + FragColor = sampled * vec4(TextColor, 1.0); +}; + diff --git a/source/shaders/ui_text_shader.vs.glsl b/source/shaders/ui_text_shader.vs.glsl new file mode 100644 index 0000000..ecaefa4 --- /dev/null +++ b/source/shaders/ui_text_shader.vs.glsl @@ -0,0 +1,12 @@ +#version 330 core +layout(location=0) in vec3 aPos; +layout(location=1) in vec2 aTex; + +uniform mat4 Projection; + +out vec2 TexCoords; + +void main() { + gl_Position = Projection * vec4(aPos, 1.0); + TexCoords = aTex; +} -- cgit v1.2.3