電腦遊戲製作開發設計論壇 首頁 電腦遊戲製作開發設計論壇
任何可以在PC上跑的遊戲都可以討論,主要以遊戲之製作開發為主軸,希望讓台灣的遊戲人有個討論、交流、教學、經驗傳承的園地
 
 常見問題常見問題   搜尋搜尋   會員列表會員列表   會員群組會員群組   會員註冊會員註冊 
 個人資料個人資料   登入檢查您的私人訊息登入檢查您的私人訊息   登入登入 

Google
[轉貼自程式設計俱樂部]glut 教學 - 平衡環鎖(Gimbal Lock)

 
發表新主題   回覆主題    電腦遊戲製作開發設計論壇 首頁 -> 遊戲程式高級班:DirectX、OpenGL及各種圖型函式庫
上一篇主題 :: 下一篇主題  
發表人 內容
satanupup
喜歡上這裡的冒險者


註冊時間: 2007-05-29
文章: 80

68.10 果凍幣

發表發表於: 2007-6-28, PM 2:19 星期四    文章主題: [轉貼自程式設計俱樂部]glut 教學 - 平衡環鎖(Gimbal Lock) 引言回覆

作者 : ma_hty(白老鼠(Gary))
關於 三維空間旋轉 的問題, 總是重複的在各討論區出現, 重複的解答同一個問題, 好像不太符合成本效益, 就讓我在這一個教學, 一次過把它解決掉吧.

三維空間的旋轉 有 三個自由度 (degree of freedom), 要記錄三維空間的旋轉, 就算是沒啥數學概念的人, 也會想到使用三個 由 三條座標軸 量度出來的 角度 去記錄. 這種可說是最原始的 三維旋轉 代表方法, 也就是大家說的 Euler Angle.

以 Euler Angle 去代表旋轉, 當成是簡單的記錄是沒問題的, 但是, 使用 Euler Angle 去實作旋轉介面時, 就會發生某旋轉方向失效的情況, 這是因為三個獨立處理的旋轉角, 它們其中之一 使兩條座標軸重疊了. 這個情況, 我們都叫作 Gimbal Lock.

為了避免 Gimbal Lock 的出現, 人們就發展出 Axis Angle 的旋轉定義, 就是說, 旋轉 都是以 自轉軸 和 自轉角 去代表的, 使用 Axis Angle 去實作旋轉介面方法, 就是先訂出兩支 unit vector (v0, v1), 然後以 Axis Angle 找出它們的最少旋轉, 即
axis = normalize( cross(v0, v1) )
angle = acosf( v0,v1)
如此, Gimbal Lock 的問題就解決了.

雖然 Axis Angle 定義, 可以解決 Gimbal Lock 的問題, 但是, 如果我們想要在兩個 Axis Angle 旋轉 之間 插值 去做出它們之間的平滑變化, 你並不可以作單純的線性插值, 你會需要一番額外的運算, 才可以達到 插值 的功能. 又說, 如果你親手做過 Axis Angle 功能的話, 你就會發現當中常用的運算, 都包含很多類同的程序, 把它們 歸納 優化 之後, 就差不多跟我們常用的 四元數(Quaternion) 同一樣了. 你可以把 四元數 想像成 優化版 的 Axis Angle 定義. 但是, 我要強調一點, 實質的程序, 兩者沒多大差別的, 最大的分別, 大概就只是 四元數 的名號讓人看來遠不可及吧.

當然, 直接使用 Axis Angle 或 Quaternion 去實作旋轉介面 是最理想的做法, 但是, 初學者們一般也會覺得它們太複雜, 因而 望而卻步. 或是說, 如果純粹只是要避免 Gimbal Lock 的問題, 其實並不是必須使用 Axis Angle 或 Quaternion 的, 關鍵是在於避免使用三個獨立控制的旋轉而已, 以下一個範例只使用最基本的東西去避免 Gimbal Lock, 大概, 應該不會再嚇怕初學者的了.
-----------------------------------------------------------------
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <GL/glut.h>

float mo[16];

void init_mo();
void update_mo( float angle, float x, float y, float z );

void display();
void keyboard( unsigned char key, int x, int y );

