feat(render): 实现摄像机系统和文字渲染功能
- 添加 Camera 类,支持位置、缩放、Z轴控制- 在 ModelRender 中集成摄像机投影矩阵计算 - 实现屏幕坐标到世界坐标的转换方法 - 添加默认文字渲染器和字体加载逻辑 - 在渲染面板中添加摄像机控制的鼠标手势支持 - 支持通过鼠标滚轮进行摄像机缩放操作 - 添加摄像机状态显示和调试信息渲染 - 实现多选框渲染逻辑的重构和优化 -修复坐标系变换相关的边界框计算问题 - 增加摄像机启用/禁用快捷键支持cyon 等- 添加对 Linux 和 macOS 的 LWJGL 原生库支持 - 将任务定义方式从 task 改为 tasks.register 以提高性能 - 更新部分 JavaFX 和其他图形库的版本 -优化依赖项排列顺序,增强可读性与逻辑分组
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user