Creating a Rotation Matrix from a Quaternion 从一个四元数中创建旋转矩阵 这另外的一个方法相对简单些。并且这个基本算法来自于Matrix FAQ,虽然我需要把它转换成行优先的顺序。 static inline void Matrix3DSetUsingQuaternion3D(Matrix3D matrix, Quaternion3D quat) { matrix[0] = (1.0f - (2.0f * ((quat.y * quat.y) + (quat.z * quat.z)))); matrix[1] = (2.0f * ((quat.x * quat.y) - (quat.z * quat.w))); matrix[2] = (2.0f * ((quat.x * quat.z) + (quat.y * quat.w))); matrix[3] = 0.0f; matrix[4] = (2.0f * ((quat.x * quat.y) + (quat.z * quat.w))); matrix[5] = (1.0f - (2.0f * ((quat.x * quat.x) + (quat.z * quat.z)))); matrix[6] = (2.0f * ((quat.y * quat.z) - (quat.x * quat.w))); matrix[7] = 0.0f; matrix[8] = (2.0f * ((quat.x * quat.z) - (quat.y * quat.w))); matrix[9] = (2.0f * ((quat.y * quat.z) + (quat.x * quat.w))); matrix[10] = (1.0f - (2.0f * ((quat.x * quat.x) + (quat.y * quat.y)))); matrix[11] = 0.0f; matrix[12] = 0.0f; matrix[13] = 0.0f; matrix[14] = 0.0f; matrix[15] = 1.0f; }
Converting an Angle and Axis of Rotation to a Quaternion 把一个角度和旋转轴转换成一个四元数
四元数可以做的另外一种转换是,表示成在一个Vector3D表示的轴线上进行旋转。这在骨骼动画里面是非常有用的,因为这种表现形式通过矩阵是很难做到的。创建一个基于角度和轴旋转得四元数,我们可以这样做: static inline Quaternion3D Quaternion3DMakeWithAxisAndAngle(Vector3D axis, GLfloat angle) { Quaternion3D quat; GLfloat sinAngle; angle *= 0.5f; Vector3DNormalize(&axis); sinAngle = sinf(angle); quat.x = (axis.x * sinAngle); quat.y = (axis.y * sinAngle); quat.z = (axis.z * sinAngle); quat.w = cos(angle); return quat; }
Extracting an Angle and Axis of Rotation from a Quaternion 从一个四元数中检测角度和轴得旋转 反过来,我们也可以从四元数中取得旋转的数据,包括旋转角度和深度,就像这样 static inline void Quaternion3DExtractAxisAndAngle(Quaternion3D quat, Vector3D *axis, GLfloat *angle) { GLfloat s; Quaternion3DNormalize(&quat); s = sqrtf(1.0f - (quat.w * quat.w)); if (fabs(s) < 0.0005f) s = 1.0f; if (axis != NULL) { axis->x = (quat.x / s); axis->y = (quat.y / s); axis->z = (quat.z / s); } if (angle != NULL) *angle = (acosf(quat.w) * 2.0f); }
Quaternion Multiplication 四元数乘法 为了合并两种不同形式的四元数中得3D旋转信息。我们只需要让他们彼此相乘。好了继续我们得代码 static inline void Quaternion3DMultiply(Quaternion3D *quat1, Quaternion3D *quat2) { Vector3D v1, v2, cp; float angle; v1.x = quat1->x; v1.y = quat1->y; v1.z = quat1->z; v2.x = quat2->x; v2.y = quat2->y; v2.z = quat2->z; angle = (quat1->w * quat2->w) - Vector3DDotProduct(v1, v2); cp = Vector3DCrossProduct(v1, v2); v1.x *= quat2->w; v1.y *= quat2->w; v1.z *= quat2->w; v2.x *= quat1->w; v2.y *= quat1->w; v2.z *= quat1->w; quat1->x = v1.x + v2.x + cp.x; quat1->y = v1.y + v2.y + cp.y; quat1->z = v1.z + v2.z + cp.z; quat1->w = angle; }
Inverting a Quaternion 四元数转置 我们通过做一个四元数的共轭运算来取得四元数的转置。四元数做共轭运算其实就是将四元数中表示向量(x,y,z)的值取反。在这里的实现中,我们把它[四元数转置计算]作为四元数标准计算的一部分,而不是一个独立的步骤: static inline void Quaternion3DInvert(Quaternion3D *quat) { GLfloat length = 1.0f / ((quat->x * quat->x) + (quat->y * quat->y) + (quat->z * quat->z) + (quat->w * quat->w)); quat->x *= -length; quat->y *= -length; quat->z *= -length; quat->w *= length; }
Creating a Quaternion from Euler Angles 从欧拉角中创建四元数 前面我说过在旋转中最好不要使用欧拉角,但是有时候我们需要将欧拉角转换成四元数,比如说用户输入的信息是欧拉角信息。转换的步骤是,将欧拉轴用Vector3D表示出来,然后将Vector3D的值转换成四元数,最后将四元数相乘来得到结果: static inline Quaternion3D Quaternion3DMakeWithEulerAngles(GLfloat x, GLfloat y, GLfloat z) { Vector3D vx = Vector3DMake(1.f, 0.f, 0.f); Vector3D vy = Vector3DMake(0.f, 1.f, 0.f); Vector3D vz = Vector3DMake(0.f, 0.f, 1.f); Quaternion3D qx = Quaternion3DMakeWithAxisAndAngle(vx, x); Quaternion3D qy = Quaternion3DMakeWithAxisAndAngle(vy, y); Quaternion3D qz = Quaternion3DMakeWithAxisAndAngle(vz, z);
Quaternion3DMultiply(&qx, &qy ); Quaternion3DMultiply(&qx, &qz ); return qx; } SLERPs and NLERPS
最后,最重要的部分来了:SLERPS 和 NLERPS。SLERPS 是 球面线性插值 的缩写,NLERPS是 归一化线性插值 的缩写,还记得我们在“Part 9a”是怎样计算 代数的 线性插值 的吗,我们由此计算出了每一个依赖于 过去了多长时间的点的准确位置。但是,在插值四元法中 通过这样的 计算我们得不到想要的结果。想象一个球和一个窝关节图:
 知道哪里用得到这样的球窝关节吗?在你的 胳膊肘 的地方。当然这只是为了描叙方便。站在镜子的前面,将你的胳膊举过头顶,并保持你的胳膊竖直,然后将胳膊放下,放到你身体的一侧。 你的胳膊不是直线运动的,对吧?你的手是弧线运动的,并且比你的肘关节和你的手臂运动的要快,很有可能你的手并不是以一个恒定的速度运动。这就是我们在做动画旋转角 时想要模拟的基本的转动方式。 球面线性插值 和 归一化线性插值 是两种最通用的方法,球面线性插值 更常用,并且对现实提供了一个更好的模拟,但是它更慢,需要的配置高。归一化线性插值 更快,因为它和我们以前用过的线性插值本质上是一样的,仅仅是插值后作归一化,这个完全可以从“归一化线性插值”这个名字中体味出来。 当动画效果比对速度的要求更严格时,使用球面线性插值,否则就用 归一化线性插值,这两种方法完全相同的参数,在这两种方法之间转换应该没什么问题,你可以尝试看一下是否球面线性插值 要更多处理器消耗。 这里是一个简单的 归一化线性插值的例子: static inline Quaternion3D Quaternion3DMakeWithNLERP(Quaternion3D *start, Quaternion3D *finish, GLclampf progress) { Quaternion3D ret; GLfloat inverseProgress = 1.0f - progress; ret.x = (start->x * inverseProgress) + (finish->x * progress); ret.y = (start->y * inverseProgress) + (finish->y * progress); ret.z = (start->z * inverseProgress) + (finish->z * progress); ret.w = (start->w * inverseProgress) + (finish->w * progress); Quaternion3DNormalize(&ret); return ret; } 现在,球面线性插值的实现就有一点复杂了,它要能够 计算 四元数的点积 ,这跟计算一个向量的点积 是一样的: static inline Quaternion3D Quaternion3DMakeWithSLERP(Quaternion3D *start, Quaternion3D *finish, GLclampf progress) { GLfloat startWeight, finishWeight, difference; Quaternion3D ret; difference = ((start->x * finish->x) + (start->y * finish->y) + (start->z * finish->z) + (start->w * finish->w)); if ((1.f - fabs(difference)) > .01f) { GLfloat theta, oneOverSinTheta; theta = acosf(fabsf(difference)); oneOverSinTheta = (1.f / sinf(theta)); startWeight = (sinf(theta * (1.f - progress)) * oneOverSinTheta); finishWeight = (sinf(theta * progress) * oneOverSinTheta); if (difference < 0.f) startWeight = -startWeight; } else { startWeight = (1.f - progress); finishWeight = progress; } ret.x = (start->x * startWeight) + (finish->x * finishWeight); ret.y = (start->y * startWeight) + (finish->y * finishWeight); ret.z = (start->z * startWeight) + (finish->z * finishWeight); ret.w = (start->w * startWeight) + (finish->w * finishWeight); Quaternion3DNormalize(&ret); return ret; } Finish Line 结束语: 现在我们有能力做一些仿真骨骼动画了,下次我们还会讲解这个东西,我已经用这些新的函数和数据类型更新了Xcode工程模板。 注:1、因为我们的程序使用了浮点数,实际上我们会看到由于“有效位丢失”造成轻微变化的想象,这不是因为我们使用四元法,是因为我们使用了浮点数。 原文:iphonedevelopment,OpenGL ES from the Ground Up 9 (intermission) Quaternions iTyran翻译讨论地址:http://ityran.com/forum-36-1.html |