Skip to content

Commit 8dc3685

Browse files
committed
Added batch text rendering.Added back cloud blur.
1 parent 1c539a4 commit 8dc3685

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

114 files changed

+41440
-44
lines changed

FallingGame/Drawer.cpp

Lines changed: 242 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@
77
#define STB_IMAGE_IMPLEMENTATION
88
#include "stb_image.h"
99

10+
#include <ft2build.h>
11+
#include FT_FREETYPE_H
12+
1013
Drawer::Drawer(Game& g)
1114
{
15+
1216
// standard rect EBO
1317
{
1418
unsigned int indices[] = {
@@ -24,6 +28,167 @@ Drawer::Drawer(Game& g)
2428
glBufferData(GL_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
2529
}
2630

31+
// font stuff with freetype
32+
33+
{
34+
FT_Library ft;
35+
if (FT_Init_FreeType(&ft)) {
36+
std::cout << "ERROR::FREETYPE: Failed to load FreeType\n";
37+
std::cin.get();
38+
}
39+
40+
FT_Face face;
41+
if (FT_New_Face(ft, "f/fonts/arial.ttf", 0, &face))
42+
{
43+
std::cout << "ERROR::FREETYPE: Font failed to load arial\n";
44+
std::cin.get();
45+
}
46+
// set width to 0 to dynamically calculate it
47+
FT_Set_Pixel_Sizes(face, 0, 48);
48+
49+
/*
50+
* How to render text efficiently
51+
* The easy method is having a seperate texture for each character,
52+
* and then issuing a draw call for each one of them,
53+
* where you specify the texture in question and it's rectangular information
54+
* precisely. Which is very costly.
55+
* Couldn't you just store all the characters in one big image?
56+
* Then in an array indexed by the character, you should store where all the
57+
* characters are exactly in 2D space, and how big they are.
58+
* Let's say you want to render the character A. You pass the shader a uniform
59+
* containing the starting position. In the vertex parameters you give it the character.
60+
* It indexes into the array buffer stored into the GPU containing the correct offsets
61+
* for characters. There it gets the x, y, w and h of the image, which it uses to
62+
* successfully query the texture in question and render a part of it. Let's now say we
63+
* want to do this for a larger amount of characters at a time; We shall then use instanced
64+
* rendering and give each instance it's own letter, but we also have to give it it's
65+
* offset compared to the previous characters. They can't all be drawn onto the same
66+
* place. So the CPU should first examine the text in question, query all the advancements
67+
* of each character and compute where they all should be offset to. Load all that into
68+
* an array containing all offsets. Then instancing will take care of moving through that
69+
* array one time per instance. Thus, all the vertices in a single instance can use it's
70+
* GL_VertexID to compute it's offset as well from the starting position of the character
71+
* because they all share the offset, width and height. The instanced array should also
72+
* contain the character in question, because there will be static memory storing the
73+
* texture offsets for all the characters in an SSBO, which the fragment shader will index
74+
* into, recieving the correct 4 coordintes in some way.
75+
*/
76+
77+
unsigned int width = 0, height = 0;
78+
79+
static constexpr int PIX_OFFSET = 10;
80+
81+
for (unsigned char c = CHARACTERS_START_AT; c < CHARACTERS; c++)
82+
{
83+
// load character glyph
84+
if (FT_Load_Char(face, c, FT_LOAD_RENDER))
85+
{
86+
std::cout << "ERROR::FREETYTPE: Failed to load Glyph:" << c << std::endl;
87+
continue;
88+
}
89+
// generate texture
90+
//glTexImage2D(GL_TEXTURE_2D,0, GL_RED, face->glyph->bitmap.width,
91+
// face->glyph->bitmap.rows, 0, GL_RED, GL_UNSIGNED_BYTE, face->glyph->bitmap.buffer
92+
//);
93+
width += face->glyph->bitmap.width + PIX_OFFSET;
94+
height = std::max(height, face->glyph->bitmap.rows);
95+
// now store character for later use
96+
Character character = {
97+
face->glyph->bitmap.width, face->glyph->bitmap.rows,
98+
face->glyph->bitmap_left, face->glyph->bitmap_top,
99+
face->glyph->advance.x
100+
};
101+
m_characters[c] = character;
102+
}
103+
104+
// generate all standard ascii characters
105+
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); // disable byte-alignment restriction
106+
107+
// create texture for use
108+
glGenTextures(1, &m_main_font_tex);
109+
glBindTexture(GL_TEXTURE_2D, m_main_font_tex); // all upcoming GL_TEXTURE_2D operations now have effect on this texture object
110+
111+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
112+
113+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
114+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
115+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
116+
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
117+
118+
glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, NULL);
119+
int x = PIX_OFFSET;
120+
121+
// exclude loading space by starting at 33.
122+
for (unsigned char c = CHARACTERS_START_AT + 1; c < CHARACTERS; c++)
123+
{
124+
// load character glyph
125+
if (FT_Load_Char(face, c, FT_LOAD_RENDER))
126+
{
127+
std::cout << "ERROR::FREETYTPE: Failed to load Glyph:" << c << std::endl;
128+
continue;
129+
}
130+
131+
// generate texture
132+
glTexSubImage2D(GL_TEXTURE_2D, 0, x, 0,
133+
face->glyph->bitmap.width, face->glyph->bitmap.rows,
134+
GL_RED, GL_UNSIGNED_BYTE, face->glyph->bitmap.buffer);
135+
x += face->glyph->bitmap.width + PIX_OFFSET;
136+
137+
}
138+
139+
140+
141+
FT_Done_Face(face);
142+
FT_Done_FreeType(ft);
143+
144+
glPixelStorei(GL_UNPACK_ALIGNMENT, 4); // turn back on?
145+
146+
147+
///////////////// OPENGL buffer stuff
148+
149+
m_text_program = g.l.compile_shader_program("f/shaders/text.vert", "f/shaders/text.frag", "text shader");
150+
glUseProgram(m_text_program);
151+
152+
glGenVertexArrays(1, &m_text_VAO);
153+
glBindVertexArray(m_text_VAO);
154+
155+
glGenBuffers(1, &m_text_VBO);
156+
glBindBuffer(GL_ARRAY_BUFFER, m_text_VBO);
157+
glBufferData(GL_ARRAY_BUFFER, TEXT_ATTRIBUTE_BYTES, NULL, GL_DYNAMIC_DRAW);
158+
159+
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, standard_rect_EBO);
160+
161+
glEnableVertexAttribArray(0);
162+
glVertexAttribPointer(0, TEXT_ATTRIBUTES, GL_FLOAT, GL_FALSE, TEXT_BYTES_PER, (void*)0);
163+
glVertexAttribDivisor(0, 1); // one per instance
164+
165+
m_text_u_offset_scale = glGetUniformLocation(m_text_program, "u_offset_scale");
166+
167+
{
168+
// make it the correct amount of data
169+
float data[CHARACTERS][U_TEXT_ATTRIBUTES_PER]; // store the width,height,bearingx,bearingy
170+
for (int i = CHARACTERS_START_AT; i < CHARACTERS; ++i) {
171+
data[i][0] = m_characters[i].width; // width
172+
data[i][1] = m_characters[i].height; // height
173+
data[i][2] = m_characters[i].bearing_x; // bearing X
174+
data[i][3] = m_characters[i].bearing_y; // bearing Y
175+
}
176+
177+
glUniform4fv(glGetUniformLocation(m_text_program, "u_text"), 128, (float*)data);
178+
}
179+
180+
{
181+
float char_x_offset_data[CHARACTERS];
182+
float x = 0.f;
183+
for (int i = CHARACTERS_START_AT; i < CHARACTERS; ++i) {
184+
char_x_offset_data[i] = x;
185+
x += m_characters[i].width + PIX_OFFSET;
186+
}
187+
188+
glUniform1fv(glGetUniformLocation(m_text_program, "u_char_x_offsets"), 128, (float*)char_x_offset_data);
189+
}
190+
}
191+
27192
// rectangle shader
28193
{
29194
rectangle_program = g.l.compile_shader_program("f/shaders/rectangle.vert", "f/shaders/rectangle.frag", "rectangle shader");
@@ -135,7 +300,7 @@ Drawer::Drawer(Game& g)
135300
constexpr float left = -g.l.WIDTH;
136301
constexpr float right = -g.G_WIDTH;
137302

138-
constexpr float vertices[] = {
303+
static constexpr float vertices[] = {
139304
// position texture coordinates
140305

141306
// left side of screen
@@ -234,33 +399,85 @@ Drawer::Drawer(Game& g)
234399
}
235400

236401
// load textures
237-
constexpr std::array<std::tuple<const char*, int, int>, TEX::TOTAL> data =
238-
{ {
239-
{"f/images/bird.png", GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER},
240-
{"f/images/bird_closed.png", GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER},
241-
{"f/images/mushroom_cap.png", GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER},
242-
{"f/images/mushroom_stem.png", GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER},
243-
{"f/images/side_background.png", GL_CLAMP_TO_BORDER, GL_REPEAT },
244-
{"f/images/vines.png", GL_REPEAT, GL_REPEAT },
245-
{"f/images/sky.png", GL_REPEAT, GL_REPEAT},
246-
{"f/images/sky_blurred.png", GL_REPEAT, GL_REPEAT},
247-
{"f/images/sky2.png", GL_REPEAT, GL_REPEAT},
248-
{"f/images/sky3.png", GL_REPEAT, GL_REPEAT},
249-
{"f/images/storm.png", GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER},
250-
{"f/images/cloud_1.png", GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER},
251-
{"f/images/cloud_2.png", GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER},
252-
{"f/images/cloud_3.png", GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER},
253-
{"f/images/cloud_4.png", GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER},
254-
{"f/images/coin.png", GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER},
255-
{"f/images/coin_blurred.png", GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER}
256-
} };
257-
258-
for (int i = 0; i < TEX::TOTAL; ++i) {
259-
auto& d = data[i];
260-
tex_sizes[i] = load_texture(std::get<0>(d), &texs[i], std::get<1>(d), std::get<2>(d));
402+
{
403+
404+
static constexpr std::array<std::tuple<const char*, int, int>, TEX::TOTAL> data =
405+
{ {
406+
{"f/images/bird.png", GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER},
407+
{"f/images/bird_closed.png", GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER},
408+
{"f/images/mushroom_cap.png", GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER},
409+
{"f/images/mushroom_stem.png", GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER},
410+
{"f/images/side_background.png", GL_CLAMP_TO_BORDER, GL_REPEAT },
411+
{"f/images/vines.png", GL_REPEAT, GL_REPEAT },
412+
{"f/images/sky.png", GL_REPEAT, GL_REPEAT},
413+
{"f/images/sky_blurred.png", GL_REPEAT, GL_REPEAT},
414+
{"f/images/sky2.png", GL_REPEAT, GL_REPEAT},
415+
{"f/images/sky3.png", GL_REPEAT, GL_REPEAT},
416+
{"f/images/storm.png", GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER},
417+
{"f/images/cloud_1.png", GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER},
418+
{"f/images/cloud_2.png", GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER},
419+
{"f/images/cloud_3.png", GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER},
420+
{"f/images/cloud_4.png", GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER},
421+
{"f/images/coin.png", GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER},
422+
{"f/images/coin_blurred.png", GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER}
423+
} };
424+
425+
for (int i = 0; i < TEX::TOTAL; ++i) {
426+
auto& d = data[i];
427+
tex_sizes[i] = load_texture(std::get<0>(d), &texs[i], std::get<1>(d), std::get<2>(d));
428+
}
429+
}
430+
431+
// mess with a texture2D
432+
{
433+
unsigned char data[100][100][4];
434+
for (int i = 0; i < 100; ++i) {
435+
for (int j = 0; j < 100; ++j) {
436+
data[i][j][0] = 255*sin(i/20.0);
437+
data[i][j][1] = 0;
438+
data[i][j][2] = 0;
439+
data[i][j][3] = 255;
440+
}
441+
}
442+
443+
for (int i = 0; i < 3; ++i) {
444+
glBindTexture(GL_TEXTURE_2D, texs[TEX::side_background]);
445+
glTexSubImage2D(GL_TEXTURE_2D, i, 100, 100, 100, 100, GL_RGBA, GL_UNSIGNED_BYTE, data);
446+
}
261447
}
262448
}
263449

450+
void Drawer::draw_text(const char* text, Color color, float x, float y, float scale)
451+
{
452+
glUseProgram(m_text_program);
453+
glBindVertexArray(m_text_VAO);
454+
455+
glBindTexture(GL_TEXTURE_2D, m_main_font_tex);
456+
457+
float data[TEXT_MAX_CHARS_RENDER][TEXT_ATTRIBUTES];
458+
float char_x_offset = 0;
459+
// i will be the total amount of elements later
460+
int i = 0;
461+
for (; text[i] != '\0'; ++i) {
462+
// char
463+
assert(i < TEXT_MAX_CHARS_RENDER);
464+
char c = text[i];
465+
data[i][0] = (float)c;
466+
data[i][1] = (float)char_x_offset;
467+
468+
//advance is number of 1/64 pixels for some reason
469+
char_x_offset += (m_characters[c].advance >> 6) * scale; //bitshift by 6 == * 2^6
470+
}
471+
const int nr_of_chars = i;
472+
473+
glBindBuffer(GL_ARRAY_BUFFER, m_text_VBO);
474+
glBufferSubData(GL_ARRAY_BUFFER, 0, nr_of_chars * TEXT_BYTES_PER, data);
475+
476+
glUniform3f(m_text_u_offset_scale, x, y, scale);
477+
478+
glDrawElementsInstanced(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL, nr_of_chars);
479+
}
480+
264481
void Drawer::draw_rectangle(float x, float y, float w, float h, const Color& color)
265482
{
266483
glUseProgram(rectangle_program);

FallingGame/Drawer.h

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,21 @@ class Camera;
99
class Player;
1010
class CoinParticle;
1111

12+
struct Character {
13+
unsigned int width; // width of glyph
14+
unsigned int height; // height of glyph
15+
int bearing_x; // x start position
16+
int bearing_y; // y start position, kind of?
17+
int advance; // x length to next glyph
18+
};
19+
1220
class Drawer
1321
{
1422
public:
1523
Drawer(Game& g);
1624

25+
void draw_text(const char* text, Color color, float x, float y, float scale);
26+
1727
void draw_rectangle(float x, float y, float w, float h, const Color& color);
1828

1929
// draws middle of image at x,y. Offset by camera
@@ -39,6 +49,38 @@ class Drawer
3949
std::array<int, 2> load_texture(const char* path, unsigned int* image, int wrapping_x, int wrapping_y);
4050

4151
private:
52+
// how many there are to draw
53+
static constexpr int CHARACTERS = 128;
54+
static constexpr int CHARACTERS_START_AT = 32;
55+
// data for all the characters; how to draw them
56+
std::array<Character, CHARACTERS> m_characters;
57+
// takes in the character and the x offset
58+
unsigned int m_text_program;
59+
unsigned int m_main_font_tex;
60+
unsigned int m_text_VAO, m_text_VBO;
61+
int m_text_u_offset_scale;
62+
// *IMPORTANT* this is the max buffer of chars allowed to be rendered IN ONE CALL
63+
static constexpr int TEXT_MAX_CHARS_RENDER = 256;
64+
// char and x offset
65+
static constexpr int TEXT_ATTRIBUTES = 2;
66+
// bytes per instance
67+
static constexpr int TEXT_BYTES_PER = sizeof(float) * TEXT_ATTRIBUTES;
68+
// one float for character, then one for x offset. Then for `above` times. Float.
69+
static constexpr int TEXT_ATTRIBUTE_BYTES = TEXT_BYTES_PER * TEXT_MAX_CHARS_RENDER;
70+
71+
// text uniform buffer object
72+
static constexpr int U_TEXT_ATTRIBUTES_PER = 4;
73+
static constexpr int U_TEXT_FLOATS = U_TEXT_ATTRIBUTES_PER * CHARACTERS;
74+
static constexpr int U_TEXT_BYTES = U_TEXT_FLOATS * sizeof(float);
75+
// Contains `CHARACTERS` times of:
76+
// Width
77+
// Height
78+
// Bearing X
79+
// Bearing Y
80+
81+
//
82+
83+
static constexpr int UBO_TEXT_SIZE = CHARACTERS * sizeof(float);
4284

4385
unsigned int standard_rect_EBO; // left bottom, left top, right top, right bottom
4486

@@ -81,7 +123,7 @@ class Drawer
81123
* float g_w; // how much bigger the width is to the height, CONSTANT = 1.6 = g.l.WIDTH
82124
*/
83125
constexpr static int UBO_GLOBAL_SIZE = 4 * sizeof(float);
84-
unsigned int ubo_globals;
126+
unsigned int ubo_globals; // BINDING 0.
85127

86128
public:
87129
std::array<unsigned int, TEX::TOTAL> texs;

0 commit comments

Comments
 (0)