I am trying to render some text with OpenGL using a font atlas texture from this generator. The settings I used were Open Sans, font size 32, with transparent background and font color #000. My code, however, results in a vertically flipped image:
I think the issue most likely lies in how I specified the vertices of each quad corresponding to each character:
for char in text.chars() {
let character_tex = get_charcoord_from_char(char).unwrap();
// Top left
let x0 = (x - character_tex.originX as f32) / ATLAS_WIDTH; // p1
let y0 = (y - character_tex.originY as f32) / ATLAS_HEIGHT;
let s0 = character_tex.x as f32 / ATLAS_WIDTH;
let t0 = character_tex.y as f32 / ATLAS_HEIGHT;
// Top right
let x1 = (x - character_tex.originX as f32 + character_tex.w as f32) / ATLAS_WIDTH; // p2
let y1 = (y - character_tex.originY as f32) / ATLAS_HEIGHT;
let s1 = (character_tex.x + character_tex.w) as f32 / ATLAS_WIDTH;
let t1 = character_tex.y as f32 / ATLAS_HEIGHT;
// Bottom left
let x2 = (x - character_tex.originX as f32) / ATLAS_WIDTH; // p4
let y2 = (y - character_tex.originY as f32 + character_tex.h as f32) / ATLAS_HEIGHT;
let s2 = character_tex.x as f32 / ATLAS_WIDTH;
let t2 = (character_tex.y + character_tex.h) as f32 / ATLAS_HEIGHT;
// Bottom right
let x3 = (x - character_tex.originX as f32 + character_tex.w as f32) / ATLAS_WIDTH; // p3
let y3 = (y - character_tex.originY as f32 + character_tex.h as f32) / ATLAS_HEIGHT;
let s3 = (character_tex.x + character_tex.w) as f32 / ATLAS_WIDTH;
let t3 = (character_tex.y + character_tex.h) as f32 / ATLAS_HEIGHT;
// Vertex order: position (x, y); vertex color (r, g, b, a); texcoord (s, t)
let p1 = [x0, y0, 1.0, 1.0, 1.0, 0.0, s0, t0];
let p2 = [x1, y1, 1.0, 1.0, 1.0, 0.0, s1, t1];
let p3 = [x3, y3, 1.0, 1.0, 1.0, 0.0, s3, t3];
let p4 = [x0, y0, 1.0, 1.0, 1.0, 0.0, s0, t0];
let p5 = [x3, y3, 1.0, 1.0, 1.0, 0.0, s3, t3];
let p6 = [x2, y2, 1.0, 1.0, 1.0, 0.0, s2, t2];
x += character_tex.advance as f32;
vertices.push(p1);
vertices.push(p2);
vertices.push(p3);
vertices.push(p4);
vertices.push(p5);
vertices.push(p6);
}
I think it is likely that the order of my vertices is incorrect, but I can't seem to tell what the correct order is.
And additionally, if it is any help, this is my current Cargo.toml:
[package]
name = "font-rendering"
version = "0.1.0"
edition = "2021"
[dependencies]
elara_gfx = { git = "https://github.com/elaraproject/elara-gfx.git" }
And complete code:
use elara_gfx::{gl_info, Buffer, BufferType, Program, Shader, VertexArray, PixelArray, Texture2D};
use elara_gfx::{GLWindow, HandlerResult, WindowHandler};
use elara_log::prelude::*;
use std::error::Error;
const VERT_SHADER: &str = r#"
#version 330 core
in vec2 position;
in vec2 tex_coord;
out vec2 TexCoord;
void main()
{
gl_Position = vec4(position.x, position.y, 0.0, 1.0);
TexCoord = vec2(tex_coord.x, tex_coord.y);
}
"#;
const FRAG_SHADER: &str = r#"
#version 330 core
in vec2 TexCoord;
out vec4 fragColor;
// texture sampler
uniform sampler2D texture1;
void main()
{
fragColor = texture(texture1, TexCoord);
}
"#;
const ATLAS_FONT_SIZE: i32 = 32;
const ATLAS_WIDTH: f32 = 358.0;
const ATLAS_HEIGHT: f32 = 133.0;
const ATLAS_CHARS: [char; 95] = [
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ' ', '!', '\"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~'
];
#[derive(Clone, Copy)]
struct CharCoord {
pub x: i32,
pub y: i32,
pub w: i32,
pub h: i32,
pub originX: i32,
pub originY: i32,
pub advance: i32
}
const ATLAS: [CharCoord; 95] = [
CharCoord { x: 65, y: 35, w: 18, h: 27, originX: 0, originY: 25, advance: 18 },
CharCoord { x: 62, y: 88, w: 12, h: 26, originX: -1, originY: 25, advance: 18 },
CharCoord { x: 274, y: 62, w: 18, h: 26, originX: 0, originY: 25, advance: 18 },
CharCoord { x: 83, y: 35, w: 18, h: 27, originX: 0, originY: 25, advance: 18 },
CharCoord { x: 155, y: 62, w: 20, h: 26, originX: 1, originY: 25, advance: 18 },
CharCoord { x: 101, y: 35, w: 18, h: 27, originX: 0, originY: 25, advance: 18 },
CharCoord { x: 119, y: 35, w: 18, h: 27, originX: 0, originY: 25, advance: 18 },
CharCoord { x: 292, y: 62, w: 18, h: 26, originX: 0, originY: 25, advance: 18 },
CharCoord { x: 137, y: 35, w: 18, h: 27, originX: 0, originY: 25, advance: 18 },
CharCoord { x: 155, y: 35, w: 18, h: 27, originX: 0, originY: 25, advance: 18 },
CharCoord { x: 199, y: 114, w: 3, h: 3, originX: 1, originY: 1, advance: 8 },
CharCoord { x: 257, y: 35, w: 7, h: 27, originX: -1, originY: 25, advance: 8 },
CharCoord { x: 91, y: 114, w: 12, h: 12, originX: 0, originY: 25, advance: 13 },
CharCoord { x: 0, y: 62, w: 23, h: 26, originX: 1, originY: 25, advance: 21 },
CharCoord { x: 151, y: 0, w: 18, h: 29, originX: 0, originY: 26, advance: 18 },
CharCoord { x: 281, y: 0, w: 27, h: 27, originX: 0, originY: 25, advance: 26 },
CharCoord { x: 308, y: 0, w: 25, h: 27, originX: 0, originY: 25, advance: 23 },
CharCoord { x: 111, y: 114, w: 7, h: 12, originX: 0, originY: 25, advance: 7 },
CharCoord { x: 102, y: 0, w: 10, h: 32, originX: 0, originY: 25, advance: 9 },
CharCoord { x: 69, y: 0, w: 11, h: 32, originX: 1, originY: 25, advance: 9 },
CharCoord { x: 73, y: 114, w: 18, h: 17, originX: 0, originY: 26, advance: 18 },
CharCoord { x: 0, y: 114, w: 18, h: 19, originX: 0, originY: 21, advance: 18 },
CharCoord { x: 103, y: 114, w: 8, h: 12, originX: 0, originY: 6, advance: 8 },
CharCoord { x: 170, y: 114, w: 11, h: 6, originX: 0, originY: 12, advance: 10 },
CharCoord { x: 163, y: 114, w: 7, h: 7, originX: -1, originY: 5, advance: 8 },
CharCoord { x: 34, y: 88, w: 14, h: 26, originX: 1, originY: 25, advance: 12 },
CharCoord { x: 211, y: 88, w: 7, h: 21, originX: -1, originY: 19, advance: 8 },
CharCoord { x: 99, y: 88, w: 9, h: 25, originX: 1, originY: 19, advance: 8 },
CharCoord { x: 18, y: 114, w: 18, h: 19, originX: 0, originY: 21, advance: 18 },
CharCoord { x: 118, y: 114, w: 18, h: 11, originX: 0, originY: 17, advance: 18 },
CharCoord { x: 36, y: 114, w: 18, h: 19, originX: 0, originY: 21, advance: 18 },
CharCoord { x: 226, y: 35, w: 16, h: 27, originX: 1, originY: 25, advance: 14 },
CharCoord { x: 122, y: 0, w: 29, h: 29, originX: 0, originY: 25, advance: 29 },
CharCoord { x: 328, y: 35, w: 24, h: 26, originX: 2, originY: 25, advance: 20 },
CharCoord { x: 175, y: 62, w: 20, h: 26, originX: -1, originY: 25, advance: 21 },
CharCoord { x: 23, y: 35, w: 21, h: 27, originX: 0, originY: 25, advance: 20 },
CharCoord { x: 46, y: 62, w: 22, h: 26, originX: -1, originY: 25, advance: 23 },
CharCoord { x: 328, y: 62, w: 17, h: 26, originX: -1, originY: 25, advance: 18 },
CharCoord { x: 0, y: 88, w: 17, h: 26, originX: -1, originY: 25, advance: 17 },
CharCoord { x: 0, y: 35, w: 23, h: 27, originX: 0, originY: 25, advance: 23 },
CharCoord { x: 134, y: 62, w: 21, h: 26, originX: -1, originY: 25, advance: 24 },
CharCoord { x: 74, y: 88, w: 6, h: 26, originX: -1, originY: 25, advance: 9 },
CharCoord { x: 80, y: 0, w: 11, h: 32, originX: 4, originY: 25, advance: 9 },
CharCoord { x: 195, y: 62, w: 20, h: 26, originX: -1, originY: 25, advance: 20 },
CharCoord { x: 17, y: 88, w: 17, h: 26, originX: -1, originY: 25, advance: 17 },
CharCoord { x: 302, y: 35, w: 26, h: 26, originX: -1, originY: 25, advance: 29 },
CharCoord { x: 68, y: 62, w: 22, h: 26, originX: -1, originY: 25, advance: 24 },
CharCoord { x: 333, y: 0, w: 25, h: 27, originX: 0, originY: 25, advance: 25 },
CharCoord { x: 310, y: 62, w: 18, h: 26, originX: -1, originY: 25, advance: 19 },
CharCoord { x: 16, y: 0, w: 25, h: 32, originX: 0, originY: 25, advance: 25 },
CharCoord { x: 215, y: 62, w: 20, h: 26, originX: -1, originY: 25, advance: 20 },
CharCoord { x: 173, y: 35, w: 18, h: 27, originX: 0, originY: 25, advance: 18 },
CharCoord { x: 235, y: 62, w: 20, h: 26, originX: 1, originY: 25, advance: 18 },
CharCoord { x: 44, y: 35, w: 21, h: 27, originX: -1, originY: 25, advance: 23 },
CharCoord { x: 23, y: 62, w: 23, h: 26, originX: 2, originY: 25, advance: 19 },
CharCoord { x: 270, y: 35, w: 32, h: 26, originX: 1, originY: 25, advance: 30 },
CharCoord { x: 90, y: 62, w: 22, h: 26, originX: 2, originY: 25, advance: 19 },
CharCoord { x: 112, y: 62, w: 22, h: 26, originX: 2, originY: 25, advance: 18 },
CharCoord { x: 255, y: 62, w: 19, h: 26, originX: 0, originY: 25, advance: 18 },
CharCoord { x: 112, y: 0, w: 10, h: 32, originX: -1, originY: 25, advance: 10 },
CharCoord { x: 48, y: 88, w: 14, h: 26, originX: 1, originY: 25, advance: 12 },
CharCoord { x: 91, y: 0, w: 11, h: 32, originX: 1, originY: 25, advance: 10 },
CharCoord { x: 54, y: 114, w: 19, h: 18, originX: 0, originY: 25, advance: 18 },
CharCoord { x: 181, y: 114, w: 18, h: 5, originX: 2, originY: -1, advance: 14 },
CharCoord { x: 136, y: 114, w: 9, h: 8, originX: 0, originY: 26, advance: 9 },
CharCoord { x: 163, y: 88, w: 17, h: 21, originX: 0, originY: 19, advance: 18 },
CharCoord { x: 227, y: 0, w: 18, h: 28, originX: -1, originY: 26, advance: 20 },
CharCoord { x: 180, y: 88, w: 16, h: 21, originX: 0, originY: 19, advance: 15 },
CharCoord { x: 189, y: 0, w: 19, h: 28, originX: 0, originY: 26, advance: 20 },
CharCoord { x: 127, y: 88, w: 18, h: 21, originX: 0, originY: 19, advance: 18 },
CharCoord { x: 242, y: 35, w: 15, h: 27, originX: 1, originY: 26, advance: 11 },
CharCoord { x: 208, y: 0, w: 19, h: 28, originX: 1, originY: 19, advance: 17 },
CharCoord { x: 191, y: 35, w: 18, h: 27, originX: -1, originY: 26, advance: 20 },
CharCoord { x: 80, y: 88, w: 6, h: 26, originX: -1, originY: 25, advance: 8 },
CharCoord { x: 6, y: 0, w: 10, h: 34, originX: 3, originY: 25, advance: 8 },
CharCoord { x: 209, y: 35, w: 17, h: 27, originX: -1, originY: 26, advance: 17 },
CharCoord { x: 264, y: 35, w: 6, h: 27, originX: -1, originY: 26, advance: 8 },
CharCoord { x: 218, y: 88, w: 28, h: 20, originX: -1, originY: 19, advance: 30 },
CharCoord { x: 312, y: 88, w: 18, h: 20, originX: -1, originY: 19, advance: 20 },
CharCoord { x: 108, y: 88, w: 19, h: 21, originX: 0, originY: 19, advance: 19 },
CharCoord { x: 245, y: 0, w: 18, h: 28, originX: -1, originY: 19, advance: 20 },
CharCoord { x: 263, y: 0, w: 18, h: 28, originX: 0, originY: 19, advance: 20 },
CharCoord { x: 345, y: 88, w: 13, h: 20, originX: -1, originY: 19, advance: 13 },
CharCoord { x: 196, y: 88, w: 15, h: 21, originX: 0, originY: 19, advance: 15 },
CharCoord { x: 86, y: 88, w: 13, h: 25, originX: 1, originY: 23, advance: 11 },
CharCoord { x: 145, y: 88, w: 18, h: 21, originX: -1, originY: 19, advance: 20 },
CharCoord { x: 273, y: 88, w: 20, h: 20, originX: 2, originY: 19, advance: 16 },
CharCoord { x: 246, y: 88, w: 27, h: 20, originX: 1, originY: 19, advance: 25 },
CharCoord { x: 293, y: 88, w: 19, h: 20, originX: 1, originY: 19, advance: 17 },
CharCoord { x: 169, y: 0, w: 20, h: 28, originX: 2, originY: 19, advance: 16 },
CharCoord { x: 330, y: 88, w: 15, h: 20, originX: 0, originY: 19, advance: 15 },
CharCoord { x: 41, y: 0, w: 14, h: 32, originX: 1, originY: 25, advance: 12 },
CharCoord { x: 0, y: 0, w: 6, h: 35, originX: -6, originY: 26, advance: 18 },
CharCoord { x: 55, y: 0, w: 14, h: 32, originX: 1, originY: 25, advance: 12 },
CharCoord { x: 145, y: 114, w: 18, h: 7, originX: 0, originY: 15, advance: 18 },
];
fn get_charcoord_from_char(character: char) -> Option<CharCoord> {
let index = ATLAS_CHARS.iter().position(|&c| c == character);
if let Some(idx) = index {
Some(ATLAS[idx])
} else {
None
}
}
struct Handler {
num_vertices: f32,
vao: VertexArray,
texture: Texture2D
}
impl Handler {
fn new(win: &GLWindow) -> Result<Handler, String> {
let text = "Hello Serendipitous World!";
let img = PixelArray::load_png_from_path("src/resources/font-tex.png").unwrap();
let mut vertices = Vec::new();
let mut total_advance = 0;
for char in text.chars() {
let character_tex = get_charcoord_from_char(char).unwrap();
total_advance += character_tex.advance;
}
let mut x = -(total_advance as f32 / 2.0);
let y = (ATLAS_FONT_SIZE as f32 / 2.0);
for char in text.chars() {
let character_tex = get_charcoord_from_char(char).unwrap();
// Top left
let x0 = (x - character_tex.originX as f32) / ATLAS_WIDTH; // p1
let y0 = (y - character_tex.originY as f32) / ATLAS_HEIGHT;
let s0 = character_tex.x as f32 / ATLAS_WIDTH;
let t0 = character_tex.y as f32 / ATLAS_HEIGHT;
// Top right
let x1 = (x - character_tex.originX as f32 + character_tex.w as f32) / ATLAS_WIDTH; // p2
let y1 = (y - character_tex.originY as f32) / ATLAS_HEIGHT;
let s1 = (character_tex.x + character_tex.w) as f32 / ATLAS_WIDTH;
let t1 = character_tex.y as f32 / ATLAS_HEIGHT;
// Bottom left
let x2 = (x - character_tex.originX as f32) / ATLAS_WIDTH; // p4
let y2 = (y - character_tex.originY as f32 + character_tex.h as f32) / ATLAS_HEIGHT;
let s2 = character_tex.x as f32 / ATLAS_WIDTH;
let t2 = (character_tex.y + character_tex.h) as f32 / ATLAS_HEIGHT;
// Bottom right
let x3 = (x - character_tex.originX as f32 + character_tex.w as f32) / ATLAS_WIDTH; // p3
let y3 = (y - character_tex.originY as f32 + character_tex.h as f32) / ATLAS_HEIGHT;
let s3 = (character_tex.x + character_tex.w) as f32 / ATLAS_WIDTH;
let t3 = (character_tex.y + character_tex.h) as f32 / ATLAS_HEIGHT;
// Vertex order: position (x, y); vertex color (r, g, b, a); texcoord (s, t)
let p1 = [x0, y0, 1.0, 1.0, 1.0, 0.0, s0, t0];
let p2 = [x1, y1, 1.0, 1.0, 1.0, 0.0, s1, t1];
let p3 = [x3, y3, 1.0, 1.0, 1.0, 0.0, s3, t3];
let p4 = [x0, y0, 1.0, 1.0, 1.0, 0.0, s0, t0];
let p5 = [x3, y3, 1.0, 1.0, 1.0, 0.0, s3, t3];
let p6 = [x2, y2, 1.0, 1.0, 1.0, 0.0, s2, t2];
x += character_tex.advance as f32;
vertices.push(p1);
vertices.push(p2);
vertices.push(p3);
vertices.push(p4);
vertices.push(p5);
vertices.push(p6);
}
let num_vertices = vertices.len() as f32;
let flattened_vertices: Vec<f32> = vertices.into_iter().flatten().collect();
let texture = Texture2D::new()?;
texture.bind();
texture.parameter_2d(gl::TEXTURE_WRAP_S, gl::REPEAT as i32);
texture.parameter_2d(gl::TEXTURE_WRAP_T, gl::REPEAT as i32);
texture.parameter_2d(gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
texture.parameter_2d(gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
texture.enable_alpha_blend();
texture.set_image_2d(img);
texture.generate_mipmap();
let vao = VertexArray::new()?;
vao.bind();
let vbo = Buffer::new()?;
vbo.bind(BufferType::Array);
vbo.data::<f32>(BufferType::Array, &flattened_vertices, gl::STATIC_DRAW);
let vertex_shader = Shader::new(&VERT_SHADER, gl::VERTEX_SHADER)?;
let fragment_shader = Shader::new(&FRAG_SHADER, gl::FRAGMENT_SHADER)?;
let program = Program::new(&[vertex_shader, fragment_shader])?;
program.use_program();
let pos_attrib = vao.get_attrib_location(&program, "position");
let col_attrib = vao.get_attrib_location(&program, "vertex_color");
let tex_coord_attrib = vao.get_attrib_location(&program, "tex_coord");
vao.enable_vertex_attrib(pos_attrib as u32);
vao.enable_vertex_attrib(col_attrib as u32);
vao.enable_vertex_attrib(tex_coord_attrib as u32);
vao.vertex_attrib_pointer::<f32>(pos_attrib as u32, 2, gl::FLOAT, false, 8, 0);
vao.vertex_attrib_pointer::<f32>(col_attrib as u32, 4, gl::FLOAT, false, 8, 2);
vao.vertex_attrib_pointer::<f32>(tex_coord_attrib as u32, 2, gl::FLOAT, false, 8, 6);
vao.unbind();
vbo.unbind(BufferType::Array);
Ok(Handler { vao, num_vertices, texture })
}
}
impl WindowHandler for Handler {
fn on_draw(&mut self) -> HandlerResult<()> {
unsafe {
gl::ClearColor(1.0, 1.0, 1.0, 1.0);
gl::Clear(gl::COLOR_BUFFER_BIT);
self.texture.bind();
self.vao.bind();
gl::DrawArrays(gl::TRIANGLES, 0, self.num_vertices as i32);
self.vao.unbind();
self.texture.unbind();
}
Ok(())
}
}
fn main() -> Result<(), Box<dyn Error>> {
Logger::new().init().unwrap();
info!("Starting logging...");
let (app, window) = GLWindow::new_with_title("OpenGL text")?;
window.get_context()?;
gl_info();
let render_handler = Handler::new(&window)?;
// Event handling
app.run_loop(window, render_handler);
Ok(())
}