void main( int argc, char **argv )
{

glutInitDisplayMode( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH );
glutInitWindowSize( 512, 512 );

glutCreateWindow( "gimbal_lock" );
glutDisplayFunc( display );
glutKeyboardFunc( keyboard );

init_mo();

glutMainLoop();
}

void display()
{
GLint viewport[4];
glGetIntegerv( GL_VIEWPORT, viewport );

glEnable( GL_DEPTH_TEST );
glEnable( GL_LIGHTING );
glEnable( GL_LIGHT0 );

glClearColor( .1, .2, .3, 1 );
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective( 45, double(viewport[2])/viewport[3], 0.1, 10 );

glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt( 0,0,3, 0,0,0, 0,1,0 );
glMultMatrixf(mo);

glutSolidTeapot(.7);

glutSwapBuffers();
}

void init_mo()
{
memset( mo, 0, sizeof(mo) );
mo[0]=mo[5]=mo[10]=mo[15]=1;
glutPostRedisplay();
}

void update_mo( float angle, float x, float y, float z )
{
glPushMatrix();
glLoadIdentity();
glRotatef( angle, x,y,z );
glMultMatrixf(mo);
glGetFloatv( GL_MODELVIEW_MATRIX, mo );
glPopMatrix();

glutPostRedisplay();
}

void keyboard( unsigned char key, int x, int y )
{
switch( key )
{
case 27:
init_mo();
break;
case '1':
update_mo( -5, 1,0,0 );
break;
case '2':
update_mo( 5, 1,0,0 );
break;

case '3':
update_mo( -5, 0,1,0 );
break;
case '4':
update_mo( 5, 0,1,0 );
break;

case '5':
update_mo( -5, 0,0,1 );
break;
case '6':
update_mo( 5, 0,0,1 );
break;
}
}
---------------------------------
這個範例, 只有一個旋轉定義

float mo[16];

就是因為只有一個旋轉定義, 因而不會出現 Gimbal Lock.

而每當我們需要改變旋轉的幅度, 我們都直接更新到 mo 去, 即

void update_mo( float angle, float x, float y, float z )
{
glPushMatrix();
glLoadIdentity();
glRotatef( angle, x,y,z );
glMultMatrixf(mo);
glGetFloatv( GL_MODELVIEW_MATRIX, mo );
glPopMatrix();

glutPostRedisplay();
}
--------------------------------
void display()
{
//...
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt( 0,0,3, 0,0,0, 0,1,0 );

// 所得出的總合旋轉, 在繪圖時引用
glMultMatrixf(mo);

// ...
}
----------------------------------
這個程式,
按下 1 鍵 或 2 鍵 時, 就會繞著 X軸 反向 或 正向 的旋轉
按下 3 鍵 或 4 鍵 時, 就會繞著 Y軸 反向 或 正向 的旋轉
按下 5 鍵 或 6 鍵 時, 就會繞著 Z軸 反向 或 正向 的旋轉
按下 Esc 鍵, 就會重置旋轉
-------------------------------------
也做一點功課吧, 上述的範例, 是一個沒有 Gimbal Lock 問題的旋轉介面, 請你, 利用 Euler Angle 定義, 做出有 Gimbal Lock 問題的旋轉介面, 然後把兩者比較一下呀.

提示:
要寫 Euler Angle 旋轉介面, 你會需要三個角度
float ax, ay, az;

按鍵時 增/減 ax, ay, az,

繪畫時, 呼叫

void display()
{
//...
glRotatef( ax, 1,0,0 );
glRotatef( ay, 0,1,0 );
glRotatef( az, 0,0,1);
//...
}

------------------------------
最後... 如果你能夠完成這個教學, 請簽個名呀. ^^
回頂端
檢視會員個人資料 發送私人訊息
從之前的文章開始顯示:   
發表新主題   回覆主題    電腦遊戲製作開發設計論壇 首頁 -> 遊戲程式高級班:DirectX、OpenGL及各種圖型函式庫 所有的時間均為 台灣時間 (GMT + 8 小時)
1頁(共1頁)

 
前往:  
無法 在這個版面發表文章
無法 在這個版面回覆文章
無法 在這個版面編輯文章
無法 在這個版面刪除文章
無法 在這個版面進行投票
可以 在這個版面附加檔案
可以 在這個版面下載檔案


Powered by phpBB © 2001, 2005 phpBB Group
正體中文語系由 phpbb-tw 維護製作