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.util.Mesh2D; import com.chuangzhou.vivid2D.render.model.util.PhysicsSystem; // 引入 PhysicsSystem import com.chuangzhou.vivid2D.render.model.util.Texture; import org.joml.Matrix3f; import org.joml.Vector2f; import org.joml.Vector3f; import org.joml.Vector4f; import org.lwjgl.opengl.*; import org.lwjgl.system.MemoryUtil; import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import static org.lwjgl.opengl.GL20.glGetUniformLocation; /** * 重构后的 ModelRender:更模块化、健壮的渲染子系统 * (已修改以应用物理系统,并支持渲染碰撞箱) * @author tzdwindows 7 */ public final class ModelRender { private ModelRender() { /* no instances */ } // ================== 全局状态 ================== private static boolean initialized = false; private static int viewportWidth = 800; private static int viewportHeight = 600; private static final Vector4f CLEAR_COLOR = new Vector4f(0.0f, 0.0f, 0.0f, 1.0f); private static boolean enableDepthTest = false; private static boolean enableBlending = true; // 着色器与资源 private static final Map shaderMap = new HashMap<>(); private static ShaderProgram defaultProgram = null; private static final Map meshResources = new HashMap<>(); private static final AtomicInteger textureUnitAllocator = new AtomicInteger(0); // 默认白色纹理 private static int defaultTextureId = 0; // ================== 碰撞箱渲染配置 ================== // 是否在渲染时绘制碰撞箱(线框) public static boolean renderColliders = false; // 碰撞箱线宽 public static float colliderLineWidth = 2.0f; // 碰撞箱颜色(默认白色) public static Vector4f colliderColor = new Vector4f(1.0f, 1.0f, 1.0f, 1.0f); // 圆形碰撞体绘制细分(越高越圆) private static final int CIRCLE_SEGMENTS = 32; // 是否在渲染时绘制碰撞箱 public static boolean renderLightPositions = true; // ================== 内部类:ShaderProgram ================== private static class ShaderProgram { final int programId; final Map uniformCache = new HashMap<>(); ShaderProgram(int programId) { this.programId = programId; } void use() { GL20.glUseProgram(programId); } void stop() { GL20.glUseProgram(0); } int getUniformLocation(String name) { return uniformCache.computeIfAbsent(name, k -> { int loc = glGetUniformLocation(programId, k); if (loc == -1) { // debug 时可以打开 // System.err.println("Warning: uniform not found: " + k); } return loc; }); } void delete() { if (GL20.glIsProgram(programId)) GL20.glDeleteProgram(programId); } } // ================== 内部类:MeshGLResources ================== private static class MeshGLResources { int vao = 0; int vbo = 0; int ebo = 0; int vertexCount = 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; } } // ================== 着色器源 ================== private static final String VERTEX_SHADER_SRC = """ #version 330 core layout(location = 0) in vec2 aPosition; layout(location = 1) in vec2 aTexCoord; out vec2 vTexCoord; out vec2 vWorldPos; uniform mat3 uModelMatrix; uniform mat3 uViewMatrix; uniform mat3 uProjectionMatrix; void main() { vec3 p = uProjectionMatrix * uViewMatrix * uModelMatrix * vec3(aPosition, 1.0); gl_Position = vec4(p.xy, 0.0, 1.0); vTexCoord = aTexCoord; vWorldPos = (uModelMatrix * vec3(aPosition, 1.0)).xy; } """; private static final String FRAGMENT_SHADER_SRC = """ #version 330 core in vec2 vTexCoord; in vec2 vWorldPos; out vec4 FragColor; uniform sampler2D uTexture; uniform vec4 uColor; uniform float uOpacity; uniform int uBlendMode; uniform int uDebugMode; #define MAX_LIGHTS 8 uniform vec2 uLightsPos[MAX_LIGHTS]; uniform vec3 uLightsColor[MAX_LIGHTS]; uniform float uLightsIntensity[MAX_LIGHTS]; uniform int uLightsIsAmbient[MAX_LIGHTS]; uniform int uLightCount; // 常用衰减系数(可在 shader 内微调) const float ATT_CONST = 1.0; const float ATT_LINEAR = 0.09; const float ATT_QUAD = 0.032; void main() { // 先采样纹理 vec4 tex = texture(uTexture, vTexCoord); float alpha = tex.a * uOpacity; if (alpha <= 0.001) discard; // 如果没有光源,跳过光照计算(性能更好并且保持原始贴图色) if (uLightCount == 0) { vec3 base = tex.rgb * uColor.rgb; // 简单的色调映射(防止数值过大) base = clamp(base, 0.0, 1.0); FragColor = vec4(base, alpha); return; } // 基础颜色(纹理 * 部件颜色) vec3 baseColor = tex.rgb * uColor.rgb; // 全局环境光基线(可以适度提高以避免全黑) vec3 ambient = vec3(0.06); // 小环境光补偿 vec3 lighting = vec3(0.0); vec3 specularAccum = vec3(0.0); // 累积环境光(来自被标记为环境光的光源) for (int i = 0; i < uLightCount; ++i) { if (uLightsIsAmbient[i] == 1) { lighting += uLightsColor[i] * uLightsIntensity[i]; } } // 加上基线环境光 lighting += ambient; // 对每个非环境光计算基于距离的衰减与简单高光 for (int i = 0; i < uLightCount; ++i) { if (uLightsIsAmbient[i] == 1) continue; vec2 toLight = uLightsPos[i] - vWorldPos; float dist = length(toLight); // 标准物理式衰减 float attenuation = ATT_CONST / (ATT_CONST + ATT_LINEAR * dist + ATT_QUAD * dist * dist); // 强度受光源强度和衰减影响 float radiance = uLightsIntensity[i] * attenuation; // 漫反射:在纯2D情景下,法线与视线近似固定(Z向), // 所以漫反射对所有片元是恒定的。我们用一个基于距离的柔和因子来模拟明暗变化。 float diffuseFactor = clamp(1.0 - (dist * 0.0015), 0.0, 1.0); // 通过调节常数控制半径感觉 vec3 diff = uLightsColor[i] * radiance * diffuseFactor; lighting += diff; // 简单高光(基于视向与反射的大致模拟,产生亮点) vec3 lightDir3 = normalize(vec3(toLight, 0.0)); vec3 viewDir = vec3(0.0, 0.0, 1.0); vec3 normal = vec3(0.0, 0.0, 1.0); vec3 reflectDir = reflect(-lightDir3, normal); float specFactor = pow(max(dot(viewDir, reflectDir), 0.0), 16.0); // 16 为高光粗糙度,可调 float specIntensity = 0.2; // 高光强度系数 specularAccum += uLightsColor[i] * radiance * specFactor * specIntensity; } // 限制光照的最大值以避免过曝(可根据场景调整) vec3 totalLighting = min(lighting + specularAccum, vec3(2.0)); // 将光照应用到基础颜色 vec3 finalColor = baseColor * totalLighting; // 支持简单混合模式(保留原有行为) if (uBlendMode == 1) finalColor = tex.rgb + uColor.rgb; else if (uBlendMode == 2) finalColor = tex.rgb * uColor.rgb; else if (uBlendMode == 3) finalColor = 1.0 - (1.0 - tex.rgb) * (1.0 - uColor.rgb); finalColor = clamp(finalColor, 0.0, 1.0); FragColor = vec4(finalColor, alpha); } """; // ================== 初始化 / 清理 ================== public static synchronized void initialize() { if (initialized) return; System.out.println("Initializing ModelRender..."); // 需要在外部创建 OpenGL 上下文并调用 GL.createCapabilities() logGLInfo(); // 初始 GL 状态 setupGLState(); // 创建默认 shader try { compileDefaultShader(); } catch (RuntimeException ex) { System.err.println("Failed to compile default shader: " + ex.getMessage()); throw ex; } // 创建默认纹理 createDefaultTexture(); // 初始化视口 GL11.glViewport(0, 0, viewportWidth, viewportHeight); initialized = true; System.out.println("ModelRender initialized successfully"); } private static void logGLInfo() { System.out.println("OpenGL Vendor: " + GL11.glGetString(GL11.GL_VENDOR)); System.out.println("OpenGL Renderer: " + GL11.glGetString(GL11.GL_RENDERER)); System.out.println("OpenGL Version: " + GL11.glGetString(GL11.GL_VERSION)); System.out.println("GLSL Version: " + GL20.glGetString(GL20.GL_SHADING_LANGUAGE_VERSION)); } private static void uploadLightsToShader(ShaderProgram sp, Model2D model) { List lights = model.getLights(); int idx = 0; // 只上传已启用的光源,最多 MAX_LIGHTS(8) for (int i = 0; i < lights.size() && idx < 8; i++) { com.chuangzhou.vivid2D.render.model.util.LightSource l = lights.get(i); if (!l.isEnabled()) continue; // 环境光的 position 在 shader 中不会用于距离计算,但我们也上传(安全) 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); idx++; } // 上传实际有效光源数量 setUniformIntInternal(sp, "uLightCount", idx); // 禁用剩余槽位(确保 shader 中不会读取到垃圾值) for (int i = idx; i < 8; i++) { setUniformFloatInternal(sp, "uLightsIntensity[" + i + "]", 0f); setUniformIntInternal(sp, "uLightsIsAmbient[" + i + "]", 0); // color/pos 不严格必要,但清零更稳健 setUniformVec3Internal(sp, "uLightsColor[" + i + "]", new org.joml.Vector3f(0f, 0f, 0f)); setUniformVec2Internal(sp, "uLightsPos[" + i + "]", new org.joml.Vector2f(0f, 0f)); } } private static void setupGLState() { GL11.glClearColor(CLEAR_COLOR.x, CLEAR_COLOR.y, CLEAR_COLOR.z, CLEAR_COLOR.w); if (enableBlending) { GL11.glEnable(GL11.GL_BLEND); GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); } else { GL11.glDisable(GL11.GL_BLEND); } if (enableDepthTest) { GL11.glEnable(GL11.GL_DEPTH_TEST); GL11.glDepthFunc(GL11.GL_LEQUAL); } else { GL11.glDisable(GL11.GL_DEPTH_TEST); } GL11.glDisable(GL11.GL_CULL_FACE); checkGLError("setupGLState"); } private static void compileDefaultShader() { int vs = compileShader(GL20.GL_VERTEX_SHADER, VERTEX_SHADER_SRC); int fs = compileShader(GL20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_SRC); int prog = linkProgram(vs, fs); ShaderProgram sp = new ShaderProgram(prog); 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); setUniformIntInternal(sp, "uDebugMode", 0); setUniformIntInternal(sp, "uLightCount", 0); // 默认没有光源 sp.stop(); } private static int compileShader(int type, String src) { int shader = GL20.glCreateShader(type); GL20.glShaderSource(shader, src); GL20.glCompileShader(shader); int status = GL20.glGetShaderi(shader, GL20.GL_COMPILE_STATUS); if (status == GL11.GL_FALSE) { String log = GL20.glGetShaderInfoLog(shader); GL20.glDeleteShader(shader); throw new RuntimeException("Shader compilation failed: " + log); } return shader; } private static int linkProgram(int vs, int fs) { int prog = GL20.glCreateProgram(); GL20.glAttachShader(prog, vs); GL20.glAttachShader(prog, fs); GL20.glLinkProgram(prog); int status = GL20.glGetProgrami(prog, GL20.GL_LINK_STATUS); if (status == GL11.GL_FALSE) { String log = GL20.glGetProgramInfoLog(prog); GL20.glDeleteProgram(prog); throw new RuntimeException("Program link failed: " + log); } // shaders can be deleted after linking GL20.glDetachShader(prog, vs); GL20.glDetachShader(prog, fs); GL20.glDeleteShader(vs); GL20.glDeleteShader(fs); return prog; } private static void createDefaultTexture() { // 使用 GL11.glGenTextures() 获取单个 id(更直观,避免 IntBuffer 问题) defaultTextureId = GL11.glGenTextures(); GL11.glBindTexture(GL11.GL_TEXTURE_2D, defaultTextureId); ByteBuffer white = MemoryUtil.memAlloc(4); white.put((byte)255).put((byte)255).put((byte)255).put((byte)255).flip(); GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, 1, 1, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, white); MemoryUtil.memFree(white); GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST); GL11.glTexParameteri(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_NEAREST); GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0); checkGLError("createDefaultTexture"); } public static synchronized void cleanup() { if (!initialized) return; System.out.println("Cleaning up ModelRender..."); // mesh resources for (MeshGLResources r : meshResources.values()) r.dispose(); meshResources.clear(); // shaders for (ShaderProgram sp : shaderMap.values()) sp.delete(); shaderMap.clear(); defaultProgram = null; // textures if (defaultTextureId != 0) { GL11.glDeleteTextures(defaultTextureId); defaultTextureId = 0; } initialized = false; System.out.println("ModelRender cleaned up"); } // ================== 渲染流程 (已修改) ================== public static void render(float deltaTime, Model2D model) { if (!initialized) throw new IllegalStateException("ModelRender not initialized"); if (model == null) return; // 物理系统更新 PhysicsSystem physics = model.getPhysics(); if (physics != null && physics.isEnabled()) { physics.update(deltaTime, model); } model.update(deltaTime); GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | (enableDepthTest ? GL11.GL_DEPTH_BUFFER_BIT : 0)); defaultProgram.use(); // 设置投影与视图 Matrix3f proj = buildOrthoProjection(viewportWidth, viewportHeight); setUniformMatrix3(defaultProgram, "uProjectionMatrix", proj); setUniformMatrix3(defaultProgram, "uViewMatrix", new Matrix3f().identity()); // 添加光源数据上传 uploadLightsToShader(defaultProgram, model); renderLightPositions(model); // 递归渲染所有根部件 Matrix3f identity = new Matrix3f().identity(); for (ModelPart p : model.getParts()) { if (p.getParent() != null) continue; renderPartRecursive(p, identity); } if (renderColliders && physics != null) { renderPhysicsColliders(physics); } defaultProgram.stop(); checkGLError("render"); } private static void renderLightPositions(Model2D model) { if (!renderLightPositions) return; GL11.glPointSize(10.0f); setUniformIntInternal(defaultProgram, "uDebugMode", 1); for (LightSource light : model.getLights()) { if (!light.isEnabled()) continue; // 绘制光源位置 com.chuangzhou.vivid2D.render.util.BufferBuilder bb = new com.chuangzhou.vivid2D.render.util.BufferBuilder(1 * 4); bb.begin(GL11.GL_POINTS, 1); bb.vertex(light.getPosition().x, light.getPosition().y, 0.5f, 0.5f); bb.end(); } setUniformIntInternal(defaultProgram, "uDebugMode", 0); GL11.glPointSize(1.0f); } /** * 关键修改点:在渲染前确保更新 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() : modelMatrix; // 设置纹理相关的uniform if (mesh.getTexture() != null) { mesh.getTexture().bind(0); // 绑定到纹理单元0 setUniformIntInternal(defaultProgram, "uTexture", 0); } else { // 使用默认白色纹理 GL11.glBindTexture(GL11.GL_TEXTURE_2D, defaultTextureId); setUniformIntInternal(defaultProgram, "uTexture", 0); } // 将模型矩阵设置为当前 mesh 使用的矩阵(shader 内名为 uModelMatrix) setUniformMatrix3(defaultProgram, "uModelMatrix", matToUse); // 调用 Mesh2D 的 draw 方法,传入当前使用的着色器程序和变换矩阵 mesh.draw(defaultProgram.programId, matToUse); checkGLError("renderMesh"); } // ================== 渲染碰撞箱相关实现 ================== private static void renderPhysicsColliders(PhysicsSystem physics) { // 设置渲染状态 GL11.glLineWidth(colliderLineWidth); // 绑定默认纹理(shader 依赖 uTexture)并设置颜色/opacity GL13.glActiveTexture(GL13.GL_TEXTURE0); GL11.glBindTexture(GL11.GL_TEXTURE_2D, defaultTextureId); setUniformIntInternal(defaultProgram, "uTexture", 0); setUniformVec4Internal(defaultProgram, "uColor", colliderColor); setUniformFloatInternal(defaultProgram, "uOpacity", 1.0f); setUniformIntInternal(defaultProgram, "uBlendMode", 0); setUniformIntInternal(defaultProgram, "uDebugMode", 0); // 使用单位矩阵作为 model(碰撞体顶点按世界坐标提供) setUniformMatrix3(defaultProgram, "uModelMatrix", new Matrix3f().identity()); for (PhysicsSystem.PhysicsCollider collider : physics.getColliders()) { if (!collider.isEnabled()) continue; if (collider instanceof PhysicsSystem.CircleCollider) { PhysicsSystem.CircleCollider c = (PhysicsSystem.CircleCollider) collider; drawCircleColliderWire(c.getCenter(), c.getRadius()); } else if (collider instanceof PhysicsSystem.RectangleCollider) { PhysicsSystem.RectangleCollider r = (PhysicsSystem.RectangleCollider) collider; drawRectangleColliderWire(r.getCenter(), r.getWidth(), r.getHeight()); } else { // 未知类型:尝试调用 collidesWith 以获取位置(跳过) } } // 恢复默认线宽 GL11.glLineWidth(1.0f); } /** * 绘制圆形碰撞框(线框) * 使用临时 VAO/VBO,每帧创建并删除(简单实现) */ private static void drawCircleColliderWire(Vector2f center, float radius) { int segments = Math.max(8, CIRCLE_SEGMENTS); com.chuangzhou.vivid2D.render.util.BufferBuilder bb = new com.chuangzhou.vivid2D.render.util.BufferBuilder(segments * 4); bb.begin(GL11.GL_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); // 给常量 texcoord bb.vertex(x, y, 0.5f, 0.5f); } bb.end(); } /** * 绘制矩形碰撞框(线框) */ private static void drawRectangleColliderWire(Vector2f center, float width, float height) { float halfW = width / 2.0f; float halfH = height / 2.0f; com.chuangzhou.vivid2D.render.util.BufferBuilder bb = new com.chuangzhou.vivid2D.render.util.BufferBuilder(4 * 4); bb.begin(GL11.GL_LINE_LOOP, 4); bb.vertex(center.x - halfW, center.y - halfH, 0.5f, 0.5f); bb.vertex(center.x + halfW, center.y - halfH, 0.5f, 0.5f); bb.vertex(center.x + halfW, center.y + halfH, 0.5f, 0.5f); bb.vertex(center.x - halfW, center.y + halfH, 0.5f, 0.5f); bb.end(); } /** * 从 float[] (x,y,u,v interleaved) 绘制 GL_LINE_LOOP */ private static void drawLineLoopFromFloatArray(float[] interleavedXYUV, int vertexCount) { // 创建 VAO/VBO int vao = GL30.glGenVertexArrays(); int vbo = GL15.glGenBuffers(); GL30.glBindVertexArray(vao); GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo); // 上传数据 FloatBuffer fb = MemoryUtil.memAllocFloat(interleavedXYUV.length); fb.put(interleavedXYUV).flip(); GL15.glBufferData(GL15.GL_ARRAY_BUFFER, fb, GL15.GL_DYNAMIC_DRAW); MemoryUtil.memFree(fb); // attrib 0 -> aPosition (vec2) GL20.glEnableVertexAttribArray(0); GL20.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, 4 * Float.BYTES, 0); // attrib 1 -> aTexCoord (vec2) (提供常量 texcoord) GL20.glEnableVertexAttribArray(1); GL20.glVertexAttribPointer(1, 2, GL11.GL_FLOAT, false, 4 * Float.BYTES, 2 * Float.BYTES); // 绘制线环 GL11.glDrawArrays(GL11.GL_LINE_LOOP, 0, vertexCount); // 清理 GL20.glDisableVertexAttribArray(0); GL20.glDisableVertexAttribArray(1); GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0); GL30.glBindVertexArray(0); GL15.glDeleteBuffers(vbo); GL30.glDeleteVertexArrays(vao); checkGLError("drawLineLoopFromFloatArray"); } // ================== uniform 设置辅助(内部使用,确保 program 已绑定) ================== private static void setUniformIntInternal(ShaderProgram sp, String name, int value) { int loc = sp.getUniformLocation(name); if (loc != -1) GL20.glUniform1i(loc, value); } private static void setUniformVec3Internal(ShaderProgram sp, String name, org.joml.Vector3f vec) { int loc = sp.getUniformLocation(name); if (loc != -1) { GL20.glUniform3f(loc, vec.x, vec.y, vec.z); } } private static void setUniformVec2Internal(ShaderProgram sp, String name, org.joml.Vector2f vec) { int loc = sp.getUniformLocation(name); if (loc != -1) { GL20.glUniform2f(loc, vec.x, vec.y); } } private static void setUniformFloatInternal(ShaderProgram sp, String name, float value) { int loc = sp.getUniformLocation(name); if (loc != -1) GL20.glUniform1f(loc, value); } private static void setUniformVec4Internal(ShaderProgram sp, String name, Vector4f vec) { int loc = sp.getUniformLocation(name); if (loc != -1) GL20.glUniform4f(loc, vec.x, vec.y, vec.z, vec.w); } private static void setUniformMatrix3(ShaderProgram sp, String name, Matrix3f m) { int loc = sp.getUniformLocation(name); if (loc == -1) return; FloatBuffer fb = MemoryUtil.memAllocFloat(9); try { m.get(fb); GL20.glUniformMatrix3fv(loc, false, fb); } finally { MemoryUtil.memFree(fb); } } // 外部可用的统一设置(会自动切换到默认程序) private static void setUniformInt(String name, int value) { defaultProgram.use(); setUniformIntInternal(defaultProgram, name, value); defaultProgram.stop(); } private static void setUniformFloat(String name, float value) { defaultProgram.use(); setUniformFloatInternal(defaultProgram, name, value); defaultProgram.stop(); } private static void setUniformVec4(String name, Vector4f v) { defaultProgram.use(); setUniformVec4Internal(defaultProgram, name, v); defaultProgram.stop(); } // ================== 部件属性 ================== private static void setPartUniforms(ShaderProgram sp, ModelPart part) { setUniformFloatInternal(sp, "uOpacity", part.getOpacity()); int blend = 0; ModelPart.BlendMode bm = part.getBlendMode(); if (bm != null) { switch (bm) { case ADDITIVE: blend = 1; break; case MULTIPLY: blend = 2; break; case SCREEN: blend = 3; break; case NORMAL: default: blend = 0; } } else { blend = 0; } setUniformIntInternal(sp, "uBlendMode", blend); // 这里保留为白色,若需要部件 tint 请替换为 part 的 color 属性 setUniformVec4Internal(sp, "uColor", new Vector4f(1,1,1,1)); } // ================== 工具 ================== private static Matrix3f buildOrthoProjection(int width, int height) { Matrix3f m = new Matrix3f(); m.set( 2.0f / width, 0.0f, -1.0f, 0.0f, -2.0f / height, 1.0f, 0.0f, 0.0f, 1.0f ); return m; } public static void setViewport(int width, int height) { viewportWidth = Math.max(1, width); viewportHeight = Math.max(1, height); GL11.glViewport(0, 0, viewportWidth, viewportHeight); } public static void setClearColor(float r, float g, float b, float a) { GL11.glClearColor(r,g,b,a); } private static void checkGLError(String op) { int e = GL11.glGetError(); if (e != GL11.GL_NO_ERROR) { System.err.println("OpenGL error during " + op + ": " + getGLErrorString(e)); } } private static String getGLErrorString(int err) { switch (err) { case GL11.GL_INVALID_ENUM: return "GL_INVALID_ENUM"; case GL11.GL_INVALID_VALUE: return "GL_INVALID_VALUE"; case GL11.GL_INVALID_OPERATION: return "GL_INVALID_OPERATION"; case GL11.GL_OUT_OF_MEMORY: return "GL_OUT_OF_MEMORY"; default: return "Unknown(0x" + Integer.toHexString(err) + ")"; } } // ================== 辅助:外部获取状态 ================== public static boolean isInitialized() { return initialized; } public static int getLoadedMeshCount() { return meshResources.size(); } }