feat(render): 实现模型渲染层级变换与网格世界坐标烘焙

- 为 Mesh2D 添加 getX/Y 方法并优化顶点访问逻辑
-修复 FloatBuffer 剩余空间判断逻辑
- 添加 bakedToWorld 标志支持网格世界坐标烘焙- 重构 ModelPart 变换更新逻辑,增加递归重计算
- 实现 ModelPart.draw() 方法支持 shader 传参绘制
- 更新 ModelRender 渲染流程,支持 worldTransform 传递
-修正网格顶点坐标上传逻辑,兼容 baked 状态- 移除废弃的调试与上传方法
- 增强部件变换时的局部与世界矩阵同步
- 修复 printWorldPosition 使用 worldTransform 坐标
- 调整测试模型初始位置与层级结构

重点:
- 修复了XY轴无法设置的重大问题
This commit is contained in:
tzdwindows 7
2025-10-08 16:49:26 +08:00
parent 52ed33b5c8
commit becf789cb8
4 changed files with 204 additions and 112 deletions

View File

@@ -317,33 +317,58 @@ public final class ModelRender {
checkGLError("render"); checkGLError("render");
} }
/**
* 关键修改点:在渲染前确保更新 part 的 worldTransform
* 然后直接使用 part.getWorldTransform() 作为 uModelMatrix 传入 shader。
*/
private static void renderPartRecursive(ModelPart part, Matrix3f parentMat) { private static void renderPartRecursive(ModelPart part, Matrix3f parentMat) {
Matrix3f local = part.getLocalTransform(); // 确保 part 的 local/world 矩阵被计算(会更新 transformDirty
Matrix3f world = new Matrix3f(parentMat).mul(local); // world = parent * local part.updateWorldTransform(parentMat, false);
// 从 world 矩阵取世界坐标 // 直接使用已经计算好的 worldTransform
//float worldX = world.m02; Matrix3f world = part.getWorldTransform();
//float worldY = world.m12;
//System.out.println("Rendering part: " + part.getName() + " at world position: " + worldX + ", " + worldY); // 先设置部件相关的 uniformopacity / blendMode / color 等)
// 传入 shader
setUniformMatrix3(defaultProgram, "uModelMatrix", world);
setPartUniforms(defaultProgram, part); setPartUniforms(defaultProgram, part);
// 把 world 矩阵传给 shader兼容 uModelMatrix 和 可能的 uModel
setUniformMatrix3(defaultProgram, "uModelMatrix", world);
setUniformMatrix3(defaultProgram, "uModel", world);
// 绘制本节点的所有 mesh将 world 传入 renderMesh
for (Mesh2D mesh : part.getMeshes()) { for (Mesh2D mesh : part.getMeshes()) {
renderMesh(mesh); renderMesh(mesh, world);
} }
// 递归渲染子节点,继续传入当前 world 作为子节点的 parent
for (ModelPart child : part.getChildren()) { for (ModelPart child : part.getChildren()) {
renderPartRecursive(child, world); renderPartRecursive(child, world);
} }
} }
private static void renderMesh(Mesh2D mesh, Matrix3f modelMatrix) {
// 使用默认 shader保证 shader 已被 use
defaultProgram.use();
// 如果 mesh 已经被烘焙到世界坐标,则传 identity 矩阵给 shader防止重复变换
Matrix3f matToUse = mesh.isBakedToWorld() ? new Matrix3f().identity() : modelMatrix;
// 确保 shader 中的矩阵 uniform 已更新(再次设置以防遗漏)
setUniformMatrix3(defaultProgram, "uModelMatrix", matToUse);
setUniformMatrix3(defaultProgram, "uModel", matToUse);
// 调用 Mesh2D 的 draw 重载(传 program id 与实际矩阵)
try {
mesh.draw(defaultProgram.programId, matToUse);
} catch (AbstractMethodError | NoSuchMethodError e) {
// 回退:仍然兼容旧的无参 draw在这种情况下 shader 的 uModelMatrix 已经被设置)
mesh.draw();
}
private static void renderMesh(Mesh2D mesh) {
mesh.draw();
checkGLError("renderMesh"); checkGLError("renderMesh");
} }
private static int getGLDrawMode(int meshDrawMode) { private static int getGLDrawMode(int meshDrawMode) {
switch (meshDrawMode) { switch (meshDrawMode) {
case Mesh2D.POINTS: return GL11.GL_POINTS; case Mesh2D.POINTS: return GL11.GL_POINTS;
@@ -357,78 +382,7 @@ public final class ModelRender {
} }
// ================== 上传数据 ==================(被弃用) // ================== 上传数据 ==================(被弃用)
//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);
//
// // ========== 添加调试输出 ==========
// System.out.println("=== Vertex data debug output ===");
// System.out.println("Grid name: " + mesh.getName());
// System.out.println("Number of vertices: " + vertexCount);
// System.out.println("Vertex coordinates (x, y):");
//
// for (int i = 0; i < vertexCount; i++) {
// float x = verts[i*2];
// float y = verts[i*2+1];
// float u = uvs[i*2];
// float v = uvs[i*2+1];
//
// // 输出每个顶点的坐标和UV
// System.out.printf("vertex %d: location(%.3f, %.3f), UV(%.3f, %.3f)%n",
// i, x, y, u, v);
//
// inter.put(x);
// inter.put(y);
// inter.put(u);
// inter.put(v);
// }
// System.out.println("=== Vertex data output is over ===");
// // ========== 调试输出结束 ==========
//
// 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 已绑定) ================== // ================== uniform 设置辅助(内部使用,确保 program 已绑定) ==================
private static void setUniformIntInternal(ShaderProgram sp, String name, int value) { private static void setUniformIntInternal(ShaderProgram sp, String name, int value) {
@@ -480,11 +434,16 @@ public final class ModelRender {
private static void setPartUniforms(ShaderProgram sp, ModelPart part) { private static void setPartUniforms(ShaderProgram sp, ModelPart part) {
setUniformFloatInternal(sp, "uOpacity", part.getOpacity()); setUniformFloatInternal(sp, "uOpacity", part.getOpacity());
int blend = 0; int blend = 0;
switch (part.getBlendMode()) { ModelPart.BlendMode bm = part.getBlendMode();
case ADDITIVE: blend = 1; break; if (bm != null) {
case MULTIPLY: blend = 2; break; switch (bm) {
case SCREEN: blend = 3; break; case ADDITIVE: blend = 1; break;
case NORMAL: default: blend = 0; case MULTIPLY: blend = 2; break;
case SCREEN: blend = 3; break;
case NORMAL: default: blend = 0;
}
} else {
blend = 0;
} }
setUniformIntInternal(sp, "uBlendMode", blend); setUniformIntInternal(sp, "uBlendMode", blend);
// 这里保留为白色,若需要部件 tint 请替换为 part 的 color 属性 // 这里保留为白色,若需要部件 tint 请替换为 part 的 color 属性

View File

@@ -72,6 +72,8 @@ public class ModelPart {
this.boundsDirty = true; this.boundsDirty = true;
updateLocalTransform(); updateLocalTransform();
// 初始时 worldTransform = localTransform无父节点时
recomputeWorldTransformRecursive();
} }
// ==================== 层级管理 ==================== // ==================== 层级管理 ====================
@@ -88,7 +90,9 @@ public class ModelPart {
} }
children.add(child); children.add(child);
child.parent = this; child.parent = this;
markTransformDirty(); child.markTransformDirty();
// 确保子节点的 worldTransform 立即更新
child.recomputeWorldTransformRecursive();
} }
/** /**
@@ -98,7 +102,8 @@ public class ModelPart {
boolean removed = children.remove(child); boolean removed = children.remove(child);
if (removed) { if (removed) {
child.parent = null; child.parent = null;
markTransformDirty(); child.markTransformDirty();
child.recomputeWorldTransformRecursive();
} }
return removed; return removed;
} }
@@ -146,7 +151,7 @@ public class ModelPart {
// ==================== 变换系统 ==================== // ==================== 变换系统 ====================
/** /**
* 更新世界变换 * 更新世界变换(保留旧方法以兼容)
*/ */
public void updateWorldTransform(Matrix3f parentTransform, boolean recursive) { public void updateWorldTransform(Matrix3f parentTransform, boolean recursive) {
// 如果需要更新局部变换 // 如果需要更新局部变换
@@ -169,6 +174,21 @@ public class ModelPart {
transformDirty = false; transformDirty = false;
} }
public void draw(int shaderProgram, org.joml.Matrix3f parentTransform) {
// 先确保 worldTransform 是最新的
updateWorldTransform(parentTransform, false);
// 绘制本节点的所有 mesh将 worldTransform 作为 model 矩阵传入)
for (Mesh2D mesh : meshes) {
mesh.draw(shaderProgram, worldTransform);
}
// 递归绘制子节点
for (ModelPart child : children) {
child.draw(shaderProgram, worldTransform);
}
}
// 更新局部矩阵 // 更新局部矩阵
private void updateLocalTransform() { private void updateLocalTransform() {
float cos = (float)Math.cos(rotation); float cos = (float)Math.cos(rotation);
@@ -189,15 +209,36 @@ public class ModelPart {
); );
} }
/**
* 立即重新计算本节点的 worldTransform并递归到子节点
*/
private void recomputeWorldTransformRecursive() {
if (transformDirty) {
updateLocalTransform();
}
if (parent != null) {
// parent.worldTransform 已经是最新(假设父节点正确维护)
parent.worldTransform.mul(localTransform, worldTransform);
} else {
worldTransform.set(localTransform);
}
// 打印世界坐标 // 递归更新子节点
public void printWorldPosition() { for (ModelPart child : children) {
float worldX = localTransform.m02; child.recomputeWorldTransformRecursive();
float worldY = localTransform.m12; }
System.out.println("World position: " + worldX + ", " + worldY);
boundsDirty = true;
transformDirty = false;
} }
// 打印世界坐标(修正为使用 worldTransform
public void printWorldPosition() {
float worldX = worldTransform.m02();
float worldY = worldTransform.m12();
System.out.println("World position: " + worldX + ", " + worldY);
}
/** /**
* 标记变换需要更新 * 标记变换需要更新
@@ -215,11 +256,15 @@ public class ModelPart {
public void setPosition(float x, float y) { public void setPosition(float x, float y) {
position.set(x, y); position.set(x, y);
markTransformDirty(); markTransformDirty();
updateLocalTransform();
recomputeWorldTransformRecursive();
} }
public void setPosition(Vector2f position) { public void setPosition(Vector2f position) {
this.position.set(position); this.position.set(position);
markTransformDirty(); markTransformDirty();
updateLocalTransform();
recomputeWorldTransformRecursive();
} }
/** /**
@@ -228,11 +273,15 @@ public class ModelPart {
public void translate(float dx, float dy) { public void translate(float dx, float dy) {
position.add(dx, dy); position.add(dx, dy);
markTransformDirty(); markTransformDirty();
updateLocalTransform();
recomputeWorldTransformRecursive();
} }
public void translate(Vector2f delta) { public void translate(Vector2f delta) {
position.add(delta); position.add(delta);
markTransformDirty(); markTransformDirty();
updateLocalTransform();
recomputeWorldTransformRecursive();
} }
/** /**
@@ -241,6 +290,8 @@ public class ModelPart {
public void setRotation(float radians) { public void setRotation(float radians) {
this.rotation = radians; this.rotation = radians;
markTransformDirty(); markTransformDirty();
updateLocalTransform();
recomputeWorldTransformRecursive();
} }
/** /**
@@ -249,6 +300,8 @@ public class ModelPart {
public void rotate(float deltaRadians) { public void rotate(float deltaRadians) {
this.rotation += deltaRadians; this.rotation += deltaRadians;
markTransformDirty(); markTransformDirty();
updateLocalTransform();
recomputeWorldTransformRecursive();
} }
/** /**
@@ -257,16 +310,22 @@ public class ModelPart {
public void setScale(float sx, float sy) { public void setScale(float sx, float sy) {
scale.set(sx, sy); scale.set(sx, sy);
markTransformDirty(); markTransformDirty();
updateLocalTransform();
recomputeWorldTransformRecursive();
} }
public void setScale(float uniformScale) { public void setScale(float uniformScale) {
scale.set(uniformScale, uniformScale); scale.set(uniformScale, uniformScale);
markTransformDirty(); markTransformDirty();
updateLocalTransform();
recomputeWorldTransformRecursive();
} }
public void setScale(Vector2f scale) { public void setScale(Vector2f scale) {
this.scale.set(scale); this.scale.set(scale);
markTransformDirty(); markTransformDirty();
updateLocalTransform();
recomputeWorldTransformRecursive();
} }
/** /**
@@ -275,6 +334,8 @@ public class ModelPart {
public void scale(float sx, float sy) { public void scale(float sx, float sy) {
scale.mul(sx, sy); scale.mul(sx, sy);
markTransformDirty(); markTransformDirty();
updateLocalTransform();
recomputeWorldTransformRecursive();
} }
// ==================== 网格管理 ==================== // ==================== 网格管理 ====================
@@ -283,6 +344,18 @@ public class ModelPart {
* 添加网格 * 添加网格
*/ */
public void addMesh(Mesh2D mesh) { public void addMesh(Mesh2D mesh) {
if (mesh == null) return;
// 确保本节点的 worldTransform 是最新的(会递归更新子节点)
recomputeWorldTransformRecursive();
// 将 mesh 的每个顶点从本地空间变换到世界空间(烘焙)
int vc = mesh.getVertexCount();
for (int i = 0; i < vc; i++) {
org.joml.Vector2f local = mesh.getVertex(i);
org.joml.Vector2f worldPt = Matrix3fUtils.transformPoint(this.worldTransform, local);
mesh.setVertex(i, worldPt.x, worldPt.y);
}
meshes.add(mesh); meshes.add(mesh);
boundsDirty = true; boundsDirty = true;
} }
@@ -517,4 +590,4 @@ public class ModelPart {
", meshes=" + meshes.size() + ", meshes=" + meshes.size() +
'}'; '}';
} }
} }

View File

@@ -10,8 +10,7 @@ import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20; import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30; import org.lwjgl.opengl.GL30;
import org.lwjgl.system.MemoryUtil; import org.lwjgl.system.MemoryUtil;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
/** /**
* 2D网格类用于存储和管理2D模型的几何数据 * 2D网格类用于存储和管理2D模型的几何数据
* 支持顶点、UV坐标、索引和变形操作 * 支持顶点、UV坐标、索引和变形操作
@@ -40,6 +39,7 @@ public class Mesh2D {
private boolean dirty = true; // 数据是否已修改 private boolean dirty = true; // 数据是否已修改
private BoundingBox bounds; private BoundingBox bounds;
private boolean boundsDirty = true; private boolean boundsDirty = true;
private boolean bakedToWorld = false;
// ==================== 常量 ==================== // ==================== 常量 ====================
public static final int POINTS = 0; public static final int POINTS = 0;
@@ -178,8 +178,7 @@ public class Mesh2D {
if (index < 0 || index >= getVertexCount()) { if (index < 0 || index >= getVertexCount()) {
throw new IndexOutOfBoundsException("Vertex index out of bounds: " + index); throw new IndexOutOfBoundsException("Vertex index out of bounds: " + index);
} }
int baseIndex = index * 2; return dest.set(getX(index), getY(index));
return dest.set(vertices[baseIndex], vertices[baseIndex + 1]);
} }
public Vector2f getVertex(int index) { public Vector2f getVertex(int index) {
@@ -308,7 +307,7 @@ public class Mesh2D {
* 获取顶点缓冲区数据 * 获取顶点缓冲区数据
*/ */
public FloatBuffer getVertexBuffer(FloatBuffer buffer) { public FloatBuffer getVertexBuffer(FloatBuffer buffer) {
if (buffer == null || buffer.capacity() < vertices.length) { if (buffer == null || buffer.remaining() < vertices.length) {
throw new IllegalArgumentException("Buffer is null or too small"); throw new IllegalArgumentException("Buffer is null or too small");
} }
buffer.clear(); buffer.clear();
@@ -330,6 +329,20 @@ public class Mesh2D {
return buffer; return buffer;
} }
public float getX(int index) {
if (index < 0 || index >= getVertexCount()) {
throw new IndexOutOfBoundsException("Vertex index out of bounds: " + index);
}
return vertices[index * 2];
}
public float getY(int index) {
if (index < 0 || index >= getVertexCount()) {
throw new IndexOutOfBoundsException("Vertex index out of bounds: " + index);
}
return vertices[index * 2 + 1];
}
/** /**
* 获取索引缓冲区数据 * 获取索引缓冲区数据
*/ */
@@ -350,17 +363,18 @@ public class Mesh2D {
int vertexCount = getVertexCount(); int vertexCount = getVertexCount();
int floatCount = vertexCount * 4; // 每个顶点x, y, u, v int floatCount = vertexCount * 4; // 每个顶点x, y, u, v
if (buffer == null || buffer.capacity() < floatCount) { if (buffer == null || buffer.remaining() < floatCount) {
throw new IllegalArgumentException("Buffer is null or too small"); throw new IllegalArgumentException("Buffer is null or too small");
} }
buffer.clear(); buffer.clear();
for (int i = 0; i < vertexCount; i++) { for (int i = 0; i < vertexCount; i++) {
System.out.println("x:" + vertices[i * 2] + "y:" + vertices[i * 2 + 1]); // 明确使用定位方法,避免下标算错
buffer.put(vertices[i * 2]); // x //System.out.println("x:"+ getX(i) + "y:"+ getY(i));
buffer.put(vertices[i * 2 + 1]); // y buffer.put(getX(i)); // x
buffer.put(uvs[i * 2]); // u buffer.put(getY(i)); // y
buffer.put(uvs[i * 2 + 1]); // v buffer.put(uvs[i * 2]); // u
buffer.put(uvs[i * 2 + 1]); // v
} }
buffer.flip(); buffer.flip();
return buffer; return buffer;
@@ -446,7 +460,7 @@ public class Mesh2D {
/** /**
* 绘制网格(会在第一次绘制时自动上传到 GPU * 绘制网格(会在第一次绘制时自动上传到 GPU
*/ */
public void draw() { public void draw(int shaderProgram, org.joml.Matrix3f modelMatrix) {
if (!visible) return; if (!visible) return;
if (indices == null || indices.length == 0) return; if (indices == null || indices.length == 0) return;
@@ -458,10 +472,46 @@ public class Mesh2D {
texture.bind(); texture.bind();
} }
// 绑定 VAO
GL30.glBindVertexArray(vaoId);
// 将 modelMatrix 上传到 shader 的 uniform "uModel"(如果 shader 有此 uniform
int loc = GL20.glGetUniformLocation(shaderProgram, "uModel");
if (loc != -1) {
// 用一个 FloatBuffer 传递 3x3 矩阵
java.nio.FloatBuffer fb = org.lwjgl.system.MemoryUtil.memAllocFloat(9);
try {
modelMatrix.get(fb); // JOML 将矩阵写入 buffer列主序适合 OpenGL
fb.flip();
GL20.glUniformMatrix3fv(loc, false, fb);
} finally {
org.lwjgl.system.MemoryUtil.memFree(fb);
}
}
// 绘制
GL11.glDrawElements(GL11.GL_TRIANGLES, indexCount, GL11.GL_UNSIGNED_INT, 0);
// 解绑
GL30.glBindVertexArray(0);
if (texture != null) {
texture.unbind();
}
}
public void draw() {
if (!visible) return;
if (indices == null || indices.length == 0) return;
if (!uploaded) {
uploadToGPU();
}
if (texture != null) {
texture.bind();
}
GL30.glBindVertexArray(vaoId); GL30.glBindVertexArray(vaoId);
GL11.glDrawElements(GL11.GL_TRIANGLES, indexCount, GL11.GL_UNSIGNED_INT, 0); GL11.glDrawElements(GL11.GL_TRIANGLES, indexCount, GL11.GL_UNSIGNED_INT, 0);
GL30.glBindVertexArray(0); GL30.glBindVertexArray(0);
if (texture != null) { if (texture != null) {
texture.unbind(); texture.unbind();
} }
@@ -582,6 +632,15 @@ public class Mesh2D {
} }
} }
/** 标记或查询网格顶点是否已经被烘焙到世界坐标 */
public void setBakedToWorld(boolean baked) {
this.bakedToWorld = baked;
}
public boolean isBakedToWorld() {
return bakedToWorld;
}
// ==================== Object 方法 ==================== // ==================== Object 方法 ====================
@Override @Override
@@ -617,4 +676,4 @@ public class Mesh2D {
", bounds=" + getBounds() + ", bounds=" + getBounds() +
'}'; '}';
} }
} }

View File

@@ -108,7 +108,7 @@ public class ModelRenderTest {
// body 放在屏幕中心 // body 放在屏幕中心
ModelPart body = testModel.createPart("body"); ModelPart body = testModel.createPart("body");
body.setPosition(400, 320); body.setPosition(0, 0);
// 身体网格:宽 80 高 120 // 身体网格:宽 80 高 120
Mesh2D bodyMesh = Mesh2D.createQuad("body_mesh", 80, 120); Mesh2D bodyMesh = Mesh2D.createQuad("body_mesh", 80, 120);
bodyMesh.setTexture(createSolidTexture(64, 128, 0xFF4A6AFF)); // 蓝衣 bodyMesh.setTexture(createSolidTexture(64, 128, 0xFF4A6AFF)); // 蓝衣
@@ -151,6 +151,7 @@ public class ModelRenderTest {
// 建立层级body 为根,其他作为 body 的子节点 // 建立层级body 为根,其他作为 body 的子节点
//testModel.addPart(body); //testModel.addPart(body);
body.addChild(head); body.addChild(head);
body.addChild(leftArm); body.addChild(leftArm);
body.addChild(rightArm); body.addChild(rightArm);