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

1044 lines
38 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.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;
import com.chuangzhou.vivid2D.render.model.util.Mesh2D;
import com.chuangzhou.vivid2D.render.model.util.PhysicsSystem;
import com.chuangzhou.vivid2D.render.systems.RenderSystem;
import com.chuangzhou.vivid2D.render.systems.sources.CompleteShader;
import com.chuangzhou.vivid2D.render.systems.sources.ShaderProgram;
import com.chuangzhou.vivid2D.render.systems.sources.ShaderManagement;
import org.joml.Matrix3f;
import org.joml.Vector2f;
import org.joml.Vector4f;
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;
/**
* vivid2D 模型完整渲染系统
*
* <p>该系统提供了完整的 vivid2D 模型加载、渲染和显示功能,支持多种渲染模式和效果:</p>
*
* <ul>
* <li>基础模型渲染</li>
* <li>光照效果渲染</li>
* <li>纹理贴图渲染</li>
* <li>模型加载与解析</li>
* </ul>
*
* <h3>使用示例:</h3>
* <ul>
* <li>{@link com.chuangzhou.vivid2D.test.ModelLoadTest} - 模型加载测试</li>
* <li>{@link com.chuangzhou.vivid2D.test.ModelRenderLightingTest} - 光照渲染测试</li>
* <li>{@link com.chuangzhou.vivid2D.test.ModelRenderTest} - 基础渲染测试</li>
* <li>{@link com.chuangzhou.vivid2D.test.ModelRenderTest2} - 进阶渲染测试</li>
* <li>{@link com.chuangzhou.vivid2D.test.ModelRenderTextureTest} - 纹理渲染测试</li>
* <li>{@link com.chuangzhou.vivid2D.test.ModelTest} - 基础模型测试</li>
* <li>{@link com.chuangzhou.vivid2D.test.ModelTest2} - 进阶模型测试</li>
* </ul>
*
* @author tzdwindows
* @version 1.2
* @since 2025-10-13
*/
public final class ModelRender {
/**
* 渲染系统日志记录器,用于记录渲染过程中的调试信息、错误和性能数据
*/
private static final Logger logger = LoggerFactory.getLogger(ModelRender.class);
/**
* 私有构造函数,防止外部实例化 - 这是一个工具类,只包含静态方法
*/
private ModelRender() { /* no instances */ }
// ================== 全局状态 ==================
/**
* 渲染系统初始化状态标志,确保系统只初始化一次
* @see #initialize()
* @see #isInitialized()
*/
private static boolean initialized = false;
/**
* 视口宽度(像素),定义渲染区域的大小
* 默认值800像素
* @see #setViewport(int, int)
*/
static int viewportWidth = 800;
/**
* 视口高度(像素),定义渲染区域的大小
* 默认值600像素
* @see #setViewport(int, int)
*/
static int viewportHeight = 600;
/**
* 清除颜色RGBA用于在每帧开始时清空颜色缓冲区
* 默认值:黑色不透明 (0.0f, 0.0f, 0.0f, 1.0f)
* @see RenderSystem#clearColor(float, float, float, float)
*/
private static final Vector4f CLEAR_COLOR = new Vector4f(0.0f, 0.0f, 0.0f, 1.0f);
/**
* 深度测试启用标志,控制是否进行深度缓冲测试
* 在2D渲染中通常禁用以提高性能
* 默认值false禁用
* @see RenderSystem#enableDepthTest()
* @see RenderSystem#disableDepthTest()
*/
private static final boolean enableDepthTest = false;
/**
* 混合功能启用标志,控制透明度和颜色混合
* 默认值true启用
* @see RenderSystem#enableBlend()
* @see RenderSystem#disableBlend()
*/
private static final boolean enableBlending = true;
/**
* 最大光源数量,用于限制同时启用的光源数量
* 默认值80
*/
private static final int MAX_LIGHTS = 80;
// ================== 着色器与资源管理 ==================
/**
* 默认着色器程序,用于大多数模型的渲染
* 包含基础的光照、纹理和变换功能
* @see #compileDefaultShader()
*/
private static ShaderProgram defaultProgram = null;
/**
* 网格GPU资源缓存管理已上传到GPU的网格数据
* 键Mesh2D对象
* 值对应的OpenGL资源VAO、VBO、EBO
* @see MeshGLResources
*/
private static final Map<Mesh2D, MeshGLResources> meshResources = new HashMap<>();
/**
* 纹理单元分配器,用于管理多个纹理的绑定
* 确保不同的纹理绑定到正确的纹理单元
* 默认从0开始递增分配
*/
private static final AtomicInteger textureUnitAllocator = new AtomicInteger(0);
/**
* 默认白色纹理ID当模型没有指定纹理时使用
* 这是一个1x1的纯白色纹理确保模型有基本的颜色显示
* @see #createDefaultTexture()
*/
private static int defaultTextureId = 0;
// ================== 碰撞箱渲染配置 ==================
/**
* 碰撞箱渲染开关,控制是否在场景中显示物理碰撞体的轮廓
* 调试时非常有用,可以直观看到碰撞边界
* 默认值true启用
*/
public static boolean renderColliders = true;
/**
* 碰撞箱线框宽度,控制碰撞体轮廓线的粗细
* 单位:像素
* 默认值1.0f
*/
public static float colliderLineWidth = 1.0f;
/**
* 碰撞箱颜色RGBA定义碰撞体轮廓的显示颜色
* 默认值:白色不透明 (1.0f, 1.0f, 1.0f, 1.0f)
*/
public static Vector4f colliderColor = new Vector4f(1.0f, 1.0f, 1.0f, 1.0f);
/**
* 圆形碰撞体细分数量,控制圆形碰撞体的平滑度
* 值越高圆形越平滑,但渲染开销也越大
* 默认值32在性能和视觉效果间取得平衡
*/
private static final int CIRCLE_SEGMENTS = 32;
/**
* 光源位置渲染开关,控制是否在场景中显示光源的位置
* 用点状标记显示每个启用的光源位置
* 默认值true启用
*/
public static boolean renderLightPositions = true;
// ================== 摄像机状态 ==================
/**
* 默认摄像机,用于控制场景的视图和缩放
* 默认位置:(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 {
int vao = 0;
int vbo = 0;
int ebo = 0;
boolean initialized = false;
void dispose() {
if (ebo != 0) { GL15.glDeleteBuffers(ebo); ebo = 0; }
if (vbo != 0) { GL15.glDeleteBuffers(vbo); vbo = 0; }
if (vao != 0) { GL30.glDeleteVertexArrays(vao); vao = 0; }
initialized = false;
}
}
// ================== 初始化 / 清理 ==================
public static synchronized void initialize() {
if (initialized) return;
logger.info("Initializing ModelRender...");
// 初始化渲染系统
RenderSystem.beginInitialization();
RenderSystem.initRenderThread();
logGLInfo();
setupGLState();
try {
compileDefaultShader();
// 初始化所有非默认着色器的基础信息
initNonDefaultShaders();
} catch (RuntimeException ex) {
logger.error("Failed to compile default shader: {}", ex.getMessage());
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");
}
/**
* 初始化所有非默认着色器的基础信息(顶点坐标等)
*/
private static void initNonDefaultShaders() {
List<CompleteShader> shaderList = ShaderManagement.getShaderList();
if (shaderList == null || shaderList.isEmpty()) {
logger.info("No shaders found to initialize");
return;
}
int nonDefaultCount = 0;
for (CompleteShader shader : shaderList) {
// 跳过默认着色器,只初始化非默认的
if (shader.isDefaultShader()) {
continue;
}
try {
// 获取着色器程序
ShaderProgram program = ShaderManagement.getShaderProgram(shader.getShaderName());
if (program == null) {
logger.warn("Shader program not found for: {}", shader.getShaderName());
continue;
}
// 设置着色器的基础uniforms主要是顶点坐标相关的
initShaderBasicUniforms(program, shader);
nonDefaultCount++;
logger.debug("Initialized non-default shader: {}", shader.getShaderName());
} catch (Exception e) {
logger.error("Failed to initialize non-default shader: {}", shader.getShaderName(), e);
}
}
logger.info("Initialized {} non-default shaders", nonDefaultCount);
}
/**
* 初始化着色器的基础uniforms顶点坐标相关
*/
private static void initShaderBasicUniforms(ShaderProgram program, CompleteShader shader) {
program.use();
try {
// 设置基础的变换矩阵为单位矩阵
setUniformMatrix3(program, "uModelMatrix", new Matrix3f().identity());
setUniformMatrix3(program, "uViewMatrix", new Matrix3f().identity());
// 设置投影矩阵(使用当前视口尺寸)
Matrix3f projection = buildOrthoProjection(viewportWidth, viewportHeight);
setUniformMatrix3(program, "uProjectionMatrix", projection);
// 设置基础颜色为白色
setUniformVec4Internal(program, "uColor", new Vector4f(1.0f, 1.0f, 1.0f, 1.0f));
// 设置基础不透明度
setUniformFloatInternal(program, "uOpacity", 1.0f);
// 设置纹理单元(如果有纹理的话)
setUniformIntInternal(program, "uTexture", 0);
RenderSystem.checkGLError("initShaderBasicUniforms_" + shader.getShaderName());
} finally {
program.stop();
}
}
private static void logGLInfo() {
logger.info("OpenGL Vendor: {}", RenderSystem.getVendor());
logger.info("OpenGL Renderer: {}", RenderSystem.getRenderer());
logger.info("OpenGL Version: {}", RenderSystem.getOpenGLVersion());
logger.info("GLSL Version: {}", RenderSystem.getGLSLVersion());
RenderSystem.logDetailedGLInfo();
}
private static void uploadLightsToShader(ShaderProgram sp, Model2D model) {
List<com.chuangzhou.vivid2D.render.model.util.LightSource> lights = model.getLights();
int idx = 0;
for (int i = 0; i < lights.size() && idx < MAX_LIGHTS; i++) {
com.chuangzhou.vivid2D.render.model.util.LightSource l = lights.get(i);
if (!l.isEnabled()) continue;
// 基础属性
setUniformVec2Internal(sp, "uLightsPos[" + idx + "]", l.isAmbient() ? new org.joml.Vector2f(0f, 0f) : l.getPosition());
setUniformVec3Internal(sp, "uLightsColor[" + idx + "]", l.getColor());
setUniformFloatInternal(sp, "uLightsIntensity[" + idx + "]", l.getIntensity());
setUniformIntInternal(sp, "uLightsIsAmbient[" + idx + "]", l.isAmbient() ? 1 : 0);
// 辉光相关(如果没有被设置也安全地上传默认值)
setUniformIntInternal(sp, "uLightsIsGlow[" + idx + "]", l.isGlow() ? 1 : 0);
setUniformVec2Internal(sp, "uLightsGlowDir[" + idx + "]", l.getGlowDirection() != null ? l.getGlowDirection() : new org.joml.Vector2f(0f, 0f));
setUniformFloatInternal(sp, "uLightsGlowIntensity[" + idx + "]", l.getGlowIntensity());
setUniformFloatInternal(sp, "uLightsGlowRadius[" + idx + "]", l.getGlowRadius());
setUniformFloatInternal(sp, "uLightsGlowAmount[" + idx + "]", l.getGlowAmount());
idx++;
}
// 上传实际有效光源数量
setUniformIntInternal(sp, "uLightCount", idx);
// 禁用剩余槽位(确保 shader 中不会读取到垃圾值)
for (int i = idx; i < MAX_LIGHTS; i++) {
setUniformFloatInternal(sp, "uLightsIntensity[" + i + "]", 0f);
setUniformIntInternal(sp, "uLightsIsAmbient[" + i + "]", 0);
setUniformVec3Internal(sp, "uLightsColor[" + i + "]", new org.joml.Vector3f(0f, 0f, 0f));
setUniformVec2Internal(sp, "uLightsPos[" + i + "]", new org.joml.Vector2f(0f, 0f));
// 关闭辉光槽
setUniformIntInternal(sp, "uLightsIsGlow[" + i + "]", 0);
setUniformVec2Internal(sp, "uLightsGlowDir[" + i + "]", new org.joml.Vector2f(0f, 0f));
setUniformFloatInternal(sp, "uLightsGlowIntensity[" + i + "]", 0f);
setUniformFloatInternal(sp, "uLightsGlowRadius[" + i + "]", 0f);
setUniformFloatInternal(sp, "uLightsGlowAmount[" + i + "]", 0f);
}
}
private static void setupGLState() {
RenderSystem.checkGLError("setupGLState_start");
RenderSystem.clearColor(CLEAR_COLOR.x, CLEAR_COLOR.y, CLEAR_COLOR.z, CLEAR_COLOR.w);
RenderSystem.checkGLError("after_clearColor");
if (enableBlending) {
RenderSystem.enableBlend();
RenderSystem.checkGLError("after_enableBlend");
RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
RenderSystem.checkGLError("after_blendFunc");
} else {
RenderSystem.disableBlend();
RenderSystem.checkGLError("after_disableBlend");
}
if (enableDepthTest) {
RenderSystem.enableDepthTest();
RenderSystem.checkGLError("after_enableDepthTest");
RenderSystem.depthFunc(GL11.GL_LEQUAL);
RenderSystem.checkGLError("after_depthFunc");
RenderSystem.depthMask(true);
RenderSystem.checkGLError("after_depthMask");
RenderSystem.clearDepth(1.0);
RenderSystem.checkGLError("after_clearDepth");
} else {
RenderSystem.disableDepthTest();
RenderSystem.checkGLError("after_disableDepthTest");
}
RenderSystem.checkGLError("after_disableCullFace");
}
private static void compileDefaultShader() {
ShaderManagement.compileAllShaders();
defaultProgram = ShaderManagement.getDefaultProgram();
if (defaultProgram == null) {
throw new RuntimeException("Failed to compile default shader: no default shader found");
}
}
private static void createDefaultTexture() {
RenderSystem.assertOnRenderThread();
defaultTextureId = RenderSystem.createDefaultTexture();
RenderSystem.checkGLError("createDefaultTexture");
}
public static synchronized void cleanup() {
if (!initialized) return;
logger.info("Cleaning up ModelRender...");
// mesh resources
for (MeshGLResources r : meshResources.values()) r.dispose();
meshResources.clear();
// 使用新的着色器管理系统清理着色器
ShaderManagement.cleanup();
defaultProgram = null;
// textures
if (defaultTextureId != 0) {
RenderSystem.deleteTextures(defaultTextureId);
defaultTextureId = 0;
}
initialized = false;
logger.info("ModelRender cleaned up");
}
// ================== 渲染流程 (已修改) ==================
public static void render(float deltaTime, Model2D model) {
if (!initialized) throw new IllegalStateException("ModelRender not initialized");
if (model == null) return;
// 确保在渲染线程
RenderSystem.assertOnRenderThread();
// 添加前置错误检查
RenderSystem.checkGLError("render_start");
// 物理系统更新
PhysicsSystem physics = model.getPhysics();
if (physics != null && physics.isEnabled()) {
physics.update(deltaTime, model);
}
model.update(deltaTime);
// 检查清除操作前的状态
RenderSystem.checkGLError("before_clear");
RenderSystem.clear(GL11.GL_COLOR_BUFFER_BIT | (enableDepthTest ? GL11.GL_DEPTH_BUFFER_BIT : 0));
RenderSystem.checkGLError("after_clear");
// 检查着色器程序
if (defaultProgram == null || defaultProgram.programId == 0) {
logger.error("Default shader program is not initialized");
return;
}
// 设置投影与视图矩阵(使用摄像机变换)
Matrix3f proj = buildCameraProjection(viewportWidth, viewportHeight);
Matrix3f view = new Matrix3f().identity();
// 1. 首先设置默认着色器
defaultProgram.use();
RenderSystem.checkGLError("after_use_default_program");
// 设置默认着色器的投影与视图
setUniformMatrix3(defaultProgram, "uProjectionMatrix", proj);
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");
// 2. 设置非默认着色器的顶点坐标相关uniform
setupNonDefaultShaders(proj, view);
RenderSystem.checkGLError("after_setup_non_default_shaders");
// 在渲染光源位置前检查
RenderSystem.checkGLError("before_render_light_positions");
renderLightPositions(model);
RenderSystem.checkGLError("after_render_light_positions");
// 递归渲染所有根部件(使用默认着色器)
Matrix3f identity = new Matrix3f().identity();
for (ModelPart p : model.getParts()) {
if (p.getParent() != null) continue;
renderPartRecursive(p, identity);
}
RenderSystem.checkGLError("after_render_parts");
if (renderColliders && physics != null) {
renderPhysicsColliders(physics);
RenderSystem.checkGLError("after_render_colliders");
}
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
*/
private static void setupNonDefaultShaders(Matrix3f projection, Matrix3f view) {
List<CompleteShader> shaderList = ShaderManagement.getShaderList();
if (shaderList == null || shaderList.isEmpty()) {
return;
}
// 保存当前绑定的着色器程序
int currentProgram = GL11.glGetInteger(GL20.GL_CURRENT_PROGRAM);
try {
for (CompleteShader shader : shaderList) {
// 跳过默认着色器
if (shader.isDefaultShader()) {
continue;
}
try {
// 获取着色器程序
ShaderProgram program = ShaderManagement.getShaderProgram(shader.getShaderName());
if (program == null || program.programId == 0) {
continue;
}
program.use();
// 只设置顶点坐标相关的uniform
setUniformMatrix3(program, "uProjectionMatrix", projection);
setUniformMatrix3(program, "uViewMatrix", view);
// 设置基础模型矩阵为单位矩阵
setUniformMatrix3(program, "uModelMatrix", new Matrix3f().identity());
// 设置摄像机Z轴位置
setUniformFloatInternal(program, "uCameraZ", camera.getZPosition());
RenderSystem.checkGLError("setupNonDefaultShaders_" + shader.getShaderName());
} catch (Exception e) {
logger.warn("Failed to setup non-default shader: {}", shader.getShaderName(), e);
}
}
} finally {
// 恢复之前绑定的着色器程序
if (currentProgram != 0) {
GL20.glUseProgram(currentProgram);
}
}
}
private static void renderLightPositions(Model2D model) {
if (!renderLightPositions) return;
// 设置灯泡颜色为光源的颜色
for (LightSource light : model.getLights()) {
if (!light.isEnabled()) continue;
// 使用光源的颜色来绘制灯泡
Vector4f lightColor = new Vector4f(light.getColor().x, light.getColor().y, light.getColor().z, 1.0f);
setUniformVec4Internal(defaultProgram, "uColor", lightColor);
// 绘制灯泡形状
drawLightBulb(light.getPosition(), light.getIntensity());
if (light.isAmbient()) {
drawCrossMark(light.getPosition(), light.getIntensity());
}
}
// 恢复原始颜色
setUniformVec4Internal(defaultProgram, "uColor", new Vector4f(1,1,1,1));
}
/**
* 绘制简洁的灯泡形状
* @param position 灯泡位置
* @param intensity 光源强度,用于控制灯泡大小
*/
private static void drawLightBulb(Vector2f position, float intensity) {
Tesselator tesselator = Tesselator.getInstance();
BufferBuilder builder = tesselator.getBuilder();
float bulbSize = 3.0f + (intensity / 10.0f);
int segments = 16;
builder.begin(RenderSystem.DRAW_TRIANGLE_FAN, segments + 2);
builder.vertex(position.x, position.y, 0.5f, 0.5f);
for (int i = 0; i <= segments; i++) {
double angle = 2.0 * Math.PI * i / segments;
float x = position.x + bulbSize * (float) Math.cos(angle);
float y = position.y + bulbSize * (float) Math.sin(angle);
builder.vertex(x, y, 0.5f, 0.5f);
}
tesselator.end();
}
/**
* 绘制十字标记(用于环境光)
*/
private static void drawCrossMark(Vector2f position, float size) {
Tesselator tesselator = Tesselator.getInstance();
BufferBuilder builder = tesselator.getBuilder();
float crossSize = size * 0.8f;
// 绘制水平线
builder.begin(RenderSystem.DRAW_LINES, 2);
builder.vertex(position.x - crossSize, position.y, 0.5f, 0.5f);
builder.vertex(position.x + crossSize, position.y, 0.5f, 0.5f);
tesselator.end();
// 绘制垂直线
builder.begin(RenderSystem.DRAW_LINES, 2);
builder.vertex(position.x, position.y - crossSize, 0.5f, 0.5f);
builder.vertex(position.x, position.y + crossSize, 0.5f, 0.5f);
tesselator.end();
}
/**
* 关键修改点:在渲染前确保更新 part 的 worldTransform
* 然后直接使用 part.getWorldTransform() 作为 uModelMatrix 传入 shader。
*/
private static void renderPartRecursive(ModelPart part, Matrix3f parentMat) {
// 确保 part 的 local/world 矩阵被计算(会更新 transformDirty
part.updateWorldTransform(parentMat, false);
// 直接使用已经计算好的 worldTransform
Matrix3f world = part.getWorldTransform();
// 先设置部件相关的 uniformopacity / blendMode / color 等)
setPartUniforms(defaultProgram, part);
// 把 world 矩阵传给 shaderuModelMatrix
setUniformMatrix3(defaultProgram, "uModelMatrix", world);
// 绘制本节点的所有 mesh将 world 传入 renderMesh
for (Mesh2D mesh : part.getMeshes()) {
renderMesh(mesh, world);
}
// 递归渲染子节点,继续传入当前 world 作为子节点的 parent
for (ModelPart child : part.getChildren()) {
renderPartRecursive(child, world);
}
}
private static void renderMesh(Mesh2D mesh, Matrix3f modelMatrix) {
if (!mesh.isVisible()) return;
// 如果 mesh 已经被烘焙到世界坐标,则传 identity 矩阵给 shader防止重复变换
Matrix3f matToUse = mesh.isBakedToWorld() ? new Matrix3f().identity() : modelMatrix;
// 设置纹理相关的uniform
if (mesh.getTexture() != null) {
mesh.getTexture().bind(0); // 绑定到纹理单元0
setUniformIntInternal(defaultProgram, "uTexture", 0);
} else {
// 使用默认白色纹理
RenderSystem.bindTexture(defaultTextureId);
setUniformIntInternal(defaultProgram, "uTexture", 0);
}
// 将模型矩阵设置为当前 mesh 使用的矩阵shader 内名为 uModelMatrix
setUniformMatrix3(defaultProgram, "uModelMatrix", matToUse);
// 调用 Mesh2D 的 draw 方法,传入当前使用的着色器程序和变换矩阵
mesh.draw(defaultProgram.programId, matToUse);
RenderSystem.checkGLError("renderMesh");
}
// ================== 渲染碰撞箱相关实现 ==================
private static void renderPhysicsColliders(PhysicsSystem physics) {
if (physics == null) {
logger.warn("renderPhysicsColliders: physics system is null");
return;
}
// 设置渲染状态
RenderSystem.checkGLError("before_set_line_width");
RenderSystem.lineWidth(colliderLineWidth);
RenderSystem.checkGLError("after_set_line_width");
RenderSystem.activeTexture(RenderSystem.GL_TEXTURE0);
RenderSystem.bindTexture(defaultTextureId);
RenderSystem.checkGLError("after_bind_texture");
setUniformIntInternal(defaultProgram, "uTexture", 0);
setUniformVec4Internal(defaultProgram, "uColor", colliderColor);
setUniformFloatInternal(defaultProgram, "uOpacity", 1.0f);
setUniformIntInternal(defaultProgram, "uBlendMode", 0);
setUniformIntInternal(defaultProgram, "uDebugMode", 0);
RenderSystem.checkGLError("after_set_uniforms");
// 使用单位矩阵作为 model碰撞体顶点按世界坐标提供
setUniformMatrix3(defaultProgram, "uModelMatrix", new Matrix3f().identity());
RenderSystem.checkGLError("after_set_model_matrix");
List<PhysicsSystem.PhysicsCollider> colliders = physics.getColliders();
if (colliders == null || colliders.isEmpty()) {
logger.debug("No colliders to render");
return;
}
int enabledColliders = 0;
for (PhysicsSystem.PhysicsCollider collider : colliders) {
if (collider == null || !collider.isEnabled()) continue;
RenderSystem.checkGLError("before_render_collider_" + enabledColliders);
if (collider instanceof PhysicsSystem.CircleCollider) {
PhysicsSystem.CircleCollider c = (PhysicsSystem.CircleCollider) collider;
if (c.getCenter() != null && c.getRadius() > 0) {
drawCircleColliderWire(c.getCenter(), c.getRadius());
enabledColliders++;
} else {
logger.warn("Invalid CircleCollider: center={}, radius={}", c.getCenter(), c.getRadius());
}
} else if (collider instanceof PhysicsSystem.RectangleCollider) {
PhysicsSystem.RectangleCollider r = (PhysicsSystem.RectangleCollider) collider;
if (r.getCenter() != null && r.getWidth() > 0 && r.getHeight() > 0) {
drawRectangleColliderWire(r.getCenter(), r.getWidth(), r.getHeight());
enabledColliders++;
} else {
logger.warn("Invalid RectangleCollider: center={}, width={}, height={}",
r.getCenter(), r.getWidth(), r.getHeight());
}
} else {
logger.warn("Unknown collider type: {}", collider.getClass().getSimpleName());
}
RenderSystem.checkGLError("after_render_collider_" + enabledColliders);
}
logger.debug("Rendered {} enabled colliders", enabledColliders);
// 恢复默认线宽
RenderSystem.lineWidth(1.0f);
RenderSystem.checkGLError("after_reset_line_width");
}
/**
* 绘制圆形碰撞框(线框)
* 使用临时 VAO/VBO每帧创建并删除简单实现
*/
private static void drawCircleColliderWire(Vector2f center, float radius) {
int segments = Math.max(8, CIRCLE_SEGMENTS);
Tesselator tesselator = Tesselator.getInstance();
BufferBuilder builder = tesselator.getBuilder();
builder.begin(RenderSystem.DRAW_LINE_LOOP, segments);
for (int i = 0; i < segments; i++) {
double ang = 2.0 * Math.PI * i / segments;
float x = center.x + radius * (float) Math.cos(ang);
float y = center.y + radius * (float) Math.sin(ang);
builder.vertex(x, y, 0.5f, 0.5f);
}
tesselator.end();
}
/**
* 绘制矩形碰撞框(线框)
*/
private static void drawRectangleColliderWire(Vector2f center, float width, float height) {
float halfW = width / 2.0f;
float halfH = height / 2.0f;
Tesselator tesselator = Tesselator.getInstance();
BufferBuilder builder = tesselator.getBuilder();
builder.begin(RenderSystem.DRAW_LINE_LOOP, 4);
builder.vertex(center.x - halfW, center.y - halfH, 0.5f, 0.5f);
builder.vertex(center.x + halfW, center.y - halfH, 0.5f, 0.5f);
builder.vertex(center.x + halfW, center.y + halfH, 0.5f, 0.5f);
builder.vertex(center.x - halfW, center.y + halfH, 0.5f, 0.5f);
tesselator.end();
}
// ================== uniform 设置辅助(内部使用,确保 program 已绑定) ==================
private static void setUniformIntInternal(ShaderProgram sp, String name, int value) {
int loc = sp.getUniformLocation(name);
if (loc != -1) RenderSystem.uniform1i(loc, value);
}
private static void setUniformVec3Internal(ShaderProgram sp, String name, org.joml.Vector3f vec) {
int loc = sp.getUniformLocation(name);
if (loc != -1) RenderSystem.uniform3f(loc, vec);
}
private static void setUniformVec2Internal(ShaderProgram sp, String name, org.joml.Vector2f vec) {
int loc = sp.getUniformLocation(name);
if (loc != -1) RenderSystem.uniform2f(loc, vec);
}
private static void setUniformFloatInternal(ShaderProgram sp, String name, float value) {
int loc = sp.getUniformLocation(name);
if (loc != -1) RenderSystem.uniform1f(loc, value);
}
private static void setUniformVec4Internal(ShaderProgram sp, String name, org.joml.Vector4f vec) {
int loc = sp.getUniformLocation(name);
if (loc != -1) RenderSystem.uniform4f(loc, vec);
}
private static void setUniformMatrix3(ShaderProgram sp, String name, org.joml.Matrix3f m) {
int loc = sp.getUniformLocation(name);
if (loc == -1) return;
RenderSystem.uniformMatrix3(loc, m);
}
// ================== 部件属性 ==================
private static void setPartUniforms(ShaderProgram sp, ModelPart part) {
setUniformFloatInternal(sp, "uOpacity", part.getOpacity());
int blend = 0;
ModelPart.BlendMode bm = part.getBlendMode();
if (bm != null) {
switch (bm) {
case ADDITIVE: blend = 1; break;
case MULTIPLY: blend = 2; break;
case SCREEN: blend = 3; break;
case NORMAL: default: blend = 0;
}
} else {
blend = 0;
}
setUniformIntInternal(sp, "uBlendMode", blend);
// 这里保留为白色,若需要部件 tint 请替换为 part 的 color 属性
setUniformVec4Internal(sp, "uColor", new Vector4f(1,1,1,1));
}
// ================== 工具 ==================
private static Matrix3f buildOrthoProjection(int width, int height) {
Matrix3f m = new Matrix3f();
m.set(
2.0f / width, 0.0f, -1.0f,
0.0f, -2.0f / height, 1.0f,
0.0f, 0.0f, 1.0f
);
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);
RenderSystem.viewport(0, 0, viewportWidth, viewportHeight);
}
// ================== 辅助:外部获取状态 ==================
public static boolean isInitialized() { return initialized; }
public static int getLoadedMeshCount() { return meshResources.size(); }
}