feat(render): 实现摄像机系统和文字渲染功能

- 添加 Camera 类,支持位置、缩放、Z轴控制- 在 ModelRender 中集成摄像机投影矩阵计算
- 实现屏幕坐标到世界坐标的转换方法
- 添加默认文字渲染器和字体加载逻辑
- 在渲染面板中添加摄像机控制的鼠标手势支持
- 支持通过鼠标滚轮进行摄像机缩放操作
- 添加摄像机状态显示和调试信息渲染
- 实现多选框渲染逻辑的重构和优化
-修复坐标系变换相关的边界框计算问题
- 增加摄像机启用/禁用快捷键支持cyon 等- 添加对 Linux 和 macOS 的 LWJGL 原生库支持
- 将任务定义方式从 task 改为 tasks.register 以提高性能
- 更新部分 JavaFX 和其他图形库的版本
-优化依赖项排列顺序,增强可读性与逻辑分组
This commit is contained in:
tzdwindows 7
2025-10-24 20:05:40 +08:00
parent 2278c5d0c7
commit 210ac72a38
12 changed files with 1911 additions and 312 deletions

View File

@@ -2,6 +2,7 @@ package com.chuangzhou.vivid2D.render;
import com.chuangzhou.vivid2D.render.model.Model2D;
import com.chuangzhou.vivid2D.render.model.ModelPart;
import com.chuangzhou.vivid2D.render.systems.Camera;
import com.chuangzhou.vivid2D.render.systems.buffer.BufferBuilder;
import com.chuangzhou.vivid2D.render.systems.buffer.Tesselator;
import com.chuangzhou.vivid2D.render.model.util.LightSource;
@@ -18,6 +19,7 @@ import org.lwjgl.opengl.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
@@ -73,14 +75,14 @@ public final class ModelRender {
* 默认值800像素
* @see #setViewport(int, int)
*/
private static int viewportWidth = 800;
static int viewportWidth = 800;
/**
* 视口高度(像素),定义渲染区域的大小
* 默认值600像素
* @see #setViewport(int, int)
*/
private static int viewportHeight = 600;
static int viewportHeight = 600;
/**
* 清除颜色RGBA用于在每帧开始时清空颜色缓冲区
@@ -179,8 +181,105 @@ public final class ModelRender {
*/
public static boolean renderLightPositions = true;
// ================== 内部类ShaderProgram ==================
// ================== 摄像机状态 ==================
/**
* 默认摄像机,用于控制场景的视图和缩放
* 默认位置:(0, 0)
*/
private static final Camera camera = new Camera();
// ================== 字体管理 ==================
private static TextRenderer defaultTextRenderer = null;
private static final int FONT_BITMAP_WIDTH = 512;
private static final int FONT_BITMAP_HEIGHT = 512;
private static final int FONT_FIRST_CHAR = 32;
private static final int FONT_CHAR_COUNT = 96;
// ================== 摄像机API方法 ==================
/**
* 获取全局摄像机实例
*/
public static Camera getCamera() {
return camera;
}
/**
* 设置摄像机位置
*/
public static void setCameraPosition(float x, float y) {
camera.setPosition(x, y);
}
/**
* 设置摄像机缩放
*/
public static void setCameraZoom(float zoom) {
camera.setZoom(zoom);
}
/**
* 设置摄像机Z轴位置
*/
public static void setCameraZPosition(float z) {
camera.setZPosition(z);
}
/**
* 移动摄像机
*/
public static void moveCamera(float dx, float dy) {
camera.move(dx, dy);
}
/**
* 缩放摄像机
*/
public static void zoomCamera(float factor) {
camera.zoom(factor);
}
/**
* 重置摄像机
*/
public static void resetCamera() {
camera.reset();
}
/**
* 启用/禁用摄像机
*/
public static void setCameraEnabled(boolean enabled) {
camera.setEnabled(enabled);
}
/**
* 构建考虑摄像机变换的投影矩阵
*/
private static Matrix3f buildCameraProjection(int width, int height) {
Matrix3f m = new Matrix3f();
if (camera.isEnabled()) {
// 考虑摄像机缩放和平移
float zoom = camera.getZoom();
Vector2f pos = camera.getPosition();
m.set(
2.0f * zoom / width, 0.0f, -1.0f - (2.0f * zoom * pos.x / width),
0.0f, -2.0f * zoom / height, 1.0f + (2.0f * zoom * pos.y / height),
0.0f, 0.0f, 1.0f
);
} else {
// 原始投影矩阵
m.set(
2.0f / width, 0.0f, -1.0f,
0.0f, -2.0f / height, 1.0f,
0.0f, 0.0f, 1.0f
);
}
return m;
}
// ================== 内部类MeshGLResources ==================
private static class MeshGLResources {
@@ -221,10 +320,47 @@ public final class ModelRender {
throw ex;
}
createDefaultTexture();
RenderSystem.viewport(0, 0, viewportWidth, viewportHeight);
RenderSystem.finishInitialization();
try {
// 初始化默认字体(可替换为你自己的 TTF 数据)
ByteBuffer fontData = null;
try {
fontData = RenderSystem.loadWindowsFont("Arial.ttf");
} catch (Exception e) {
logger.warn("Failed to load Arial.ttf, trying fallback fonts", e);
// 尝试其他字体
try {
fontData = RenderSystem.loadWindowsFont("arial.ttf");
} catch (Exception e2) {
try {
fontData = RenderSystem.loadWindowsFont("times.ttf");
} catch (Exception e3) {
logger.error("All font loading attempts failed");
}
}
}
if (fontData != null && fontData.capacity() > 0) {
defaultTextRenderer = new TextRenderer(FONT_BITMAP_WIDTH, FONT_BITMAP_HEIGHT, FONT_FIRST_CHAR, FONT_CHAR_COUNT);
RenderSystem.checkGLError("TextRenderer constructor");
defaultTextRenderer.initialize(fontData, 32.0f); // 字体像素高度 32
RenderSystem.checkGLError("defaultTextRenderer initialization");
if (!defaultTextRenderer.isInitialized()) {
logger.error("TextRenderer failed to initialize properly");
}
} else {
logger.error("No valid font data available for text rendering");
}
} catch (Exception e) {
logger.warn("Failed to initialize default text renderer", e);
}
initialized = true;
logger.info("ModelRender initialized successfully");
}
@@ -456,8 +592,8 @@ public final class ModelRender {
return;
}
// 设置投影与视图矩阵(所有着色器都需要
Matrix3f proj = buildOrthoProjection(viewportWidth, viewportHeight);
// 设置投影与视图矩阵(使用摄像机变换
Matrix3f proj = buildCameraProjection(viewportWidth, viewportHeight);
Matrix3f view = new Matrix3f().identity();
// 1. 首先设置默认着色器
@@ -469,6 +605,10 @@ public final class ModelRender {
setUniformMatrix3(defaultProgram, "uViewMatrix", view);
RenderSystem.checkGLError("after_set_default_matrices");
// 设置摄像机Z轴位置如果着色器支持
setUniformFloatInternal(defaultProgram, "uCameraZ", camera.getZPosition());
RenderSystem.checkGLError("after_set_camera_z");
// 添加光源数据上传到默认着色器
uploadLightsToShader(defaultProgram, model);
RenderSystem.checkGLError("after_upload_lights");
@@ -495,10 +635,22 @@ public final class ModelRender {
RenderSystem.checkGLError("after_render_colliders");
}
defaultProgram.stop();
if (defaultTextRenderer != null) {
String camInfo = String.format("Camera X: %.2f Y: %.2f Zoom: %.2f",
camera.getPosition().x,
camera.getPosition().y,
camera.getZoom());
float x = 10.0f;
float y = viewportHeight - 30.0f;
Vector4f color = new Vector4f(1.0f, 1.0f, 1.0f, 1.0f);
renderText(camInfo, x, y, color);
RenderSystem.checkGLError("renderText");
}
RenderSystem.checkGLError("render_end");
}
/**
* 设置所有非默认着色器的顶点坐标相关uniform
*/
@@ -534,6 +686,9 @@ public final class ModelRender {
// 设置基础模型矩阵为单位矩阵
setUniformMatrix3(program, "uModelMatrix", new Matrix3f().identity());
// 设置摄像机Z轴位置
setUniformFloatInternal(program, "uCameraZ", camera.getZPosition());
RenderSystem.checkGLError("setupNonDefaultShaders_" + shader.getShaderName());
} catch (Exception e) {
@@ -840,6 +995,41 @@ public final class ModelRender {
return m;
}
/**
* 渲染文字
* @param text 文字内容
* @param x 世界坐标 X
* @param y 世界坐标 Y
* @param color RGBA 颜色
*/
public static void renderText(String text, float x, float y, Vector4f color) {
if (!initialized || defaultTextRenderer == null) return;
RenderSystem.assertOnRenderThread();
Vector2f offset = getCameraOffset();
float px = x + offset.x;
float py = y + offset.y;
defaultTextRenderer.renderText(text, px, py, color);
}
/**
* 获取默认摄像机与当前摄像机之间的偏移量
* @return Vector2f 偏移向量 (dx, dy)
*/
public static Vector2f getCameraOffset() {
float width = viewportWidth;
float height = viewportHeight;
float zoom = camera.getZoom();
Vector2f pos = camera.getPosition();
float tx = -1.0f - (2.0f * zoom * pos.x / width);
float ty = 1.0f + (2.0f * zoom * pos.y / height);
float tx0 = -1.0f;
float ty0 = 1.0f;
float offsetX = tx - tx0;
float offsetY = ty - ty0;
offsetX = -offsetX * width / 2.0f / zoom;
offsetY = offsetY * height / 2.0f / zoom;
return new Vector2f(offsetX, offsetY);
}
public static void setViewport(int width, int height) {
viewportWidth = Math.max(1, width);
viewportHeight = Math.max(1, height);