Files
window-axis-innovators-box/src/main/java/com/chuangzhou/vivid2D/render/ModelRender.java
tzdwindows 7 8a01020cbe refactor(render):重构关键帧插值器以支持顶点变形- 更新 FrameInterpolator以直接操作 Mesh2D 的一级顶点
- 引入 deformationVertex 参数控制带 VertexTag.DEFORMATION 标签的顶点- 移除对 secondaryVertex 的旧支持及相关冗余代码
- 简化插值计算逻辑并提高角度单位转换容差
- 优化顶点目标计算方法并重命名为 DeformationVertexTarget
- 清理无用的反射回退和安全读取机制- 移除 liquify 工具相关的顶点渲染快捷键控制
- 删除已废弃的 LiquifyTargetPartRander 类文件
-优化导入语句并更新相关类引用路径
2025-11-10 23:12:19 +08:00

1393 lines
50 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.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 模型完整渲染系统
*
* <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.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<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() {
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");
}
// ================== 缩略图渲染方法 ==================
/**
* 渲染模型缩略图(图层式渲染,不受摄像机控制)
*
* <p>该方法提供类似PS图层预览的缩略图渲染功能</p>
* <ul>
* <li>固定位置和大小,不受摄像机影响</li>
* <li>自动缩放确保模型完全可见</li>
* <li>禁用复杂效果以提高性能</li>
* <li>独立的渲染状态管理</li>
* </ul>
*
* @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<LightSource> 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<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() : 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<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 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();
}
}