You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

OpenGL文本渲染无屏幕输出问题排查(Rust移植版)

Rust OpenGL文本渲染无显示问题排查与修复

我正在将LearnOpenGL的文本渲染类移植到Rust中,完成代码后无法在屏幕上输出任何文本。已通过glGetError()检查未发现OpenGL错误,怀疑问题出在预分配的空VBO数据填充环节。以下是完整代码和依赖配置:

完整Rust代码

use elara_gfx::{gl_info, Shader, Program, VertexArray, Buffer, BufferType, Color, Uniform, WindowHandler, HandlerResult};
use freetype::Library;
use freetype::face::LoadFlag;
use std::collections::HashMap;
use std::error::Error;
use elara_gfx::GLWindow;
use elara_gfx::types::c_void;

const VERTEX_SHADER: &'static str = r#"#version 330 core
in vec4 vertex;
out vec2 TexCoord;

void main() {
    gl_Position = vec4(vertex.xy, 0.0, 1.0);
    TexCoord = vertex.zw;
}"#;

const FRAGMENT_SHADER: &'static str = r#"#version 330 core
in vec2 TexCoord;
out vec4 FragColor;

uniform sampler2D text;
uniform vec3 textColor;

void main() {
    vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoord).r);
    FragColor = vec4(textColor, 1.0) * sampled;
}"#;

#[derive(Debug)]
struct Character {
    pub texture_id: i32,
    pub size: (i32, i32),
    pub bearing: (i32, i32),
    pub advance: i32
}

impl Character {
    fn new(texture_id: i32, size: (i32, i32), bearing: (i32, i32), advance: i32) -> Character {
        Character { texture_id, size, bearing, advance }
    }
}

struct TextRenderer {
    program: Program,
    vao: VertexArray,
    vbo: Buffer,
    characters: HashMap<char, Character>
}

impl TextRenderer {
    fn new() -> Result<TextRenderer, String> {
        let vertex_shader = Shader::new(&VERTEX_SHADER, gl::VERTEX_SHADER)?;
        let fragment_shader = Shader::new(&FRAGMENT_SHADER, gl::FRAGMENT_SHADER)?;
        let program = Program::new(&[vertex_shader, fragment_shader])?;

        let vao = VertexArray::new()?;
        let vbo = Buffer::new()?;
        vao.bind();
        vbo.bind(BufferType::Array);
        vbo.data_empty::<f32>(BufferType::Array, 24, gl::DYNAMIC_DRAW);

        let vertex_attrib = vao.get_attrib_location(&program, "vertex");
        vao.enable_vertex_attrib(vertex_attrib as u32);
        vao.vertex_attrib_pointer::<f32>(vertex_attrib as u32, 4, gl::FLOAT, false, 4, 0);

        vbo.unbind(BufferType::Array);
        vao.unbind();

        let characters: HashMap<char, Character> = HashMap::new();

        Ok(TextRenderer { program, vao, vbo, characters })
    }

    fn load(&mut self, font: &str, size: u32) {
        if !self.characters.is_empty() {
            self.characters.clear();
        }

        let ft = Library::init().unwrap();
        let face = ft.new_face(font, 0).unwrap();
        face.set_pixel_sizes(0, size).unwrap();
        unsafe {
            gl::PixelStorei(gl::UNPACK_ALIGNMENT, 1);
        }
        // Enable blending
        unsafe {
            gl::Enable(gl::CULL_FACE);
            gl::Enable(gl::BLEND);
            gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
        }

        // Load first 128 characters of ASCII set
        for c in 0..128 as u8 {
            face.load_char(c as usize, LoadFlag::RENDER).unwrap();
            unsafe {
                let mut texture = 0;
                gl::GenTextures(1, &mut texture);
                gl::BindTexture(gl::TEXTURE_2D, texture);
                gl::TexImage2D(
                    gl::TEXTURE_2D,
                    0,
                    gl::RED as i32,
                    face.glyph().bitmap().width(),
                    face.glyph().bitmap().rows(),
                    0,
                    gl::RED,
                    gl::UNSIGNED_BYTE,
                    face.glyph().bitmap().buffer().as_ptr() as *const c_void
                );
                gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32);
                gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32);
                gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
                gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);

                let character = Character::new(
                    texture as i32,
                    (face.glyph().bitmap().width(), face.glyph().bitmap().rows()),
                    (face.glyph().bitmap_left(), face.glyph().bitmap_top()),
                    face.glyph().advance().x as i32
                );

