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
相关产品推荐
相关产品推荐

