Files
window-axis-innovators-box/src/main/java/com/chuangzhou/vivid2D/render/ModelRender.java
tzdwindows 7 173c30f277 feat(render):优化模型渲染与局部变换矩阵计算
- 精简 updateLocalTransform 方法注释并调整代码格式
- 修正局部变换矩阵的构建方式,明确先缩放再旋转的顺序
- 添加 printWorldPosition 方法用于调试世界坐标
- 在 ModelRender 中引入 Vector2f 类(暂未使用)- 调整 renderPartRecursive 方法逻辑结构并增加世界坐标打印注释- 移除冗余空行,提升代码可读性
2025-10-08 12:30:37 +08:00

530 lines
20 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.Mesh2D;
import com.chuangzhou.vivid2D.render.model.util.Texture;
import org.joml.Matrix3f;
import org.joml.Vector2f;
import org.joml.Vector4f;
import org.lwjgl.opengl.*;
import org.lwjgl.system.MemoryUtil;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 重构后的 ModelRender更模块化、健壮的渲染子系统
*/
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<String, ShaderProgram> shaderMap = new HashMap<>();
private static ShaderProgram defaultProgram = null;
private static final Map<Mesh2D, MeshGLResources> meshResources = new HashMap<>();
private static final AtomicInteger textureUnitAllocator = new AtomicInteger(0);
// 默认白色纹理
private static int defaultTextureId = 0;
// ================== 内部类ShaderProgram ==================
private static class ShaderProgram {
final int programId;
final Map<String, Integer> 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 = GL20.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\n" +
"layout(location = 0) in vec2 aPosition;\n" +
"layout(location = 1) in vec2 aTexCoord;\n" +
"out vec2 vTexCoord;\n" +
"uniform mat3 uModelMatrix;\n" +
"uniform mat3 uViewMatrix;\n" +
"uniform mat3 uProjectionMatrix;\n" +
"void main() {\n" +
" vec3 p = uProjectionMatrix * uViewMatrix * uModelMatrix * vec3(aPosition, 1.0);\n" +
" gl_Position = vec4(p.xy, 0.0, 1.0);\n" +
" vTexCoord = aTexCoord;\n" +
"}";
private static final String FRAGMENT_SHADER_SRC =
"#version 330 core\n" +
"in vec2 vTexCoord;\n" +
"out vec4 FragColor;\n" +
"uniform sampler2D uTexture;\n" +
"uniform vec4 uColor;\n" +
"uniform float uOpacity;\n" +
"uniform int uBlendMode;\n" +
"void main() {\n" +
" vec4 tex = texture(uTexture, vTexCoord);\n" +
" vec4 finalColor = tex * uColor;\n" +
" if (uBlendMode == 1) finalColor.rgb = tex.rgb + uColor.rgb;\n" +
" else if (uBlendMode == 2) finalColor.rgb = tex.rgb * uColor.rgb;\n" +
" else if (uBlendMode == 3) finalColor.rgb = 1.0 - (1.0 - tex.rgb) * (1.0 - uColor.rgb);\n" +
" finalColor.a = tex.a * uOpacity;\n" +
" if (finalColor.a <= 0.001) discard;\n" +
" FragColor = finalColor;\n" +
"}";
// ================== 初始化 / 清理 ==================
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 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;
// 设置一些默认 uniform需要先 use
sp.use();
setUniformIntInternal(sp, "uTexture", 0);
setUniformFloatInternal(sp, "uOpacity", 1.0f);
setUniformVec4Internal(sp, "uColor", new Vector4f(1,1,1,1));
setUniformIntInternal(sp, "uBlendMode", 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;
// 更新模型(确保 worldTransform 已经被计算)
model.update(deltaTime);
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | (enableDepthTest ? GL11.GL_DEPTH_BUFFER_BIT : 0));
// 使用默认 shader保持绑定直到完成渲染
defaultProgram.use();
// 设置投影与视图3x3 正交投影用于 2D
Matrix3f proj = buildOrthoProjection(viewportWidth, viewportHeight);
setUniformMatrix3(defaultProgram, "uProjectionMatrix", proj);
setUniformMatrix3(defaultProgram, "uViewMatrix", new Matrix3f().identity());
// 递归渲染所有根部件(使用 3x3 矩阵)
Matrix3f identity = new Matrix3f().identity();
for (ModelPart p : model.getParts()) {
if (p.getParent() != null) continue;
renderPartRecursive(p, identity);
}
defaultProgram.stop();
checkGLError("render");
}
private static void renderPartRecursive(ModelPart part, Matrix3f parentMat) {
Matrix3f local = part.getLocalTransform(); // 局部矩阵
Matrix3f world = new Matrix3f(parentMat).mul(local); // world = parent * local
// 从 world 矩阵取世界坐标
//float worldX = world.m02;
//float worldY = world.m12;
//System.out.println("Rendering part: " + part.getName() + " at world position: " + worldX + ", " + worldY);
// 传入 shader
setUniformMatrix3(defaultProgram, "uModelMatrix", world);
setPartUniforms(defaultProgram, part);
for (Mesh2D mesh : part.getMeshes()) {
renderMesh(mesh);
}
for (ModelPart child : part.getChildren()) {
renderPartRecursive(child, world);
}
}
private static void renderMesh(Mesh2D mesh) {
// 确保 mesh 的 GL 资源已上传ModelRender 管理 upload
MeshGLResources res = meshResources.computeIfAbsent(mesh, k -> new MeshGLResources());
if (!res.initialized) uploadMeshData(mesh, res);
// 绑定纹理到单元0我们使用 0 固定)
Texture tex = mesh.getTexture();
int texId = (tex != null && !tex.isDisposed()) ? tex.getTextureId() : defaultTextureId;
// active unit & bind — 确保 shader 已被 use()(调用者保证)
GL13.glActiveTexture(GL13.GL_TEXTURE0);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texId);
// 将 sampler 设为 0内部函数保证 program 绑定)
setUniformIntInternal(defaultProgram, "uTexture", 0);
// 绑定 VAO 并绘制
GL30.glBindVertexArray(res.vao);
int drawMode = getGLDrawMode(mesh.getDrawMode());
if (mesh.getIndices().length > 0 &&
(drawMode == GL11.GL_TRIANGLES || drawMode == GL11.GL_TRIANGLE_STRIP || drawMode == GL11.GL_TRIANGLE_FAN)) {
GL11.glDrawElements(drawMode, mesh.getIndices().length, GL11.GL_UNSIGNED_INT, 0);
} else {
GL11.glDrawArrays(drawMode, 0, res.vertexCount);
}
GL30.glBindVertexArray(0);
// 解绑纹理(避免污染后续 state
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
checkGLError("renderMesh");
}
private static int getGLDrawMode(int meshDrawMode) {
switch (meshDrawMode) {
case Mesh2D.POINTS: return GL11.GL_POINTS;
case Mesh2D.LINES: return GL11.GL_LINES;
case Mesh2D.LINE_STRIP: return GL11.GL_LINE_STRIP;
case Mesh2D.TRIANGLES: return GL11.GL_TRIANGLES;
case Mesh2D.TRIANGLE_STRIP: return GL11.GL_TRIANGLE_STRIP;
case Mesh2D.TRIANGLE_FAN: return GL11.GL_TRIANGLE_FAN;
default: return GL11.GL_TRIANGLES;
}
}
// ================== 上传数据 ==================
private static void uploadMeshData(Mesh2D mesh, MeshGLResources res) {
System.out.println("Uploading mesh data: " + mesh.getName());
res.vao = GL30.glGenVertexArrays();
GL30.glBindVertexArray(res.vao);
res.vbo = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, res.vbo);
float[] verts = mesh.getVertices();
float[] uvs = mesh.getUVs();
int vertexCount = mesh.getVertexCount();
if (verts == null || verts.length == 0) throw new IllegalStateException("Mesh has no vertices: " + mesh.getName());
FloatBuffer inter = MemoryUtil.memAllocFloat(vertexCount * 4);
for (int i = 0; i < vertexCount; i++) {
inter.put(verts[i*2]);
inter.put(verts[i*2+1]);
inter.put(uvs[i*2]);
inter.put(uvs[i*2+1]);
}
inter.flip();
GL15.glBufferData(GL15.GL_ARRAY_BUFFER, inter, GL15.GL_STATIC_DRAW);
MemoryUtil.memFree(inter);
// 设置 attribute位置 / uvlayout 已在 shader 中固定
int stride = 4 * Float.BYTES;
GL20.glEnableVertexAttribArray(0);
GL20.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, stride, 0);
GL20.glEnableVertexAttribArray(1);
GL20.glVertexAttribPointer(1, 2, GL11.GL_FLOAT, false, stride, 2 * Float.BYTES);
int[] indices = mesh.getIndices();
if (indices != null && indices.length > 0) {
res.ebo = GL15.glGenBuffers();
GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, res.ebo);
IntBuffer ib = MemoryUtil.memAllocInt(indices.length);
ib.put(indices).flip();
GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, ib, GL15.GL_STATIC_DRAW);
MemoryUtil.memFree(ib);
res.vertexCount = indices.length; // drawElements 使用 count
} else {
res.vertexCount = vertexCount;
}
// 不解绑 ELEMENT_ARRAY_BUFFER它属于 VAO解绑 ARRAY_BUFFER
GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GL30.glBindVertexArray(0);
res.initialized = true;
checkGLError("uploadMeshData");
System.out.println("Uploaded mesh: " + mesh.getName() + " (v=" + vertexCount + ")");
}
// ================== 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 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;
switch (part.getBlendMode()) {
case ADDITIVE: blend = 1; break;
case MULTIPLY: blend = 2; break;
case SCREEN: blend = 3; break;
case NORMAL: default: 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(); }
}