                self.characters.insert(c as char, character);
            }
        }
        unsafe {
            gl::BindTexture(gl::TEXTURE_2D, 0);
        }
    }

    fn render_text(&self, text: &str, mut x: f32, y: f32, scale: f32, color: Color) -> Result<(), String> {
        self.program.use_program();
        let color_uniform = Uniform::new(&self.program, "textColor")?;
        color_uniform.uniform3f(color.0 as f32 / 255.0, color.1 as f32 / 255.0, color.2 as f32 / 255.0);
        unsafe {
            gl::ActiveTexture(gl::TEXTURE0);
        }
        self.vao.bind();
        for c in text.chars() {
            let ch = self.characters.get(&c).unwrap();
            let xpos = x + ch.bearing.0 as f32 * scale;
            let ypos = y - (ch.size.1 as f32 - ch.bearing.1 as f32) * scale;
            let w = ch.size.0 as f32 * scale;
            let h = ch.size.1 as f32 * scale;
            let vertices: [f32; 24] = [
                xpos,     ypos + h,   0.0_f32, 0.0,            
                xpos,     ypos,       0.0,     1.0,
                xpos + w, ypos,       1.0,     1.0,

                xpos,     ypos + h,   0.0,     0.0,
                xpos + w, ypos,       1.0,     1.0,
                xpos + w, ypos + h,   1.0,     0.0  
            ];
            unsafe {
                gl::BindTexture(gl::TEXTURE_2D, ch.texture_id as u32);
                self.vbo.bind(BufferType::Array);
                self.vbo.subdata(BufferType::Array, 0, &vertices);
                gl::DrawArrays(gl::TRIANGLES, 0, 6);
                x += (ch.advance >> 6) as f32 * scale;
                self.vbo.unbind(BufferType::Array);
            }
        }
        self.vao.unbind();
        unsafe {
            gl::BindTexture(gl::TEXTURE_2D, 0);
        }
        Ok(())
    }
}

struct Handler {
    renderer: TextRenderer   
}

impl Handler {
    fn new() -> Result<Handler, String> {
        let mut renderer = TextRenderer::new()?;
        renderer.load("resources/OpenSans-Regular.ttf", 48);
        Ok(Handler { renderer })
    }
}

impl WindowHandler for Handler {
    fn on_draw(&mut self) -> HandlerResult<()> {
        unsafe {
            gl::ClearColor(0.2, 0.3, 0.3, 1.0);
            gl::Clear(gl::COLOR_BUFFER_BIT);
            self.renderer.render_text("This is some sample text", 0.0, 0.0, 1.0, Color(255, 255, 255, 1.0))?;
        }
        Ok(())
    }
}

fn main() -> Result<(), Box<dyn Error>> {
    let (app, window) = GLWindow::new_with_title("Hi OpenGL!")?;
    window.get_context()?;
    gl_info();
    
    let render_handler = Handler::new()?;

    // Event handling
    app.run_loop(window, render_handler);
    Ok(())
}

Cargo.toml配置

[package]
name = "font-rendering"
version = "0.1.0"
edition = "2021"

[dependencies]
elara_gfx = { git = "https://github.com/elaraproject/elara-gfx.git" }

可能的问题点及修复方法

1. 顶点属性步长参数错误

在TextRenderer::new()中设置顶点属性指针时,步长参数传入的是4,但OpenGL的stride参数要求是字节数,而非元素数。每个顶点包含4个f32类型的值,每个f32占4字节,因此正确步长应为16。

修改代码:

// 原代码
vao.vertex_attrib_pointer::<f32>(vertex_attrib as u32, 4, gl::FLOAT, false, 4, 0);

// 修改后
vao.vertex_attrib_pointer::<f32>(vertex_attrib as u32, 4, gl::FLOAT, false, 4 * std::mem::size_of::<f32>(), 0);

2. VBO预分配大小歧义

vbo.data_empty::<f32>(BufferType::Array, 24, gl::DYNAMIC_DRAW);中的24如果是指字节数,则仅能容纳6个f32,无法满足每次渲染的24个顶点数据需求。为避免歧义,建议显式按字节数分配:

修改代码:

// 原代码
vbo.data_empty::<f32>(BufferType::Array, 24, gl::DYNAMIC_DRAW);

// 修改后(明确分配24个f32的字节数)
vbo.data_empty::<f32>(BufferType::Array, 24 * std::mem::size_of::<f32>(), gl::DYNAMIC_DRAW);

3. 渲染坐标超出屏幕范围

当前渲染使用的是像素坐标,但OpenGL的NDC(标准化设备坐标)范围是[-1, 1],初始x=0、y=0会导致文本被裁剪出屏幕。需要将像素坐标转换为NDC,或调整初始位置到可见区域:

示例:转换坐标到NDC

// 在Handler的on_draw中获取窗口尺寸
let (width, height) = window.get_size();
let x_ndc = 10.0 / width as f32 * 2.0 - 1.0;
let y_ndc = 1.0 - (height as f32 - 50.0) / height as f32 * 2.0;
self.renderer.render_text("This is some sample text", x_ndc, y_ndc, 1.0, Color(255, 255, 255, 1.0))?;

4. 纹理单元未显式绑定

虽然激活了TEXTURE0,但最好显式将text uniform设置为0,确保采样器正确关联纹理单元:

在render_text方法中添加:

self.program.use_program();
// 新增:设置纹理单元
let text_uniform = Uniform::new(&self.program, "text")?;
text_uniform.uniform1i(0);
// 原有的颜色设置...

5. 字体文件路径检查

确保resources/OpenSans-Regular.ttf路径正确,程序能读取到字体文件。建议替换unwrap()为错误处理,避免静默失败:

// 原代码
let ft = Library::init().unwrap();
let face = ft.new_face(font, 0).unwrap();

// 修改为带错误处理
let ft = Library::init().map_err(|e| format!("FreeType初始化失败: {}", e))?;
let face = ft.new_face(font, 0).map_err(|e| format!("字体加载失败: {}", e))?;

内容的提问来源于stack exchange,提问作者JS4137

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 06:57:14