OpenGL着色器中无父子关系的多轴旋转实现问题
我正尝试用PyOpenGL结合着色器实现物体多轴旋转,但旧版OpenGL的glRotatef()已经不能用,只能用4×4旋转矩阵。但矩阵乘法不满足交换律,会形成父子层级——一个轴独立旋转(父),另一个轴基于前者旋转(子)。我刚学AP预微积分,还没接触矩阵知识,但知道旋转矩阵相乘必然会有这种层级关系。
我目前的矩阵实现代码如下:
model1 = np.array([[1, 0, 0, 0], [0, cos(player_rotation_x), sin(player_rotation_x), 0], [0, -sin(player_rotation_x), cos(player_rotation_x), 0], [0, 0, 0, 1] ]) @ np.array([[cos(-player_rotation_z), -sin(-player_rotation_z), 0, 0], [sin(-player_rotation_z), cos(-player_rotation_z), 0, 0], [0, 0, 1, 0], [0, 0, 0, 1] ]) @ np.array([[cos(player_rotation_y), 0, -sin(player_rotation_y), 0], [0, 1, 0, 0], [sin(player_rotation_y), 0, cos(player_rotation_y), 0], [0, 0, 0, 1]])
这段代码形成了从x轴到y轴的旋转层级。
我想复现旧版固定功能管线的旋转逻辑,当时的代码是这样的:
rotation_matrix = (GLfloat * 16)(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) while True: glPushMatrix() glLoadIdentity() glRotatef(-player_rotation_x, 1, 0, 0) glRotatef(-player_rotation_y, 0, 1, 0) glMultMatrixf(rotation_matrix) glGetFloatv(GL_MODELVIEW_MATRIX, rotation_matrix) glPopMatrix()
你要复现的旧版固定管线逻辑,本质是基于当前物体自身坐标系的增量旋转——每次旋转都是在物体当前的局部坐标系下进行,而非固定的世界坐标系。这和你现在直接用三个轴的旋转矩阵相乘(基于世界坐标系的顺序旋转)是完全不同的逻辑。
要在着色器环境下实现这个效果,核心是保存并更新当前的旋转状态矩阵,而非每次都用初始的三个轴旋转矩阵重新计算。具体步骤如下:
维护一个全局的旋转状态矩阵
就像旧代码里的rotation_matrix变量一样,在CPU端维护一个持续更新的4×4矩阵,用来记录物体当前的旋转状态,初始值设为单位矩阵。每次更新旋转时,基于当前状态矩阵计算新矩阵
当有新的旋转增量(比如delta_x、delta_y)时,先把增量旋转转换成对应的旋转矩阵,再和当前的状态矩阵相乘以更新状态。注意矩阵乘法的顺序:增量旋转矩阵要左乘当前状态矩阵,这样才是在物体当前局部坐标系下进行旋转,和旧版glRotatef的行为一致。示例代码如下:
import numpy as np # 初始化旋转状态矩阵为单位矩阵 current_rot_matrix = np.identity(4, dtype=np.float32) # 假设delta_x是x轴的旋转增量(弧度),delta_y是y轴的旋转增量 def update_rotation(delta_x, delta_y): global current_rot_matrix # 生成x轴增量旋转矩阵 rot_x = np.array([[1, 0, 0, 0], [0, np.cos(delta_x), np.sin(delta_x), 0], [0, -np.sin(delta_x), np.cos(delta_x), 0], [0, 0, 0, 1]], dtype=np.float32) # 生成y轴增量旋转矩阵 rot_y = np.array([[np.cos(delta_y), 0, -np.sin(delta_y), 0], [0, 1, 0, 0], [np.sin(delta_y), 0, np.cos(delta_y), 0], [0, 0, 0, 1]], dtype=np.float32) # 按旧代码的旋转顺序,左乘增量矩阵到当前状态矩阵 current_rot_matrix = rot_y @ rot_x @ current_rot_matrix这里的乘法顺序要和旧代码
glRotatef的调用顺序对应:旧代码先调用x轴旋转,再y轴,所以矩阵相乘时要把y轴矩阵放在前面(因为矩阵乘法是从右到左应用变换)。将更新后的状态矩阵传入着色器
把维护的current_rot_matrix作为uniform变量传入顶点着色器,用来变换物体的顶点坐标,代替你之前直接计算的model1矩阵。
关键区别说明
- 你之前的代码是基于世界坐标系的顺序旋转:每次都从单位矩阵开始,用固定的x、y、z旋转角度生成矩阵相乘,结果的旋转顺序固定,形成固定的父子层级(比如先绕世界x轴转,再绕世界y轴转)。
- 旧版固定管线的逻辑是基于局部坐标系的增量旋转:每次旋转都是在物体当前姿态基础上,绕自身的轴旋转,旋转顺序不固定(可随时添加任意轴的增量旋转),不会有固定的父子层级。
按上述方式实现后,就能完全复现旧版glRotatef的旋转行为,避免固定的父子层级问题。
内容的提问来源于stack exchange,提问作者eeeeeeeeeeeeeee

