Files
window-axis-innovators-box/src/main/java/com/chuangzhou/vivid2D/render/TextRenderer.java
tzdwindows 7 7ac960be5e feat(render): 实现摄像机系统和文字渲染功能
- 添加 Camera 类,支持位置、缩放、Z轴控制- 在 ModelRender 中集成摄像机投影矩阵计算
- 实现屏幕坐标到世界坐标的转换方法
- 添加默认文字渲染器和字体加载逻辑
- 在渲染面板中添加摄像机控制的鼠标手势支持
- 支持通过鼠标滚轮进行摄像机缩放操作
- 添加摄像机状态显示和调试信息渲染
- 实现多选框渲染逻辑的重构和优化
-修复坐标系变换相关的边界框计算问题
- 增加摄像机启用/禁用快捷键支持
2025-10-24 20:05:40 +08:00

237 lines
8.0 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.chuangzhou.vivid2D.render;
import com.chuangzhou.vivid2D.render.systems.RenderSystem;
import com.chuangzhou.vivid2D.render.systems.buffer.BufferBuilder;
import com.chuangzhou.vivid2D.render.systems.buffer.Tesselator;
import com.chuangzhou.vivid2D.render.systems.sources.ShaderManagement;
import com.chuangzhou.vivid2D.render.systems.sources.ShaderProgram;
import org.joml.Vector2f;
import org.joml.Vector4f;
import org.lwjgl.opengl.*;
import org.lwjgl.stb.STBTTAlignedQuad;
import org.lwjgl.stb.STBTTBakedChar;
import org.lwjgl.system.MemoryStack;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import static org.lwjgl.stb.STBTruetype.*;
/**
* OpenGL 文字渲染器实例类
* 支持多字体、多实例管理,每个实例维护独立的字符数据与纹理
*
* @author tzdwindows 7
*/
public final class TextRenderer {
private static final Logger logger = LoggerFactory.getLogger(TextRenderer.class);
private final int bitmapWidth;
private final int bitmapHeight;
private final int firstChar;
private final int charCount;
private STBTTBakedChar.Buffer charData;
private int fontTextureId;
private boolean initialized = false;
/**
* 构造函数
*
* @param bitmapWidth 字符纹理宽度
* @param bitmapHeight 字符纹理高度
* @param firstChar 字符起始码
* @param charCount 字符数量
*/
public TextRenderer(int bitmapWidth, int bitmapHeight, int firstChar, int charCount) {
this.bitmapWidth = bitmapWidth;
this.bitmapHeight = bitmapHeight;
this.firstChar = firstChar;
this.charCount = charCount;
}
/**
* 初始化字体渲染器
*
* @param fontData TTF 字体文件内容
* @param fontHeight 字体像素高度
*/
public void initialize(ByteBuffer fontData, float fontHeight) {
if (initialized) return;
ShaderProgram shader = ShaderManagement.getShaderProgram("TextShader");
shader.use();
// 验证输入参数
if (fontData == null || fontData.capacity() == 0) {
logger.error("Invalid font data provided to TextRenderer");
return;
}
if (fontHeight <= 0) {
logger.error("Invalid font height: {}", fontHeight);
return;
}
if (bitmapWidth <= 0 || bitmapHeight <= 0) {
logger.error("Invalid bitmap dimensions: {}x{}", bitmapWidth, bitmapHeight);
return;
}
try {
charData = STBTTBakedChar.malloc(charCount);
// 分配位图内存
int bitmapSize = bitmapWidth * bitmapHeight;
if (bitmapSize <= 0) {
logger.error("Invalid bitmap size: {}", bitmapSize);
return;
}
ByteBuffer bitmap = ByteBuffer.allocateDirect(bitmapSize);
// 烘焙字体位图
int result = stbtt_BakeFontBitmap(fontData, fontHeight, bitmap, bitmapWidth, bitmapHeight, firstChar, charData);
if (result <= 0) {
logger.error("stbtt_BakeFontBitmap failed with result: {}", result);
charData.free();
return;
}
// 创建纹理
fontTextureId = createTextureFromBitmap(bitmapWidth, bitmapHeight, bitmap);
if (fontTextureId == 0) {
logger.error("Failed to create font texture");
charData.free();
return;
}
initialized = true;
logger.debug("TextRenderer initialized successfully with texture ID: {}", fontTextureId);
} catch (Exception e) {
logger.error("Exception during TextRenderer initialization: {}", e.getMessage());
if (charData != null) {
charData.free();
charData = null;
}
}
shader.stop();
}
private int createTextureFromBitmap(int width, int height, ByteBuffer pixels) {
RenderSystem.assertOnRenderThread();
int textureId = RenderSystem.genTextures();
RenderSystem.bindTexture(textureId);
// 使用更兼容的纹理格式
RenderSystem.texImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_ALPHA,
width, height, 0, GL11.GL_ALPHA, GL11.GL_UNSIGNED_BYTE, pixels);
RenderSystem.setTextureMinFilter(GL11.GL_LINEAR);
RenderSystem.setTextureMagFilter(GL11.GL_LINEAR);
RenderSystem.setTextureWrapS(GL12.GL_CLAMP_TO_EDGE);
RenderSystem.setTextureWrapT(GL12.GL_CLAMP_TO_EDGE);
RenderSystem.bindTexture(0);
return textureId;
}
/**
* 渲染文字(使用 RenderSystem 封装,不使用 glBegin/glEnd
*
* @param text 要显示的文字
* @param x 世界坐标 X
* @param y 世界坐标 Y
* @param color 文字颜色
*/
public void renderText(String text, float x, float y, Vector4f color) {
if (!initialized || text == null || text.isEmpty()) return;
RenderSystem.assertOnRenderThread();
// 保存当前状态
RenderSystem.pushState();
try {
// 检查文本着色器是否存在,如果不存在则创建默认的
ShaderProgram shader = ShaderManagement.getShaderProgram("TextShader");
shader.use();
ShaderManagement.setUniformVec4(shader, "uColor", color);
ShaderManagement.setUniformInt(shader, "uTexture", 0);
RenderSystem.enableBlend();
RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
RenderSystem.disableDepthTest();
RenderSystem.activeTexture(GL13.GL_TEXTURE0);
RenderSystem.bindTexture(fontTextureId);
Vector2f offset = ModelRender.getCameraOffset();
float px = x + offset.x;
float py = y + offset.y;
try (MemoryStack stack = MemoryStack.stackPush()) {
STBTTAlignedQuad q = STBTTAlignedQuad.mallocStack(stack);
float[] xpos = {px};
float[] ypos = {py};
Tesselator tesselator = Tesselator.getInstance();
BufferBuilder builder = tesselator.getBuilder();
// 计算估计的顶点数量每个字符6个顶点2个三角形
int estimatedVertexCount = text.length() * 6;
// 修复begin方法需要2个参数
builder.begin(RenderSystem.DRAW_TRIANGLES, estimatedVertexCount);
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if (c < firstChar || c >= firstChar + charCount) continue;
stbtt_GetBakedQuad(charData, bitmapWidth, bitmapHeight, c - firstChar, xpos, ypos, q, true);
// 使用两个三角形组成一个四边形
// 第一个三角形
builder.vertex(q.x0(), q.y0(), q.s0(), q.t0());
builder.vertex(q.x1(), q.y0(), q.s1(), q.t0());
builder.vertex(q.x0(), q.y1(), q.s0(), q.t1());
// 第二个三角形
builder.vertex(q.x1(), q.y0(), q.s1(), q.t0());
builder.vertex(q.x1(), q.y1(), q.s1(), q.t1());
builder.vertex(q.x0(), q.y1(), q.s0(), q.t1());
}
tesselator.end();
}
RenderSystem.checkGLError("renderText");
} finally {
// 恢复之前的状态
RenderSystem.popState();
}
}
/**
* 清理字体资源
*/
public void cleanup() {
if (fontTextureId != 0) {
RenderSystem.deleteTextures(fontTextureId);
fontTextureId = 0;
}
if (charData != null) {
charData.free();
charData = null;
}
initialized = false;
}
public boolean isInitialized() {
return initialized;
}
public int getFontTextureId() {
return fontTextureId;
}
}