LWJGL3下OpenGL场景图父子节点变换继承异常问题求助
场景图父子节点变换异常问题
问题描述
基于OpenGL使用LWJGL3构建场景图,实现父节点变换作用于子节点的逻辑时功能运行异常。
核心问题为子节点的旋转无法正确跟随父节点:子节点确实会围绕父对象中心公转,符合预期逻辑,但公转速度会随父节点旋转角度增大而加快,当父节点旋转满一周回到初始角度时速度又回落至初始值。此前还遇到过子节点相对父节点平移不正确的问题,已做临时修复,具体逻辑可参考下文代码。
相关代码
Transform类(变换逻辑核心实现)
public class Transform { private Vector3f position, lastPosition; private Vector3f rotation, lastRotation; private Vector3f scale, lastScale; private Transform parent; private Matrix4f parentMatrix; public Transform() { this(null, null, null); } public Transform(Vector3f position, Vector3f rotation, Vector3f scale) { this.position = position != null ? position : new Vector3f(0, 0, 0); this.rotation = rotation != null ? rotation : new Vector3f(0, 0, 0); this.scale = scale != null ? scale : new Vector3f(1, 1, 1); this.lastPosition = new Vector3f(0, 0, 0); this.lastRotation = new Vector3f(0, 0, 0); this.lastScale = new Vector3f(1, 1, 1); parentMatrix = new Matrix4f().identity(); } public boolean requireTMUpdate() { //checks if the matrix has changed and requires an update if(parent != null) { return parent.requireTMUpdate(); } if(!position.equals(lastPosition)) { lastPosition.set(position); return true; } if(!rotation.equals(lastRotation)) { lastRotation.set(rotation); return true; } if(!scale.equals(lastScale)) { lastScale.set(scale); return true; } return false; } public Matrix4f getTransformation() { if((parent != null) && (requireTMUpdate())) { if(!(getParent().equals(getParent().getParent()))) { parentMatrix.set(parent.getTransformation()); //The above line sets the updated parentMatrix setPosition(parentMatrix.transformPosition(position)); //The above line sets the position to where child is supposed to be in //relation the parent, otherwise once the key is stopped being pressed, //it will return to the position at the start of the game / program. } }else { parentMatrix.rotationXYZ(0, 0, 0).translation(0, 0, 0).scale(1); // The above line is supposed to reset the matrix, otherwise the previous //transformations or rotations add up each frame and the it just gets messed //up. } //System.out.println(parentMatrix.toString()); return parentMatrix.mul(Matrices.transformationMatrix(position, rotation, scale)) // The transformationMatrix() method above from the Matrices class is //supposed to return a worldMatrix. } public Vector3f getPosition() { return position; } public void setPosition(Vector3f position) { this.position = position; } public Vector3f getRotation() { return rotation; } public void setRotation(Vector3f rotation) { this.rotation = rotation; } public Vector3f getScale() { return scale; } public void setScale(Vector3f scale) { this.scale = scale; } public Transform getParent() { return parent; } public void setParent(Transform parent) { this.parent = parent; } }
Matrices工具类
public class Matrices { public static Matrix4f transformationMatrix(Vector3f pos, Vector3f rot, Vector3f scale) { Matrix4f result = new Matrix4f(); result.identity(); result.translate(pos.x, pos.y, pos.z); Quaternionf rotation = new Quaternionf(). identity(). rotateX((float) Math.toRadians(rot.x)). rotateY((float) Math.toRadians(rot.y)). rotateZ((float) Math.toRadians(rot.z)); /*result.rotate((float) Math.toRadians(rot.getX()), 1, 0, 0); result.rotate((float) Math.toRadians(rot.getY()), 0, 1, 0); result.rotate((float) Math.toRadians(rot.getZ()), 0, 0, 1);*/ result.rotate(rotation); result.scale(scale.x, scale.y, scale.z); return result; } public static Matrix4f transformationMatrix(Vector3f pos, Quaternionf rotation, Vector3f scale) { Matrix4f result = new Matrix4f(); result.identity(); result.translate(pos.x, pos.y, pos.z); /*Quaternionf rotation = new Quaternionf(). identity(). rotateX((float) Math.toRadians(rot.x)). rotateY((float) Math.toRadians(rot.y)). rotateZ((float) Math.toRadians(rot.z));*/ /*result.rotate((float) Math.toRadians(rot.getX()), 1, 0, 0); result.rotate((float) Math.toRadians(rot.getY()), 0, 1, 0); result.rotate((float) Math.toRadians(rot.getZ()), 0, 0, 1);*/ result.rotate(rotation); result.scale(scale.x, scale.y, scale.z); return result; } public static Matrix4f viewMatrix(Vector3f pos, Vector3f rot) { Matrix4f result = new Matrix4f(); result.identity(); Quaternionf rotation = new Quaternionf(). identity(). rotateX((float) Math.toRadians(rot.x)). rotateY((float) Math.toRadians(rot.y)). rotateZ((float) Math.toRadians(rot.z)); /*result.rotate((float) Math.toRadians(rot.getX()), new org.joml.Vector3f(1, 0, 0)); result.rotate((float) Math.toRadians(rot.getY()), new org.joml.Vector3f(0, 1, 0));*/ result.rotate(rotation); result.translate(-pos.x, -pos.y, -pos.z); return result; } public static Matrix4f viewMatrix(Vector3f pos, float pitch, float yaw, float roll) { Matrix4f result = new Matrix4f(); result.identity(); Quaternionf rotation = new Quaternionf(). identity(). rotateX((float) Math.toRadians(pitch)). rotateY((float) Math.toRadians(yaw)). rotateZ((float) Math.toRadians(roll)); /*result.rotate((float) Math.toRadians(rot.getX()), new org.joml.Vector3f(1, 0, 0)); result.rotate((float) Math.toRadians(rot.getY()), new org.joml.Vector3f(0, 1, 0));*/ result.rotate(rotation); result.translate(-pos.x, -pos.y, -pos.z); return result; } public static Matrix4f projectionMatrix(float FOV, float aspectRatio, float NearPlaneDis, float FarPlaneDis) { Matrix4f result = new Matrix4f(); result.identity(); result.perspective(FOV, aspectRatio, NearPlaneDis, FarPlaneDis); /*float y_scale = (float) ((1f / Math.tan(Math.toRadians(FOV / 2f))) * aspectRatio); float x_scale = y_scale / aspectRatio; float frustum_length = FarPlaneDis - NearPlaneDis; result.m00(x_scale); result.m11(y_scale); result.m22(-((FarPlaneDis + NearPlaneDis) / frustum_length)); result.m23(-1); result.m32(-((2 * NearPlaneDis * FarPlaneDis) / frustum_length)); result.m33(0);*/ return result; } public static Matrix4f translate(Vector3f vec, Matrix4f src, Matrix4f dest) { if (dest == null) dest = new Matrix4f(); dest.m30(dest.m30() + src.m00() * vec.x + src.m10() * vec.y + src.m20() * vec.z); dest.m31(dest.m31() + src.m01() * vec.x + src.m11() * vec.y + src.m21() * vec.z); dest.m32(dest.m32() + src.m02() * vec.x + src.m12() * vec.y + src.m22() * vec.z); dest.m33(dest.m33() + src.m03() * vec.x + src.m13() * vec.y + src.m23() * vec.z); return dest; } public static Vector4f transform(Matrix4f left, Vector4f right, Vector4f dest) { if (dest == null) dest = new Vector4f(0, 0, 0, 0); float x = left.m00() * right.x + left.m10() * right.y + left.m20() * right.z + left.m30() * right.w; float y = left.m01() * right.x + left.m11() * right.y + left.m21() * right.z + left.m31() * right.w; float z = left.m02() * right.x + left.m12() * right.y + left.m22() * right.z + left.m32() * right.w; float w = left.m03() * right.x + left.m13() * right.y + left.m23() * right.z + left.m33() * right.w; dest.x = x; dest.y = y; dest.z = z; dest.w = w; return dest; } public static Vector3f scale(Vector3f vector, float scale) { vector.x *= scale; vector.y *= scale; vector.z *= scale; return vector; } public static float[] getAll(Matrix4f matrix) { float[] f = new float[16]; return matrix.get(f); } public static float barryCentric(Vector3f p1, Vector3f p2, Vector3f p3, Vector2f pos) { float det = (p2.z - p3.z) * (p1.x - p3.x) + (p3.x - p2.x) * (p1.z - p3.z); float l1 = ((p2.z - p3.z) * (pos.x - p3.x) + (p3.x - p2.x) * (pos.y - p3.z)) / det; float l2 = ((p3.z - p1.z) * (pos.x - p3.x) + (p1.x - p3.x) * (pos.y - p3.z)) / det; float l3 = 1.0f - l1 - l2; return l1 * p1.y + l2 * p2.y + l3 * p3.y; } }
Renderer类(对象初始化逻辑)
public class Renderer { private Model model; private Model model2; private GameObject root; private GameObject child; public Camera camera; private float time = 0; /*private float vertices[] = { 0.5f, 0.5f, 0f, //0 - top right 0.5f, -0.5f, 0f, //1 - bottom right -0.5f, -0.5f, 0f, //2 - bottom left -0.5f, 0.5f, 0f //3 - top left }; private int indices[] = { 0, 1, 3, // first triangle 3, 1, 2 // second triangle };*/ public Renderer() { model = new Model("/backpack.obj"); model2 = new Model("/backpack.obj"); camera = new Camera(new Vector3f(0, 0, 0), new Vector3f(0, 0, 0)); root = new GameObject(); child = new GameObject(); root.addComponent(camera); root.addComponent(model); child.addComponent(model2); root.addChild(child); model.getTransform().setPosition(new Vector3f(0, 0, 0)); model2.getTransform().setPosition(new Vector3f(10, 0, 10)); } public void render(Shader shader) { if(Input.isKeyDown(GLFW.GLFW_KEY_RIGHT)) { model.getTransform().setRotation(new Vector3f(0, time += 1f, 0)); } //model2.getTransform().setRotation(new Vector3f(0, time += 0.01f, 0)); shader.bind(); root.input(); root.update(); root.render(shader, camera); shader.unbind(); } public void cleanUp() { for(Mesh mesh: model.getMeshes()) { mesh.cleanUp(); } } }
问题根因与修复方案
根因
- 本地坐标被错误覆写:
getTransformation()中setPosition(parentMatrix.transformPosition(position))逻辑直接将父节点变换后的世界坐标覆盖了子节点的本地坐标属性,导致每帧都会在上一帧的变换结果上再次叠加父节点变换,角度越大叠加的偏移量越高,公转速度就越快,绕满一周后偏移量重置,速度回落。 - 更新检查逻辑错误:
requireTMUpdate()方法存在父节点时直接返回父节点的更新状态,完全忽略子节点自身的变换变化,会导致子节点修改自身属性后不会触发矩阵更新。 - 冗余逻辑干扰:临时修复添加的坐标覆写逻辑、无父节点时的矩阵重置逻辑,破坏了场景图变换的层级计算规则。
修复代码
1. 修正更新检查逻辑
public boolean requireTMUpdate() { boolean parentChanged = parent != null && parent.requireTMUpdate(); boolean selfChanged = !position.equals(lastPosition) || !rotation.equals(lastRotation) || !scale.equals(lastScale); if (selfChanged) { lastPosition.set(position); lastRotation.set(rotation); lastScale.set(scale); } return parentChanged || selfChanged; }
2. 重写变换矩阵计算逻辑
删除parentMatrix成员变量、坐标覆写逻辑、冗余的矩阵重置逻辑,直接按层级计算世界矩阵:
public Matrix4f getTransformation() { // 计算当前节点的本地变换矩阵 Matrix4f localMatrix = Matrices.transformationMatrix(position, rotation, scale); if (parent != null) { // 父节点世界矩阵 * 子节点本地矩阵 = 子节点世界矩阵 return parent.getTransformation().mul(localMatrix); } // 无父节点时本地矩阵就是世界矩阵 return localMatrix; }
修复后子节点的position/rotation/scale永远存储相对父节点的本地值,不会被计算逻辑修改,自然不会出现速度累加的问题。
内容的提问来源于stack exchange,提问作者Dstarred
相关产品推荐
相关产品推荐

