用幾個實用的例子帶你理解四元數!文末獲取完整項目!html
前言
本文不會講太多四元數公式的推導過程,重點講講幾個接口的使用和我的理解。node
閱讀本文可能須要一些前置的知識(但不限於這些知識點):git
-
向量 (內積外積/基本運算/幾何意義) -
座標系(左手系/右手系/世界座標/本地座標) -
矩陣(平移/旋轉/縮放/模型矩陣/視圖矩陣/投影矩陣) -
視點和視線(視點/觀察目標/上方向)
表示3D旋轉通常採用三種方法:github
-
矩陣 -
歐拉角 -
四元數
爲何使用四元數表示旋轉呢?web
-
平滑插值。(矩陣基本沒有,歐拉角能夠作插值,但可能遭遇萬向鎖的問題) -
快速鏈接和角位移求逆。 -
能和矩陣快速轉換。 -
僅用四個數。(矩陣9個,歐拉角3個) -
難以理解,學會了看起來很牛逼。
固然四元數也有一些缺點:編程
-
四元數可能不合法。(通常經過四元數標準化解決這個問題,確保四元數爲單位四元數) -
對給定的方位的表達方式有兩種方法,它們相互爲負。(矩陣惟一,歐拉角有無數種) -
相對難以使用。
![](http://static.javashuo.com/static/loading.gif)
實例
構造四元數
四元數的定義這邊就不詳細說了,大概知道就是用四個數字去表達旋轉。api
那麼怎麼去構造這個四元數呢?咱們從API入手去講解和理解。微信
旋轉軸和旋轉角
有了旋轉軸和旋轉角,就能夠表示旋轉了,那麼四元數也能夠經過這個構造出來。app
![](http://static.javashuo.com/static/loading.gif)
/**
* @zh 根據旋轉軸和旋轉弧度計算四元數
*/
public static fromAxisAngle<Out extends IQuatLike, VecLike extends IVec3Like> (out: Out, axis: VecLike, rad: number) {
rad = rad * 0.5; // 爲何要除以2?由於公式推導出來的!
const s = Math.sin(rad);
out.x = s * axis.x;
out.y = s * axis.y;
out.z = s * axis.z;
out.w = Math.cos(rad);
return out;
}
本地座標軸
根據該物體本地座標軸也能肯定旋轉。編輯器
![](http://static.javashuo.com/static/loading.gif)
/**
* @zh 根據本地座標軸朝向計算四元數,默認三向量都已歸一化且相互垂直
*/
public static fromAxes<Out extends IQuatLike, VecLike extends IVec3Like> (out: Out, xAxis: VecLike, yAxis: VecLike, zAxis: VecLike) {
Mat3.set(m3_1,
xAxis.x, xAxis.y, xAxis.z,
yAxis.x, yAxis.y, yAxis.z,
zAxis.x, zAxis.y, zAxis.z,
);
return Quat.normalize(out, Quat.fromMat3(out, m3_1));
}
視口和上方向
根據視口的前方向和上方向,先計算本地座標軸的右向量,再算出本地座標的上向量,最後再構形成四元數。
![](http://static.javashuo.com/static/loading.gif)
/**
* @zh 根據視口的前方向和上方向計算四元數
* @param view 視口面向的前方向,必須歸一化
* @param up 視口的上方向,必須歸一化,默認爲 (0, 1, 0)
*/
public static fromViewUp<Out extends IQuatLike, VecLike extends IVec3Like> (out: Out, view: VecLike, up?: Vec3) {
Mat3.fromViewUp(m3_1, view, up);
return Quat.normalize(out, Quat.fromMat3(out, m3_1));
}
兩向量間的最短路徑旋轉
也能夠用一個四元數表示兩向量旋轉的最短路徑。
![](http://static.javashuo.com/static/loading.gif)
/**
* @zh 設置四元數爲兩向量間的最短路徑旋轉,默認兩向量都已歸一化
*/
public static rotationTo<Out extends IQuatLike, VecLike extends IVec3Like> (out: Out, a: VecLike, b: VecLike) {
// 省略代碼實現
}
矩陣/歐拉角
也能夠經過其餘表示方法轉換爲四元數。
/**
* @zh 根據三維矩陣信息計算四元數,默認輸入矩陣不含有縮放信息
*/
public static fromMat3<Out extends IQuatLike> (out: Out, m: Mat3) {
// 省略代碼實現
}
/**
* @zh 根據歐拉角信息計算四元數,旋轉順序爲 YZX
*/
public static fromEuler<Out extends IQuatLike> (out: Out, x: number, y: number, z: number) {
// 省略代碼實現
}
獲取四元數相關信息
上面講了如何去構造,相應的也能夠經過四元數獲取相關信息,這裏不細講了含義了,直接看看API吧。
/**
* @zh 獲取四元數的旋轉軸和旋轉弧度
* @param outAxis 旋轉軸輸出
* @param q 源四元數
* @return 旋轉弧度
*/
public static getAxisAngle<Out extends IQuatLike, VecLike extends IVec3Like> (outAxis: VecLike, q: Out) {
//...
}
/**
* @zh 返回定義此四元數的座標系 X 軸向量
*/
public static toAxisX (out: IVec3Like, q: IQuatLike) {
//...
}
/**
* @zh 返回定義此四元數的座標系 Y 軸向量
*/
public static toAxisY (out: IVec3Like, q: IQuatLike) {
//...
}
/**
* @zh 返回定義此四元數的座標系 Z 軸向量
*/
public static toAxisZ (out: IVec3Like, q: IQuatLike) {
//...
}
/**
* @zh 根據四元數計算歐拉角,返回角度 x, y 在 [-180, 180] 區間內, z 默認在 [-90, 90] 區間內,旋轉順序爲 YZX
* @param outerZ z 取值範圍區間改成 [-180, -90] U [90, 180]
*/
public static toEuler (out: IVec3Like, q: IQuatLike, outerZ?: boolean) {
//...
}
實際例子
沒有實戰,單純講API就是耍流氓!直接進入實戰部分!
角色朝向和平滑插值
已知當前點和下一個點,如何求出角色的朝向四元數?
-
先算出前方向 -
根據視口上方向求出四元數
const cur_p = list[index - 1]; // 當前點
const next_p = list[index]; // 最終點
const quat_end = new Quat(); // 最終旋轉四元數
const dir = next_p.clone().subtract(cur_p); // 前向量
// 模型正好朝z軸方向
Quat.fromViewUp(quat_end, dir.normalize(), v3(0, 1, 0)); // 根據視口的前方向和上方向計算四元數
// 最終旋轉四元數 / 視口面向的前方向 / 視口的上方向
已知起始四元數和終點四元數,如何平滑旋轉?
const tw = tween(this.node_bezier_role); // 使用tween動畫
const quat_start = new Quat();
this.node_bezier_role.getRotation(quat_start); // 獲取起始四元數
const quat_end = new Quat(); // 最終旋轉四元數 假設已經算出
const quat_now = new Quat(); // 用一箇中間變量
tw.to(0.2, {}, {
onUpdate: (target, ratio: number) => {
// ratio : 0~1
// 這裏使用球面插值,旋轉時不會出現變形
quat_now.set(quat_start).slerp(quat_end, ratio);
this.node_bezier_role.setRotation(quat_now);
},
})
tw.start();
將旋轉和移動結合起來就能達到下面這個效果。
![](http://static.javashuo.com/static/loading.gif)
觸摸旋轉
關鍵是求出旋轉軸,這邊處理的旋轉軸在 xoy
這個平面上。
![](http://static.javashuo.com/static/loading.gif)
// private onTouchMove(touch: Touch) {
const delta = touch.getDelta();
// 自傳
// 這個物體模型‘錨點’在正中心效果比較好
// 垂直的軸,右手
//
// 旋轉軸
// ↑
// ---> 觸摸方向
const axis = v3(-delta.y, delta.x, 0); //旋轉軸,根據類似三角形求出
const rad = delta.length() * 1e-2; //旋轉角度
const quat_cur = this.node_touch_rotation_role.getRotation(); //當前的四元數
Quat.rotateAround(this.__temp_quat, quat_cur, axis.normalize(), rad); //當面的四元數繞旋轉軸旋轉
// 旋轉後的結果 / 當前的四元數 / 旋轉軸 / 旋轉四元數
this.node_touch_rotation_role.setRotation(this.__temp_quat);
展現結果以下:
![](http://static.javashuo.com/static/loading.gif)
繞軸旋轉
已知旋轉點、旋轉軸、旋轉角度,求旋轉後的位置和朝向。
朝向計算和觸摸旋轉相似,這裏不詳說了。
這邊講講如何計算旋轉後的座標。
-
先計算旋轉點和當前位置點的向量(起始向量) -
計算旋轉四元數 -
計算起始向量旋轉後的向量 -
計算旋轉後的座標點
![](http://static.javashuo.com/static/loading.gif)
// private onTouchMove(touch: Touch) {
const delta = touch.getDelta();
// 繞軸轉
// 這裏選取軸朝上
const axis2 = Vec3.UP;//旋轉軸
const rad2 = 1e-2 * delta.x; //旋轉角度
// 計算座標
const point = this.node_axi.worldPosition; //旋轉點
const point_now = this.node_touch_axi_role.worldPosition; // 當前點的位置
// 算出座標點的旋轉四元數
Quat.fromAxisAngle(this.__temp_quat, axis2, rad2);
// 計算旋轉點和現有點的向量
Vec3.subtract(this.__temp_v3, point_now, point);
// 計算旋轉後的向量
Vec3.transformQuat(this.__temp_v3, this.__temp_v3, this.__temp_quat)
// 計算旋轉後的點
Vec3.add(this.__temp_v3, point, this.__temp_v3);
this.node_touch_axi_role.setWorldPosition(this.__temp_v3);
// 計算朝向
// 這麼旋轉會按原始的朝向一塊兒旋轉
const quat_now = this.node_touch_axi_role.worldRotation;
Quat.rotateAround(this.__temp_quat, quat_now, axis2, rad2);
Quat.normalize(this.__temp_quat, this.__temp_quat);
this.node_touch_axi_role.setWorldRotation(this.__temp_quat);
最終效果以下。
![](http://static.javashuo.com/static/loading.gif)
小結
能夠把四元數看成一個工具,想一想旋轉能夠是用軸角度,本地座標系,或者視角方向構造出來的,再使用相應的接口去實現咱們的各類需求。
以上爲白玉無冰使用 Cocos Creator 3D v1.2
實現 "四元數與旋轉"
的技術分享。歡迎分享給身邊的朋友!
參考
-
《WebGL編程指南》 -
-
《3D數學基礎:圖形與遊戲開發》 -
-
https://docs.cocos.com/creator3d/api/zh/classes/core_math.quat.html -
https://en.wikipedia.org/wiki/Quaternion -
https://eater.net/quaternions -
https://github.com/Krasjet/quaternion -
https://forum.cocos.org/t/creator-3d-unity-transfrom-rotatearound-api/85157/5 -
https://forum.cocos.org/t/topic/92924/11 -
https://forum.cocos.org/t/creator-3d/91299
轉載請保留文末二維碼和完整代碼獲取方式!
完整代碼(詳見readme):
https://github.com/baiyuwubing/cocos-creator-3d-examples/tree/master/1-2-x
點擊「閱讀原文」查看精選導航
‘讚揚’「點贊「 」在看」 鼓勵一下▼
本文分享自微信公衆號 - 白玉無冰(lamyoung-com)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。