OpenGL+GLFW中如何围绕世界空间同一枢轴点旋转多个模型矩阵?
问题:OpenGL多模型围绕同一枢轴旋转错位问题
我使用OpenGL与GLFW制作兔子模型,包含身体、前腿、后腿三个独立部件,希望让它们围绕世界空间中的同一Y轴枢轴旋转朝向同一方向,但旋转时腿部出现错位移动;不进行Y轴旋转时显示正常。我需要找到一种无需使用四元数的解决方案(对四元数感到困惑,无法写出可用代码)。
效果演示:
核心代码
class rabbit { public: std::vector<colored_model_object*> rabbit_renderers; std::vector<glm::mat4> rabbit_models; glm::vec3 position = glm::vec3(0.0f); float fore_leg_rotation = 0; float hind_leg_rotation = 0; float whole_rotation = 0; int jump_phase = 0; float jump_lerp = 0.0f; float jump_speed = 0.003f; glm::vec3 jump_start, jump_end; float y_velocity = 0; rabbit() { rabbit_renderers.push_back(new colored_model_object("res/objects/rabbit/rabbit_body.obj")); rabbit_renderers.push_back(new colored_model_object("res/objects/rabbit/rabbit_front_legs.obj")); rabbit_renderers.push_back(new colored_model_object("res/objects/rabbit/rabbit_hind_legs.obj")); } void jump(glm::vec3 direction, float magnitude) { if (jump_phase >= 1) return; glm::vec3 jump_power = glm::normalize(direction) * magnitude; jump_start = position; jump_end = position + jump_power; jump_phase = 1; jump_lerp = 0; fore_leg_rotation = 0.0f; hind_leg_rotation = 0.0f; y_velocity = 1; glm::vec2 dir_2d = glm::normalize(glm::vec2(direction.x, direction.z)); glm::vec2 up = glm::vec2(0, 1); float dot = dir_2d.x * up.x + dir_2d.y * up.y; float det = dir_2d.x * up.y - dir_2d.y * up.x; float body_angle = atan2(det, dot); whole_rotation = body_angle; } void update() { position.y += y_velocity; y_velocity -= 0.008f; if (position.y < 0) position.y = 0; if (jump_phase> 0) { glm::vec2 jump_start_xz = glm::vec2(jump_start.x, jump_start.z); glm::vec2 jump_end_xz = glm::vec2(jump_end.x, jump_end.z); glm::vec2 position_xz = glm::mix(jump_start_xz, jump_end_xz, jump_lerp); position.x = position_xz.x; position.z = position_xz.y; jump_lerp += jump_speed; } if (jump_phase == 1) { fore_leg_rotation += 1; hind_leg_rotation += 1; if (fore_leg_rotation > 45.0f) { jump_phase = 2; } } if (jump_phase == 2) { fore_leg_rotation -= 0.2f; hind_leg_rotation -= 0.2f; if (fore_leg_rotation < 0.0f) { jump_phase = 3; } } if (jump_lerp > 1) { jump_phase = 0; jump_lerp = 0; fore_leg_rotation = 0.0f; hind_leg_rotation = 0.0f; } } void update_models() { update(); rabbit_models.clear(); rabbit_models.push_back(system3d::get_model()); rabbit_models.push_back(system3d::get_model()); rabbit_models.push_back(system3d::get_model()); rabbit_models.at(0) = (system3d::create_model(rabbit_models.at(0),glm::vec3(0.1f, 0.1f, 0.1f), glm::vec3(-100 + position.x, 310.5f + position.y, 0 + position.z), 0.0f, glm::vec3(1, 1, 1))); rabbit_models.at(1) = (system3d::create_model(rabbit_models.at(1), glm::vec3(0.1f, 0.1f, 0.1f), glm::vec3(-100 + position.x, 310.5f + position.y, 0 + position.z), fore_leg_rotation, glm::vec3(1, 0, 0), glm::vec3(0, -42.0f, 0))); rabbit_models.at(2) = (system3d::create_model(rabbit_models.at(2), glm::vec3(0.1f, 0.1f, 0.1f), glm::vec3(-100 + position.x, 310.5f + position.y, 0 + position.z), hind_leg_rotation, glm::vec3(1, 0, 0), glm::vec3(0, -42.0f, 17.0f))); for (int i = 0; i < rabbit_models.size(); i++) { rabbit_models.at(i) = glm::rotate(rabbit_models.at(i), glm::radians(whole_rotation), glm::vec3(0, 1, 0)); } } };
引用的system3d类代码
class system3d { public: static glm::mat4 get_model(glm::vec3 scale_vec, glm::vec3 translate_vec, float rotation, glm::vec3 rotation_vec) { glm::mat4 model = glm::mat4(1.0f); model = glm::scale(model, scale_vec); model = glm::rotate(model, glm::radians(rotation), rotation_vec); model = glm::translate(model, translate_vec); return model; } static glm::mat4 get_model(glm::vec3 scale_vec, glm::vec3 translate_vec, float rotation, glm::vec3 rotation_vec, glm::vec3 rotation_pivot) { glm::mat4 model = glm::mat4(1.0f); model = glm::scale(model, scale_vec); model = glm::translate(model, translate_vec); model = glm::translate(model, -rotation_pivot); model = glm::rotate(model, glm::radians(rotation), rotation_vec); model = glm::translate(model, rotation_pivot); return model; } static glm::mat4 create_model(glm::mat4 existing, glm::vec3 scale_vec, glm::vec3 translate_vec, float rotation, glm::vec3 rotation_vec) { glm::mat4 model = existing; model = glm::scale(model, scale_vec); model = glm::rotate(model, glm::radians(rotation), rotation_vec); model = glm::translate(model, translate_vec); return model; } static glm::mat4 create_model(glm::mat4 existing, glm::vec3 scale_vec, glm::vec3 translate_vec, float rotation, glm::vec3 rotation_vec, glm::vec3 rotation_pivot) { glm::mat4 model = existing; model = glm::scale(model, scale_vec); model = glm::translate(model, translate_vec); model = glm::translate(model, -rotation_pivot); model = glm::rotate(model, glm::radians(rotation), rotation_vec); model = glm::translate(model, rotation_pivot); return model; } static glm::mat4 get_model() { glm::mat4 model = glm::mat4(1.0f); return model; } };
解决办法
问题根源是矩阵变换顺序错误:你先给每个部件做局部变换(缩放、旋转、平移到兔子位置),再叠加全局Y轴旋转,这会让每个部件绕自身变换后的原点旋转,而非统一的枢轴点。
具体修改方案
调整update_models函数逻辑,先构建全局基础矩阵(包含缩放、全局旋转、整体位置),再给每个部件叠加局部动画旋转,确保全局旋转统一作用于所有部件:
void update_models() { update(); rabbit_models.clear(); // 构建全局基础矩阵:缩放 → 全局Y轴旋转 → 平移到兔子世界位置 glm::mat4 base_model = glm::mat4(1.0f); base_model = glm::scale(base_model, glm::vec3(0.1f, 0.1f, 0.1f)); base_model = glm::rotate(base_model, glm::radians(whole_rotation), glm::vec3(0, 1, 0)); base_model = glm::translate(base_model, glm::vec3(-100 + position.x, 310.5f + position.y, 0 + position.z)); // 身体:直接使用基础矩阵,无额外局部旋转 rabbit_models.push_back(base_model); // 前腿:基于基础矩阵,叠加局部关节旋转 glm::mat4 fore_leg_model = base_model; // 平移到腿部枢轴 → 旋转 → 移回,确保绕关节旋转 fore_leg_model = glm::translate(fore_leg_model, glm::vec3(0, -42.0f, 0)); fore_leg_model = glm::rotate(fore_leg_model, glm::radians(fore_leg_rotation), glm::vec3(1, 0, 0)); fore_leg_model = glm::translate(fore_leg_model, glm::vec3(0, 42.0f, 0)); rabbit_models.push_back(fore_leg_model); // 后腿:同理处理局部关节旋转 glm::mat4 hind_leg_model = base_model; hind_leg_model = glm::translate(hind_leg_model, glm::vec3(0, -42.0f, 17.0f)); hind_leg_model = glm::rotate(hind_leg_model, glm::radians(hind_leg_rotation), glm::vec3(1, 0, 0)); hind_leg_model = glm::translate(hind_leg_model, glm::vec3(0, 42.0f, -17.0f)); rabbit_models.push_back(hind_leg_model); }
原理说明
- 矩阵变换顺序遵循缩放→旋转→平移的全局变换逻辑,确保全局旋转是作用在整个兔子的统一空间上。
- 局部关节旋转需要先平移到枢轴点,旋转后再移回,这样局部旋转的中心是部件的关节位置,而非模型原点。
- 放弃system3d的封装,直接使用glm基础变换函数,逻辑更透明,避免封装带来的变换顺序混淆。
内容的提问来源于stack exchange,提问作者instance
相关产品推荐
相关产品推荐

