package com.chuangzhou.vivid2D.render; import com.chuangzhou.vivid2D.render.model.Model2D; import com.chuangzhou.vivid2D.render.model.ModelPart; import com.chuangzhou.vivid2D.render.model.util.LightSource; import com.chuangzhou.vivid2D.render.model.Mesh2D; import com.chuangzhou.vivid2D.render.model.util.PhysicsSystem; import com.chuangzhou.vivid2D.render.systems.Camera; 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.CompleteShader; import com.chuangzhou.vivid2D.render.systems.sources.ShaderManagement; import com.chuangzhou.vivid2D.render.systems.sources.ShaderProgram; import org.joml.Matrix3f; import org.joml.Vector2f; import org.joml.Vector3f; import org.joml.Vector4f; import org.lwjgl.opengl.GL11; import org.lwjgl.opengl.GL15; import org.lwjgl.opengl.GL20; import org.lwjgl.opengl.GL30; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; /** * vivid2D 模型完整渲染系统 * *

该系统提供了完整的 vivid2D 模型加载、渲染和显示功能,支持多种渲染模式和效果:

* * * *

使用示例:

* * * @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 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.loadFont("FZYTK.TTF"); } catch (Exception e) { logger.warn("Failed to load Arial.ttf, trying fallback fonts", e); // 尝试其他字体 try { fontData = RenderSystem.loadFont("arial.ttf"); } catch (Exception e2) { try { fontData = RenderSystem.loadFont("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, 20.0f); 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 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() { RenderSystem.logDetailedGLInfo(); } private static void uploadLightsToShader(ShaderProgram sp, Model2D model) { List 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"); } // ================== 缩略图渲染方法 ================== /** * 渲染模型缩略图(图层式渲染,不受摄像机控制) * *

该方法提供类似PS图层预览的缩略图渲染功能:

*
    *
  • 固定位置和大小,不受摄像机影响
  • *
  • 自动缩放确保模型完全可见
  • *
  • 禁用复杂效果以提高性能
  • *
  • 独立的渲染状态管理
  • *
* * @param model 要渲染的模型 * @param x 缩略图左上角X坐标(屏幕坐标) * @param y 缩略图左上角Y坐标(屏幕坐标) * @param width 缩略图宽度 * @param height 缩略图高度 */ public static void renderThumbnail(Model2D model, float x, float y, float width, float height) { if (!initialized) throw new IllegalStateException("ModelRender not initialized"); if (model == null) return; RenderSystem.assertOnRenderThread(); RenderSystem.checkGLError("renderThumbnail_start"); // 保存原始状态以便恢复 boolean originalRenderColliders = renderColliders; boolean originalRenderLightPositions = renderLightPositions; int originalViewportWidth = viewportWidth; int originalViewportHeight = viewportHeight; try { // 设置缩略图专用状态 renderColliders = false; renderLightPositions = false; // 设置缩略图视口(屏幕坐标) RenderSystem.viewport((int)x, (int)y, (int)width, (int)height); // 清除缩略图区域 RenderSystem.clear(GL11.GL_COLOR_BUFFER_BIT | (enableDepthTest ? GL11.GL_DEPTH_BUFFER_BIT : 0)); RenderSystem.checkGLError("thumbnail_after_clear"); // 简化版的模型更新(跳过物理系统) model.update(0.016f); // 使用固定时间步长 // 计算模型边界和缩放比例 ThumbnailBounds bounds = calculateThumbnailBounds(model, width, height); // 设置缩略图专用的正交投影(固定位置,不受摄像机影响) Matrix3f proj = buildThumbnailProjection(width, height); Matrix3f view = new Matrix3f().identity(); // 使用默认着色器 defaultProgram.use(); RenderSystem.checkGLError("thumbnail_after_use_program"); // 设置基础变换矩阵 setUniformMatrix3(defaultProgram, "uProjectionMatrix", proj); setUniformMatrix3(defaultProgram, "uViewMatrix", view); setUniformFloatInternal(defaultProgram, "uCameraZ", 0f); // 固定Z位置 RenderSystem.checkGLError("thumbnail_after_set_matrices"); // 简化光源:只使用环境光 setupThumbnailLighting(defaultProgram, model); RenderSystem.checkGLError("thumbnail_after_setup_lighting"); // 应用缩放和平移确保模型完全可见 Matrix3f thumbnailTransform = new Matrix3f( bounds.scale, 0, bounds.offsetX, 0, bounds.scale, bounds.offsetY, 0, 0, 1 ); // 递归渲染所有根部件(应用缩略图专用变换) for (ModelPart p : model.getParts()) { if (p.getParent() != null) continue; renderPartForThumbnail(p, thumbnailTransform); } RenderSystem.checkGLError("thumbnail_after_render_parts"); } finally { // 恢复原始状态 renderColliders = originalRenderColliders; renderLightPositions = originalRenderLightPositions; RenderSystem.viewport(0, 0, originalViewportWidth, originalViewportHeight); } RenderSystem.checkGLError("renderThumbnail_end"); } /** * 缩略图边界计算结果 */ private static class ThumbnailBounds { public float minX, maxX, minY, maxY; public float scale; public float offsetX, offsetY; } /** * 计算模型的边界和合适的缩放比例 */ private static ThumbnailBounds calculateThumbnailBounds(Model2D model, float thumbWidth, float thumbHeight) { ThumbnailBounds bounds = new ThumbnailBounds(); // 初始化为极值 bounds.minX = Float.MAX_VALUE; bounds.maxX = Float.MIN_VALUE; bounds.minY = Float.MAX_VALUE; bounds.maxY = Float.MIN_VALUE; // 计算模型的世界坐标边界(递归遍历所有部件) calculateModelBounds(model, bounds, new Matrix3f().identity()); // 如果模型没有有效边界,使用默认值 if (bounds.minX > bounds.maxX) { bounds.minX = -50f; bounds.maxX = 50f; bounds.minY = -50f; bounds.maxY = 50f; } // 计算模型宽度和高度 float modelWidth = bounds.maxX - bounds.minX; float modelHeight = bounds.maxY - bounds.minY; // 计算中心点 float centerX = (bounds.minX + bounds.maxX) * 0.5f; float centerY = (bounds.minY + bounds.maxY) * 0.5f; // 计算缩放比例(考虑边距) float margin = 0.1f; // 10%边距 float scaleX = (thumbWidth * (1 - margin)) / modelWidth; float scaleY = (thumbHeight * (1 - margin)) / modelHeight; bounds.scale = Math.min(scaleX, scaleY); // 计算偏移量(将模型中心对齐到缩略图中心) bounds.offsetX = -centerX; bounds.offsetY = -centerY; return bounds; } /** * 递归计算模型的边界 */ private static void calculateModelBounds(Model2D model, ThumbnailBounds bounds, Matrix3f parentTransform) { for (ModelPart part : model.getParts()) { if (part.getParent() != null) continue; // 只处理根部件 // 计算部件的世界变换 part.updateWorldTransform(parentTransform, false); Matrix3f worldTransform = part.getWorldTransform(); // 计算部件的边界 calculatePartBounds(part, bounds, worldTransform); // 递归处理子部件 for (ModelPart child : part.getChildren()) { calculateModelBoundsForPart(child, bounds, worldTransform); } } } /** * 递归计算部件及其子部件的边界 */ private static void calculateModelBoundsForPart(ModelPart part, ThumbnailBounds bounds, Matrix3f parentTransform) { part.updateWorldTransform(parentTransform, false); Matrix3f worldTransform = part.getWorldTransform(); calculatePartBounds(part, bounds, worldTransform); for (ModelPart child : part.getChildren()) { calculateModelBoundsForPart(child, bounds, worldTransform); } } /** * 计算单个部件的边界 */ private static void calculatePartBounds(ModelPart part, ThumbnailBounds bounds, Matrix3f worldTransform) { for (Mesh2D mesh : part.getMeshes()) { if (!mesh.isVisible()) continue; // 获取网格的顶点数据 float[] vertices = mesh.getVertices(); // 假设有这个方法获取原始顶点 if (vertices == null) continue; // 变换顶点并更新边界 for (int i = 0; i < vertices.length; i += 3) { // 假设顶点格式:x, y, z float x = vertices[i]; float y = vertices[i + 1]; // 应用世界变换 Vector3f transformed = new Vector3f(x, y, 1.0f); worldTransform.transform(transformed); // 更新边界 bounds.minX = Math.min(bounds.minX, transformed.x); bounds.maxX = Math.max(bounds.maxX, transformed.x); bounds.minY = Math.min(bounds.minY, transformed.y); bounds.maxY = Math.max(bounds.maxY, transformed.y); } } } /** * 构建缩略图专用的正交投影矩阵 */ private static Matrix3f buildThumbnailProjection(float width, float 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; } /** * 缩略图专用的部件渲染 */ public static void renderPartForThumbnail(ModelPart part, Matrix3f parentTransform) { part.updateWorldTransform(parentTransform, false); Matrix3f world = part.getWorldTransform(); setPartUniforms(defaultProgram, part); setUniformMatrix3(defaultProgram, "uModelMatrix", world); for (Mesh2D mesh : part.getMeshes()) { renderMeshForThumbnail(mesh, world); } for (ModelPart child : part.getChildren()) { renderPartForThumbnail(child, world); } } /** * 缩略图专用的网格渲染 */ private static void renderMeshForThumbnail(Mesh2D mesh, Matrix3f modelMatrix) { if (!mesh.isVisible()) return; Matrix3f matToUse = mesh.isBakedToWorld() ? new Matrix3f().identity() : new Matrix3f(modelMatrix); if (mesh.getTexture() != null) { mesh.getTexture().bind(0); setUniformIntInternal(defaultProgram, "uTexture", 0); } else { RenderSystem.bindTexture(defaultTextureId); setUniformIntInternal(defaultProgram, "uTexture", 0); } setUniformMatrix3(defaultProgram, "uModelMatrix", matToUse); mesh.draw(defaultProgram.programId, matToUse); RenderSystem.checkGLError("renderMeshForThumbnail"); } /** * 设置缩略图专用的简化光照 */ private static void setupThumbnailLighting(ShaderProgram sp, Model2D model) { List lights = model.getLights(); int ambientLightCount = 0; // 查找环境光 for (int i = 0; i < lights.size() && ambientLightCount < 1; i++) { LightSource light = lights.get(i); if (light.isEnabled() && light.isAmbient()) { setUniformVec2Internal(sp, "uLightsPos[0]", new Vector2f(0f, 0f)); setUniformVec3Internal(sp, "uLightsColor[0]", light.getColor()); setUniformFloatInternal(sp, "uLightsIntensity[0]", light.getIntensity()); setUniformIntInternal(sp, "uLightsIsAmbient[0]", 1); setUniformIntInternal(sp, "uLightsIsGlow[0]", 0); ambientLightCount++; } } // 如果没有环境光,创建一个默认的环境光 if (ambientLightCount == 0) { setUniformVec2Internal(sp, "uLightsPos[0]", new Vector2f(0f, 0f)); setUniformVec3Internal(sp, "uLightsColor[0]", new Vector3f(0.8f, 0.8f, 0.8f)); setUniformFloatInternal(sp, "uLightsIntensity[0]", 1.0f); setUniformIntInternal(sp, "uLightsIsAmbient[0]", 1); setUniformIntInternal(sp, "uLightsIsGlow[0]", 0); ambientLightCount = 1; } setUniformIntInternal(sp, "uLightCount", ambientLightCount); // 禁用所有其他光源槽位 for (int i = ambientLightCount; i < MAX_LIGHTS; i++) { setUniformFloatInternal(sp, "uLightsIntensity[" + i + "]", 0f); setUniformIntInternal(sp, "uLightsIsAmbient[" + i + "]", 0); } } /** * 设置所有非默认着色器的顶点坐标相关uniform */ private static void setupNonDefaultShaders(Matrix3f projection, Matrix3f view) { List 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(); // 先设置部件相关的 uniform(opacity / blendMode / color 等) setPartUniforms(defaultProgram, part); // 把 world 矩阵传给 shader(uModelMatrix) 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() : new Matrix3f(modelMatrix); // 手动应用摄像机偏移 Vector2f offset = getCameraOffset(); matToUse.m20(matToUse.m20() - offset.x); matToUse.m21(matToUse.m21() - offset.y); // 设置纹理相关的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 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 c) { 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 r) { 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)); } public static TextRenderer getTextRenderer() { return defaultTextRenderer; } // ================== 工具 ================== private static Matrix3f buildOrthoProjection(int width, int height) { Matrix3f m = new Matrix3f(); // 这个投影把屏幕像素坐标(x in [0,width], y in [0,height])映射到 NDC [-1,1]x[1,-1] 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(); } }