2025-10-08 11:08:57 +08:00
|
|
|
|
package com.chuangzhou.vivid2D.render;
|
|
|
|
|
|
|
|
|
|
|
|
import com.chuangzhou.vivid2D.render.model.Model2D;
|
|
|
|
|
|
import com.chuangzhou.vivid2D.render.model.ModelPart;
|
2025-10-17 01:48:07 +08:00
|
|
|
|
import com.chuangzhou.vivid2D.render.systems.buffer.BufferBuilder;
|
|
|
|
|
|
import com.chuangzhou.vivid2D.render.systems.buffer.Tesselator;
|
2025-10-11 20:21:11 +08:00
|
|
|
|
import com.chuangzhou.vivid2D.render.model.util.LightSource;
|
2025-10-08 11:08:57 +08:00
|
|
|
|
import com.chuangzhou.vivid2D.render.model.util.Mesh2D;
|
2025-10-12 08:16:42 +08:00
|
|
|
|
import com.chuangzhou.vivid2D.render.model.util.PhysicsSystem;
|
2025-10-17 01:48:07 +08:00
|
|
|
|
import com.chuangzhou.vivid2D.render.systems.RenderSystem;
|
|
|
|
|
|
import com.chuangzhou.vivid2D.render.systems.ShaderSources;
|
2025-10-08 11:08:57 +08:00
|
|
|
|
import org.joml.Matrix3f;
|
2025-10-08 12:30:37 +08:00
|
|
|
|
import org.joml.Vector2f;
|
2025-10-08 11:08:57 +08:00
|
|
|
|
import org.joml.Vector4f;
|
|
|
|
|
|
import org.lwjgl.opengl.*;
|
2025-10-12 08:41:34 +08:00
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
|
|
import org.slf4j.LoggerFactory;
|
2025-10-08 11:08:57 +08:00
|
|
|
|
|
|
|
|
|
|
import java.util.*;
|
|
|
|
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
|
|
|
|
|
2025-10-08 15:33:26 +08:00
|
|
|
|
import static org.lwjgl.opengl.GL20.glGetUniformLocation;
|
|
|
|
|
|
|
2025-10-08 11:08:57 +08:00
|
|
|
|
/**
|
2025-10-13 22:12:30 +08:00
|
|
|
|
* 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
|
2025-10-17 01:48:07 +08:00
|
|
|
|
* @version 1.2
|
2025-10-13 22:12:30 +08:00
|
|
|
|
* @since 2025-10-13
|
2025-10-08 11:08:57 +08:00
|
|
|
|
*/
|
|
|
|
|
|
public final class ModelRender {
|
2025-10-17 01:48:07 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 渲染系统日志记录器,用于记录渲染过程中的调试信息、错误和性能数据
|
|
|
|
|
|
*/
|
2025-10-12 08:41:34 +08:00
|
|
|
|
private static final Logger logger = LoggerFactory.getLogger(ModelRender.class);
|
2025-10-17 01:48:07 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 私有构造函数,防止外部实例化 - 这是一个工具类,只包含静态方法
|
|
|
|
|
|
*/
|
2025-10-08 11:08:57 +08:00
|
|
|
|
private ModelRender() { /* no instances */ }
|
|
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
// ================== 全局状态 ==================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 渲染系统初始化状态标志,确保系统只初始化一次
|
|
|
|
|
|
* @see #initialize()
|
|
|
|
|
|
* @see #isInitialized()
|
|
|
|
|
|
*/
|
2025-10-08 11:08:57 +08:00
|
|
|
|
private static boolean initialized = false;
|
2025-10-17 01:48:07 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 视口宽度(像素),定义渲染区域的大小
|
|
|
|
|
|
* 默认值:800像素
|
|
|
|
|
|
* @see #setViewport(int, int)
|
|
|
|
|
|
*/
|
2025-10-08 11:08:57 +08:00
|
|
|
|
private static int viewportWidth = 800;
|
2025-10-17 01:48:07 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 视口高度(像素),定义渲染区域的大小
|
|
|
|
|
|
* 默认值:600像素
|
|
|
|
|
|
* @see #setViewport(int, int)
|
|
|
|
|
|
*/
|
2025-10-08 11:08:57 +08:00
|
|
|
|
private static int viewportHeight = 600;
|
2025-10-17 01:48:07 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 清除颜色(RGBA),用于在每帧开始时清空颜色缓冲区
|
|
|
|
|
|
* 默认值:黑色不透明 (0.0f, 0.0f, 0.0f, 1.0f)
|
|
|
|
|
|
* @see RenderSystem#clearColor(float, float, float, float)
|
|
|
|
|
|
*/
|
2025-10-08 11:08:57 +08:00
|
|
|
|
private static final Vector4f CLEAR_COLOR = new Vector4f(0.0f, 0.0f, 0.0f, 1.0f);
|
2025-10-17 01:48:07 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 深度测试启用标志,控制是否进行深度缓冲测试
|
|
|
|
|
|
* 在2D渲染中通常禁用以提高性能
|
|
|
|
|
|
* 默认值:false(禁用)
|
|
|
|
|
|
* @see RenderSystem#enableDepthTest()
|
|
|
|
|
|
* @see RenderSystem#disableDepthTest()
|
|
|
|
|
|
*/
|
2025-10-12 08:41:34 +08:00
|
|
|
|
private static final boolean enableDepthTest = false;
|
2025-10-17 01:48:07 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 混合功能启用标志,控制透明度和颜色混合
|
|
|
|
|
|
* 默认值:true(启用)
|
|
|
|
|
|
* @see RenderSystem#enableBlend()
|
|
|
|
|
|
* @see RenderSystem#disableBlend()
|
|
|
|
|
|
*/
|
2025-10-12 08:41:34 +08:00
|
|
|
|
private static final boolean enableBlending = true;
|
2025-10-08 11:08:57 +08:00
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
private static final int SHADER_MAX_LIGHTS = 8;
|
|
|
|
|
|
|
|
|
|
|
|
// ================== 着色器与资源管理 ==================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 着色器程序缓存映射,按名称存储已编译的着色器程序
|
|
|
|
|
|
* 键:着色器名称(如 "default")
|
|
|
|
|
|
* 值:对应的着色器程序对象
|
|
|
|
|
|
* @see ShaderSources.ShaderProgram
|
|
|
|
|
|
*/
|
|
|
|
|
|
private static final Map<String, ShaderSources.ShaderProgram> shaderMap = new HashMap<>();
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 默认着色器程序,用于大多数模型的渲染
|
|
|
|
|
|
* 包含基础的光照、纹理和变换功能
|
|
|
|
|
|
* @see #compileDefaultShader()
|
|
|
|
|
|
*/
|
|
|
|
|
|
private static ShaderSources.ShaderProgram defaultProgram = null;
|
2025-10-08 11:08:57 +08:00
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 网格GPU资源缓存,管理已上传到GPU的网格数据
|
|
|
|
|
|
* 键:Mesh2D对象
|
|
|
|
|
|
* 值:对应的OpenGL资源(VAO、VBO、EBO)
|
|
|
|
|
|
* @see MeshGLResources
|
|
|
|
|
|
*/
|
2025-10-08 11:08:57 +08:00
|
|
|
|
private static final Map<Mesh2D, MeshGLResources> meshResources = new HashMap<>();
|
2025-10-17 01:48:07 +08:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 纹理单元分配器,用于管理多个纹理的绑定
|
|
|
|
|
|
* 确保不同的纹理绑定到正确的纹理单元
|
|
|
|
|
|
* 默认从0开始递增分配
|
|
|
|
|
|
*/
|
2025-10-08 11:08:57 +08:00
|
|
|
|
private static final AtomicInteger textureUnitAllocator = new AtomicInteger(0);
|
|
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 默认白色纹理ID,当模型没有指定纹理时使用
|
|
|
|
|
|
* 这是一个1x1的纯白色纹理,确保模型有基本的颜色显示
|
|
|
|
|
|
* @see #createDefaultTexture()
|
|
|
|
|
|
*/
|
2025-10-08 11:08:57 +08:00
|
|
|
|
private static int defaultTextureId = 0;
|
|
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
// ================== 碰撞箱渲染配置 ==================
|
2025-10-11 20:21:11 +08:00
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 碰撞箱渲染开关,控制是否在场景中显示物理碰撞体的轮廓
|
|
|
|
|
|
* 调试时非常有用,可以直观看到碰撞边界
|
|
|
|
|
|
* 默认值:true(启用)
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static boolean renderColliders = true;
|
2025-10-08 11:08:57 +08:00
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 碰撞箱线框宽度,控制碰撞体轮廓线的粗细
|
|
|
|
|
|
* 单位:像素
|
|
|
|
|
|
* 默认值:1.0f
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static float colliderLineWidth = 1.0f;
|
2025-10-08 11:08:57 +08:00
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 碰撞箱颜色(RGBA),定义碰撞体轮廓的显示颜色
|
|
|
|
|
|
* 默认值:白色不透明 (1.0f, 1.0f, 1.0f, 1.0f)
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static Vector4f colliderColor = new Vector4f(1.0f, 1.0f, 1.0f, 1.0f);
|
2025-10-08 11:08:57 +08:00
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 圆形碰撞体细分数量,控制圆形碰撞体的平滑度
|
|
|
|
|
|
* 值越高圆形越平滑,但渲染开销也越大
|
|
|
|
|
|
* 默认值:32(在性能和视觉效果间取得平衡)
|
|
|
|
|
|
*/
|
|
|
|
|
|
private static final int CIRCLE_SEGMENTS = 32;
|
2025-10-08 11:08:57 +08:00
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 光源位置渲染开关,控制是否在场景中显示光源的位置
|
|
|
|
|
|
* 用点状标记显示每个启用的光源位置
|
|
|
|
|
|
* 默认值:true(启用)
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static boolean renderLightPositions = true;
|
|
|
|
|
|
|
|
|
|
|
|
// ================== 内部类:ShaderSources.ShaderProgram ==================
|
2025-10-08 11:08:57 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ================== 内部类: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;
|
|
|
|
|
|
|
2025-10-12 08:41:34 +08:00
|
|
|
|
logger.info("Initializing ModelRender...");
|
2025-10-08 11:08:57 +08:00
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
// 初始化渲染系统
|
|
|
|
|
|
RenderSystem.beginInitialization();
|
|
|
|
|
|
RenderSystem.initRenderThread();
|
2025-10-08 11:08:57 +08:00
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
logGLInfo();
|
2025-10-08 11:08:57 +08:00
|
|
|
|
setupGLState();
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
compileDefaultShader();
|
|
|
|
|
|
} catch (RuntimeException ex) {
|
2025-10-12 08:41:34 +08:00
|
|
|
|
logger.error("Failed to compile default shader: {}", ex.getMessage());
|
2025-10-08 11:08:57 +08:00
|
|
|
|
throw ex;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
createDefaultTexture();
|
2025-10-17 01:48:07 +08:00
|
|
|
|
RenderSystem.viewport(0, 0, viewportWidth, viewportHeight);
|
|
|
|
|
|
RenderSystem.finishInitialization();
|
2025-10-08 11:08:57 +08:00
|
|
|
|
|
|
|
|
|
|
initialized = true;
|
2025-10-12 08:41:34 +08:00
|
|
|
|
logger.info("ModelRender initialized successfully");
|
2025-10-08 11:08:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void logGLInfo() {
|
2025-10-17 01:48:07 +08:00
|
|
|
|
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();
|
2025-10-08 11:08:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
|
|
|
|
|
|
private static void uploadLightsToShader(ShaderSources.ShaderProgram sp, Model2D model) {
|
2025-10-12 08:01:25 +08:00
|
|
|
|
List<com.chuangzhou.vivid2D.render.model.util.LightSource> lights = model.getLights();
|
|
|
|
|
|
int idx = 0;
|
2025-10-11 20:21:11 +08:00
|
|
|
|
|
2025-10-12 08:01:25 +08:00
|
|
|
|
// 只上传已启用的光源,最多 MAX_LIGHTS(8)
|
|
|
|
|
|
for (int i = 0; i < lights.size() && idx < 8; i++) {
|
|
|
|
|
|
com.chuangzhou.vivid2D.render.model.util.LightSource l = lights.get(i);
|
2025-10-11 20:21:11 +08:00
|
|
|
|
if (!l.isEnabled()) continue;
|
|
|
|
|
|
|
2025-10-12 08:16:42 +08:00
|
|
|
|
// 基础属性
|
2025-10-12 08:01:25 +08:00
|
|
|
|
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);
|
2025-10-11 20:21:11 +08:00
|
|
|
|
|
2025-10-12 08:16:42 +08:00
|
|
|
|
// 辉光相关(如果没有被设置也安全地上传默认值)
|
|
|
|
|
|
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());
|
|
|
|
|
|
|
2025-10-12 08:01:25 +08:00
|
|
|
|
idx++;
|
2025-10-11 20:21:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-12 08:01:25 +08:00
|
|
|
|
// 上传实际有效光源数量
|
|
|
|
|
|
setUniformIntInternal(sp, "uLightCount", idx);
|
|
|
|
|
|
|
|
|
|
|
|
// 禁用剩余槽位(确保 shader 中不会读取到垃圾值)
|
|
|
|
|
|
for (int i = idx; i < 8; i++) {
|
2025-10-11 20:21:11 +08:00
|
|
|
|
setUniformFloatInternal(sp, "uLightsIntensity[" + i + "]", 0f);
|
|
|
|
|
|
setUniformIntInternal(sp, "uLightsIsAmbient[" + i + "]", 0);
|
2025-10-12 08:01:25 +08:00
|
|
|
|
setUniformVec3Internal(sp, "uLightsColor[" + i + "]", new org.joml.Vector3f(0f, 0f, 0f));
|
|
|
|
|
|
setUniformVec2Internal(sp, "uLightsPos[" + i + "]", new org.joml.Vector2f(0f, 0f));
|
2025-10-12 08:16:42 +08:00
|
|
|
|
|
|
|
|
|
|
// 关闭辉光槽
|
|
|
|
|
|
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);
|
2025-10-11 20:21:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
|
2025-10-08 11:08:57 +08:00
|
|
|
|
private static void setupGLState() {
|
2025-10-17 01:48:07 +08:00
|
|
|
|
RenderSystem.clearColor(CLEAR_COLOR.x, CLEAR_COLOR.y, CLEAR_COLOR.z, CLEAR_COLOR.w);
|
2025-10-08 11:08:57 +08:00
|
|
|
|
|
|
|
|
|
|
if (enableBlending) {
|
2025-10-17 01:48:07 +08:00
|
|
|
|
RenderSystem.enableBlend();
|
|
|
|
|
|
RenderSystem.blendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);
|
2025-10-08 11:08:57 +08:00
|
|
|
|
} else {
|
2025-10-17 01:48:07 +08:00
|
|
|
|
RenderSystem.disableBlend();
|
2025-10-08 11:08:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (enableDepthTest) {
|
2025-10-17 01:48:07 +08:00
|
|
|
|
RenderSystem.enableDepthTest();
|
|
|
|
|
|
RenderSystem.depthFunc(GL11.GL_LEQUAL);
|
|
|
|
|
|
RenderSystem.depthMask(true);
|
|
|
|
|
|
RenderSystem.clearDepth(1.0);
|
2025-10-08 11:08:57 +08:00
|
|
|
|
} else {
|
2025-10-17 01:48:07 +08:00
|
|
|
|
RenderSystem.disableDepthTest();
|
2025-10-08 11:08:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
RenderSystem.checkGLError("setupGLState");
|
2025-10-08 11:08:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void compileDefaultShader() {
|
2025-10-17 01:48:07 +08:00
|
|
|
|
int vs = compileShader(GL20.GL_VERTEX_SHADER, ShaderSources.VERTEX_SHADER_SRC);
|
|
|
|
|
|
int fs = compileShader(GL20.GL_FRAGMENT_SHADER, ShaderSources.FRAGMENT_SHADER_SRC);
|
2025-10-08 11:08:57 +08:00
|
|
|
|
int prog = linkProgram(vs, fs);
|
2025-10-17 01:48:07 +08:00
|
|
|
|
ShaderSources.ShaderProgram sp = new ShaderSources.ShaderProgram(prog);
|
2025-10-08 11:08:57 +08:00
|
|
|
|
shaderMap.put("default", sp);
|
|
|
|
|
|
defaultProgram = sp;
|
|
|
|
|
|
|
|
|
|
|
|
sp.use();
|
|
|
|
|
|
setUniformIntInternal(sp, "uTexture", 0);
|
|
|
|
|
|
setUniformFloatInternal(sp, "uOpacity", 1.0f);
|
|
|
|
|
|
setUniformVec4Internal(sp, "uColor", new Vector4f(1,1,1,1));
|
|
|
|
|
|
setUniformIntInternal(sp, "uBlendMode", 0);
|
2025-10-11 20:21:11 +08:00
|
|
|
|
setUniformIntInternal(sp, "uDebugMode", 0);
|
|
|
|
|
|
setUniformIntInternal(sp, "uLightCount", 0); // 默认没有光源
|
2025-10-08 11:08:57 +08:00
|
|
|
|
sp.stop();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static int compileShader(int type, String src) {
|
2025-10-17 01:48:07 +08:00
|
|
|
|
RenderSystem.assertOnRenderThread();
|
|
|
|
|
|
return RenderSystem.compileShader(type, src);
|
2025-10-08 11:08:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static int linkProgram(int vs, int fs) {
|
2025-10-17 01:48:07 +08:00
|
|
|
|
RenderSystem.assertOnRenderThread();
|
|
|
|
|
|
return RenderSystem.linkProgram(vs, fs);
|
2025-10-08 11:08:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private static void createDefaultTexture() {
|
2025-10-17 01:48:07 +08:00
|
|
|
|
RenderSystem.assertOnRenderThread();
|
|
|
|
|
|
defaultTextureId = RenderSystem.createDefaultTexture();
|
|
|
|
|
|
RenderSystem.checkGLError("createDefaultTexture");
|
2025-10-08 11:08:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static synchronized void cleanup() {
|
|
|
|
|
|
if (!initialized) return;
|
|
|
|
|
|
|
2025-10-12 08:41:34 +08:00
|
|
|
|
logger.info("Cleaning up ModelRender...");
|
2025-10-08 11:08:57 +08:00
|
|
|
|
|
|
|
|
|
|
// mesh resources
|
|
|
|
|
|
for (MeshGLResources r : meshResources.values()) r.dispose();
|
|
|
|
|
|
meshResources.clear();
|
|
|
|
|
|
|
|
|
|
|
|
// shaders
|
2025-10-17 01:48:07 +08:00
|
|
|
|
for (ShaderSources.ShaderProgram sp : shaderMap.values()) sp.delete();
|
2025-10-08 11:08:57 +08:00
|
|
|
|
shaderMap.clear();
|
|
|
|
|
|
defaultProgram = null;
|
|
|
|
|
|
|
|
|
|
|
|
// textures
|
|
|
|
|
|
if (defaultTextureId != 0) {
|
2025-10-17 01:48:07 +08:00
|
|
|
|
RenderSystem.deleteTextures(defaultTextureId);
|
2025-10-08 11:08:57 +08:00
|
|
|
|
defaultTextureId = 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
initialized = false;
|
2025-10-12 08:41:34 +08:00
|
|
|
|
logger.info("ModelRender cleaned up");
|
2025-10-08 11:08:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-11 20:21:11 +08:00
|
|
|
|
// ================== 渲染流程 (已修改) ==================
|
2025-10-08 11:08:57 +08:00
|
|
|
|
public static void render(float deltaTime, Model2D model) {
|
|
|
|
|
|
if (!initialized) throw new IllegalStateException("ModelRender not initialized");
|
|
|
|
|
|
if (model == null) return;
|
|
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
// 确保在渲染线程
|
|
|
|
|
|
RenderSystem.assertOnRenderThread();
|
|
|
|
|
|
|
|
|
|
|
|
// 添加前置错误检查
|
|
|
|
|
|
RenderSystem.checkGLError("render_start");
|
|
|
|
|
|
|
2025-10-11 20:21:11 +08:00
|
|
|
|
// 物理系统更新
|
|
|
|
|
|
PhysicsSystem physics = model.getPhysics();
|
|
|
|
|
|
if (physics != null && physics.isEnabled()) {
|
|
|
|
|
|
physics.update(deltaTime, model);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-08 11:08:57 +08:00
|
|
|
|
model.update(deltaTime);
|
|
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
// 检查清除操作前的状态
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
2025-10-08 11:08:57 +08:00
|
|
|
|
|
|
|
|
|
|
defaultProgram.use();
|
2025-10-17 01:48:07 +08:00
|
|
|
|
RenderSystem.checkGLError("after_use_program");
|
2025-10-08 11:08:57 +08:00
|
|
|
|
|
2025-10-11 20:21:11 +08:00
|
|
|
|
// 设置投影与视图
|
2025-10-08 11:08:57 +08:00
|
|
|
|
Matrix3f proj = buildOrthoProjection(viewportWidth, viewportHeight);
|
|
|
|
|
|
setUniformMatrix3(defaultProgram, "uProjectionMatrix", proj);
|
|
|
|
|
|
setUniformMatrix3(defaultProgram, "uViewMatrix", new Matrix3f().identity());
|
2025-10-17 01:48:07 +08:00
|
|
|
|
RenderSystem.checkGLError("after_set_matrices");
|
2025-10-08 11:08:57 +08:00
|
|
|
|
|
2025-10-11 20:21:11 +08:00
|
|
|
|
// 添加光源数据上传
|
|
|
|
|
|
uploadLightsToShader(defaultProgram, model);
|
2025-10-17 01:48:07 +08:00
|
|
|
|
RenderSystem.checkGLError("after_upload_lights");
|
|
|
|
|
|
|
|
|
|
|
|
// 在渲染光源位置前检查
|
|
|
|
|
|
RenderSystem.checkGLError("before_render_light_positions");
|
2025-10-11 20:21:11 +08:00
|
|
|
|
renderLightPositions(model);
|
2025-10-17 01:48:07 +08:00
|
|
|
|
RenderSystem.checkGLError("after_render_light_positions");
|
2025-10-11 20:21:11 +08:00
|
|
|
|
|
|
|
|
|
|
// 递归渲染所有根部件
|
2025-10-08 11:08:57 +08:00
|
|
|
|
Matrix3f identity = new Matrix3f().identity();
|
|
|
|
|
|
for (ModelPart p : model.getParts()) {
|
|
|
|
|
|
if (p.getParent() != null) continue;
|
|
|
|
|
|
renderPartRecursive(p, identity);
|
|
|
|
|
|
}
|
2025-10-17 01:48:07 +08:00
|
|
|
|
RenderSystem.checkGLError("after_render_parts");
|
2025-10-08 11:08:57 +08:00
|
|
|
|
|
2025-10-11 20:21:11 +08:00
|
|
|
|
if (renderColliders && physics != null) {
|
|
|
|
|
|
renderPhysicsColliders(physics);
|
2025-10-17 01:48:07 +08:00
|
|
|
|
RenderSystem.checkGLError("after_render_colliders");
|
2025-10-11 20:21:11 +08:00
|
|
|
|
}
|
2025-10-08 11:08:57 +08:00
|
|
|
|
|
2025-10-11 20:21:11 +08:00
|
|
|
|
defaultProgram.stop();
|
2025-10-17 01:48:07 +08:00
|
|
|
|
RenderSystem.checkGLError("render_end");
|
2025-10-08 11:08:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-11 20:21:11 +08:00
|
|
|
|
private static void renderLightPositions(Model2D model) {
|
|
|
|
|
|
if (!renderLightPositions) return;
|
2025-10-17 01:48:07 +08:00
|
|
|
|
// 设置灯泡颜色为光源的颜色
|
2025-10-11 20:21:11 +08:00
|
|
|
|
for (LightSource light : model.getLights()) {
|
|
|
|
|
|
if (!light.isEnabled()) continue;
|
|
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
// 使用光源的颜色来绘制灯泡
|
|
|
|
|
|
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());
|
|
|
|
|
|
}
|
2025-10-11 20:21:11 +08:00
|
|
|
|
}
|
2025-10-17 01:48:07 +08:00
|
|
|
|
// 恢复原始颜色
|
|
|
|
|
|
setUniformVec4Internal(defaultProgram, "uColor", new Vector4f(1,1,1,1));
|
|
|
|
|
|
}
|
2025-10-11 20:21:11 +08:00
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 绘制简洁的灯泡形状
|
|
|
|
|
|
* @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();
|
2025-10-11 20:21:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-08 16:49:26 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 关键修改点:在渲染前确保更新 part 的 worldTransform,
|
|
|
|
|
|
* 然后直接使用 part.getWorldTransform() 作为 uModelMatrix 传入 shader。
|
|
|
|
|
|
*/
|
2025-10-08 11:08:57 +08:00
|
|
|
|
private static void renderPartRecursive(ModelPart part, Matrix3f parentMat) {
|
2025-10-08 16:49:26 +08:00
|
|
|
|
// 确保 part 的 local/world 矩阵被计算(会更新 transformDirty)
|
|
|
|
|
|
part.updateWorldTransform(parentMat, false);
|
|
|
|
|
|
|
|
|
|
|
|
// 直接使用已经计算好的 worldTransform
|
|
|
|
|
|
Matrix3f world = part.getWorldTransform();
|
|
|
|
|
|
|
|
|
|
|
|
// 先设置部件相关的 uniform(opacity / blendMode / color 等)
|
2025-10-08 11:08:57 +08:00
|
|
|
|
setPartUniforms(defaultProgram, part);
|
|
|
|
|
|
|
2025-10-11 20:21:11 +08:00
|
|
|
|
// 把 world 矩阵传给 shader(uModelMatrix)
|
2025-10-08 16:49:26 +08:00
|
|
|
|
setUniformMatrix3(defaultProgram, "uModelMatrix", world);
|
|
|
|
|
|
|
|
|
|
|
|
// 绘制本节点的所有 mesh(将 world 传入 renderMesh)
|
2025-10-08 11:08:57 +08:00
|
|
|
|
for (Mesh2D mesh : part.getMeshes()) {
|
2025-10-08 16:49:26 +08:00
|
|
|
|
renderMesh(mesh, world);
|
2025-10-08 11:08:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-08 16:49:26 +08:00
|
|
|
|
// 递归渲染子节点,继续传入当前 world 作为子节点的 parent
|
2025-10-08 11:08:57 +08:00
|
|
|
|
for (ModelPart child : part.getChildren()) {
|
|
|
|
|
|
renderPartRecursive(child, world);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-08 16:49:26 +08:00
|
|
|
|
private static void renderMesh(Mesh2D mesh, Matrix3f modelMatrix) {
|
2025-10-08 18:45:17 +08:00
|
|
|
|
if (!mesh.isVisible()) return;
|
|
|
|
|
|
|
2025-10-08 16:49:26 +08:00
|
|
|
|
// 如果 mesh 已经被烘焙到世界坐标,则传 identity 矩阵给 shader(防止重复变换)
|
|
|
|
|
|
Matrix3f matToUse = mesh.isBakedToWorld() ? new Matrix3f().identity() : modelMatrix;
|
|
|
|
|
|
|
2025-10-08 18:45:17 +08:00
|
|
|
|
// 设置纹理相关的uniform
|
|
|
|
|
|
if (mesh.getTexture() != null) {
|
|
|
|
|
|
mesh.getTexture().bind(0); // 绑定到纹理单元0
|
|
|
|
|
|
setUniformIntInternal(defaultProgram, "uTexture", 0);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 使用默认白色纹理
|
2025-10-17 01:48:07 +08:00
|
|
|
|
RenderSystem.bindTexture(defaultTextureId);
|
2025-10-08 18:45:17 +08:00
|
|
|
|
setUniformIntInternal(defaultProgram, "uTexture", 0);
|
2025-10-08 16:49:26 +08:00
|
|
|
|
}
|
2025-10-08 12:30:37 +08:00
|
|
|
|
|
2025-10-11 20:21:11 +08:00
|
|
|
|
// 将模型矩阵设置为当前 mesh 使用的矩阵(shader 内名为 uModelMatrix)
|
|
|
|
|
|
setUniformMatrix3(defaultProgram, "uModelMatrix", matToUse);
|
|
|
|
|
|
|
2025-10-08 18:45:17 +08:00
|
|
|
|
// 调用 Mesh2D 的 draw 方法,传入当前使用的着色器程序和变换矩阵
|
|
|
|
|
|
mesh.draw(defaultProgram.programId, matToUse);
|
|
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
RenderSystem.checkGLError("renderMesh");
|
2025-10-08 11:08:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-11 20:21:11 +08:00
|
|
|
|
// ================== 渲染碰撞箱相关实现 ==================
|
|
|
|
|
|
|
|
|
|
|
|
private static void renderPhysicsColliders(PhysicsSystem physics) {
|
2025-10-17 01:48:07 +08:00
|
|
|
|
if (physics == null) {
|
|
|
|
|
|
logger.warn("renderPhysicsColliders: physics system is null");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-11 20:21:11 +08:00
|
|
|
|
// 设置渲染状态
|
2025-10-17 01:48:07 +08:00
|
|
|
|
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");
|
2025-10-11 20:21:11 +08:00
|
|
|
|
|
|
|
|
|
|
setUniformIntInternal(defaultProgram, "uTexture", 0);
|
|
|
|
|
|
setUniformVec4Internal(defaultProgram, "uColor", colliderColor);
|
|
|
|
|
|
setUniformFloatInternal(defaultProgram, "uOpacity", 1.0f);
|
|
|
|
|
|
setUniformIntInternal(defaultProgram, "uBlendMode", 0);
|
|
|
|
|
|
setUniformIntInternal(defaultProgram, "uDebugMode", 0);
|
2025-10-17 01:48:07 +08:00
|
|
|
|
RenderSystem.checkGLError("after_set_uniforms");
|
2025-10-11 20:21:11 +08:00
|
|
|
|
|
|
|
|
|
|
// 使用单位矩阵作为 model(碰撞体顶点按世界坐标提供)
|
|
|
|
|
|
setUniformMatrix3(defaultProgram, "uModelMatrix", new Matrix3f().identity());
|
2025-10-17 01:48:07 +08:00
|
|
|
|
RenderSystem.checkGLError("after_set_model_matrix");
|
|
|
|
|
|
|
|
|
|
|
|
List<PhysicsSystem.PhysicsCollider> colliders = physics.getColliders();
|
|
|
|
|
|
if (colliders == null || colliders.isEmpty()) {
|
|
|
|
|
|
logger.debug("No colliders to render");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2025-10-11 20:21:11 +08:00
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
int enabledColliders = 0;
|
|
|
|
|
|
for (PhysicsSystem.PhysicsCollider collider : colliders) {
|
|
|
|
|
|
if (collider == null || !collider.isEnabled()) continue;
|
|
|
|
|
|
|
|
|
|
|
|
RenderSystem.checkGLError("before_render_collider_" + enabledColliders);
|
2025-10-11 20:21:11 +08:00
|
|
|
|
|
|
|
|
|
|
if (collider instanceof PhysicsSystem.CircleCollider) {
|
|
|
|
|
|
PhysicsSystem.CircleCollider c = (PhysicsSystem.CircleCollider) collider;
|
2025-10-17 01:48:07 +08:00
|
|
|
|
if (c.getCenter() != null && c.getRadius() > 0) {
|
|
|
|
|
|
drawCircleColliderWire(c.getCenter(), c.getRadius());
|
|
|
|
|
|
enabledColliders++;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
logger.warn("Invalid CircleCollider: center={}, radius={}", c.getCenter(), c.getRadius());
|
|
|
|
|
|
}
|
2025-10-11 20:21:11 +08:00
|
|
|
|
} else if (collider instanceof PhysicsSystem.RectangleCollider) {
|
|
|
|
|
|
PhysicsSystem.RectangleCollider r = (PhysicsSystem.RectangleCollider) collider;
|
2025-10-17 01:48:07 +08:00
|
|
|
|
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());
|
|
|
|
|
|
}
|
2025-10-11 20:21:11 +08:00
|
|
|
|
} else {
|
2025-10-17 01:48:07 +08:00
|
|
|
|
logger.warn("Unknown collider type: {}", collider.getClass().getSimpleName());
|
2025-10-11 20:21:11 +08:00
|
|
|
|
}
|
2025-10-17 01:48:07 +08:00
|
|
|
|
|
|
|
|
|
|
RenderSystem.checkGLError("after_render_collider_" + enabledColliders);
|
2025-10-11 20:21:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
logger.debug("Rendered {} enabled colliders", enabledColliders);
|
|
|
|
|
|
|
2025-10-11 20:21:11 +08:00
|
|
|
|
// 恢复默认线宽
|
2025-10-17 01:48:07 +08:00
|
|
|
|
RenderSystem.lineWidth(1.0f);
|
|
|
|
|
|
RenderSystem.checkGLError("after_reset_line_width");
|
2025-10-11 20:21:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-08 16:49:26 +08:00
|
|
|
|
|
2025-10-11 20:21:11 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 绘制圆形碰撞框(线框)
|
|
|
|
|
|
* 使用临时 VAO/VBO,每帧创建并删除(简单实现)
|
|
|
|
|
|
*/
|
|
|
|
|
|
private static void drawCircleColliderWire(Vector2f center, float radius) {
|
|
|
|
|
|
int segments = Math.max(8, CIRCLE_SEGMENTS);
|
2025-10-17 01:48:07 +08:00
|
|
|
|
|
|
|
|
|
|
Tesselator tesselator = Tesselator.getInstance();
|
|
|
|
|
|
BufferBuilder builder = tesselator.getBuilder();
|
|
|
|
|
|
|
|
|
|
|
|
builder.begin(RenderSystem.DRAW_LINE_LOOP, segments);
|
2025-10-11 20:21:11 +08:00
|
|
|
|
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);
|
2025-10-17 01:48:07 +08:00
|
|
|
|
builder.vertex(x, y, 0.5f, 0.5f);
|
2025-10-08 11:08:57 +08:00
|
|
|
|
}
|
2025-10-17 01:48:07 +08:00
|
|
|
|
tesselator.end();
|
2025-10-11 20:21:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 绘制矩形碰撞框(线框)
|
|
|
|
|
|
*/
|
|
|
|
|
|
private static void drawRectangleColliderWire(Vector2f center, float width, float height) {
|
|
|
|
|
|
float halfW = width / 2.0f;
|
|
|
|
|
|
float halfH = height / 2.0f;
|
|
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
Tesselator tesselator = Tesselator.getInstance();
|
|
|
|
|
|
BufferBuilder builder = tesselator.getBuilder();
|
2025-10-11 20:21:11 +08:00
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
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();
|
2025-10-11 20:21:11 +08:00
|
|
|
|
}
|
2025-10-08 11:08:57 +08:00
|
|
|
|
|
|
|
|
|
|
// ================== uniform 设置辅助(内部使用,确保 program 已绑定) ==================
|
2025-10-17 01:48:07 +08:00
|
|
|
|
private static void setUniformIntInternal(ShaderSources.ShaderProgram sp, String name, int value) {
|
2025-10-08 11:08:57 +08:00
|
|
|
|
int loc = sp.getUniformLocation(name);
|
2025-10-17 01:48:07 +08:00
|
|
|
|
if (loc != -1) RenderSystem.uniform1i(loc, value);
|
2025-10-08 11:08:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
private static void setUniformVec3Internal(ShaderSources.ShaderProgram sp, String name, org.joml.Vector3f vec) {
|
2025-10-11 20:21:11 +08:00
|
|
|
|
int loc = sp.getUniformLocation(name);
|
2025-10-17 01:48:07 +08:00
|
|
|
|
if (loc != -1) RenderSystem.uniform3f(loc, vec);
|
2025-10-11 20:21:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
private static void setUniformVec2Internal(ShaderSources.ShaderProgram sp, String name, org.joml.Vector2f vec) {
|
2025-10-11 20:21:11 +08:00
|
|
|
|
int loc = sp.getUniformLocation(name);
|
2025-10-17 01:48:07 +08:00
|
|
|
|
if (loc != -1) RenderSystem.uniform2f(loc, vec);
|
2025-10-11 20:21:11 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
private static void setUniformFloatInternal(ShaderSources.ShaderProgram sp, String name, float value) {
|
2025-10-08 11:08:57 +08:00
|
|
|
|
int loc = sp.getUniformLocation(name);
|
2025-10-17 01:48:07 +08:00
|
|
|
|
if (loc != -1) RenderSystem.uniform1f(loc, value);
|
2025-10-08 11:08:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
private static void setUniformVec4Internal(ShaderSources.ShaderProgram sp, String name, org.joml.Vector4f vec) {
|
2025-10-08 11:08:57 +08:00
|
|
|
|
int loc = sp.getUniformLocation(name);
|
2025-10-17 01:48:07 +08:00
|
|
|
|
if (loc != -1) RenderSystem.uniform4f(loc, vec);
|
2025-10-08 11:08:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-17 01:48:07 +08:00
|
|
|
|
private static void setUniformMatrix3(ShaderSources.ShaderProgram sp, String name, org.joml.Matrix3f m) {
|
2025-10-08 11:08:57 +08:00
|
|
|
|
int loc = sp.getUniformLocation(name);
|
|
|
|
|
|
if (loc == -1) return;
|
2025-10-17 01:48:07 +08:00
|
|
|
|
RenderSystem.uniformMatrix3(loc, m);
|
2025-10-08 11:08:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ================== 部件属性 ==================
|
2025-10-17 01:48:07 +08:00
|
|
|
|
private static void setPartUniforms(ShaderSources.ShaderProgram sp, ModelPart part) {
|
2025-10-08 11:08:57 +08:00
|
|
|
|
setUniformFloatInternal(sp, "uOpacity", part.getOpacity());
|
|
|
|
|
|
int blend = 0;
|
2025-10-08 16:49:26 +08:00
|
|
|
|
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;
|
2025-10-08 11:08:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public static void setViewport(int width, int height) {
|
|
|
|
|
|
viewportWidth = Math.max(1, width);
|
|
|
|
|
|
viewportHeight = Math.max(1, height);
|
2025-10-17 01:48:07 +08:00
|
|
|
|
RenderSystem.viewport(0, 0, viewportWidth, viewportHeight);
|
2025-10-08 11:08:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ================== 辅助:外部获取状态 ==================
|
|
|
|
|
|
public static boolean isInitialized() { return initialized; }
|
|
|
|
|
|
public static int getLoadedMeshCount() { return meshResources.size(); }
|
|
|
|
|
|
|
|
|
|
|
|
}
|