4 Transformation,Animation and Viewing
聲明:該代碼來自:Computer Graphics Through OpenGL From Theory to Experiments,僅用作學習參考
4.1 Modeling Transformations
平移、縮放和旋轉,即 OpenGL 的建模轉換,應用于物體以更改其位置和形狀。
Modeling Transformations控制世界坐標系中物體的位置、大小、旋轉
4.1.1 Translation
glFrustum(left, right, bottom, top, near, far)
請注意 OpenGL 的一個小怪癖,即 near 和 far 值在符號上顛倒。
給函數glFrustum輸入near,far為正數,實際上為 z = ? near , z = ? far \text{z}=-\text{near}, \text{z}=-\text{far} z=?near,z=?far
4.1.2 Scaling
the scaling command glScalef(u, v, w) maps each point (x,y,z) of an object to the point (ux vy wz). This has the e ect of stretching objects by a factor of u in the x-direction, v in the y-direction, and w in the z-direction
一個或多個縮放因子為零的縮放轉換稱為退化轉換。雖然并不常見,但偶爾會有一些應用程序會派上用場。
如果w為0那么,將3維物體退化為2維
4.1.3 Rotation
4.2 Composing Modeling Transformations
任務:想要對正方形繞它自己的中心逆時針旋轉45°,如 Figure 4.20(a)所示
如果直接使用glRotatef(45.0, 0.0, 0.0, 1.0)則會得到4.20(b)中的效果,即繞原點逆時針45°,而不是繞物體本身的中心旋轉45°
正確步驟應該是:
(3)glTranslatef(7.5, 7.5, 0.0); // Translate back
(2)glRotatef(45.0, 0.0, 0.0, 1.0); // Rotate about origin.
(1)glTranslatef(-7.5,-7.5, 0.0); // Translate to origin.
在 OpenGL 中,變換矩陣的操作是以堆棧的方式進行的,最新的變換操作會首先應用。也就是說,變換操作的執行順序是從最后一個變換開始,依次向前應用。
4.3 Placing Multiple Objects
在 OpenGL 中,所有的矩陣變換操作會累積在當前的模型視圖矩陣上,并且會影響后續的繪制操作。因此,在你的 drawScene 函數中,所有的變換操作會同時作用于立方體和球體。這些變換會作用于立方體和球體。為了確保球體的變換不受立方體的變換影響,你可以在繪制球體之前重置模型視圖矩陣例如使用glLoadIdentity(); 或者使用 glPushMatrix 和 glPopMatrix 來保存和恢復矩陣狀態。
在創建時,Object1 的局部坐標系與世界坐標系重合。
(1)如果變換 tn 是由函數 glTranslatef(p, q, r) 指定的 translation 變換
則物體所有頂點 V 相對于 object1 的局部坐標系的位置 ( a , b , c ) (a,b,c) (a,b,c) 不會改變。但V在世界坐標系中的位置變為了 ( a + p , b + q , c + r ) (a+p,b+q,c+r) (a+p,b+q,c+r)
(2) 如果變換 tn 是由函數 glRotatef(A, p, q, r) 指定的 rotation 變換
則物體所有頂點 V 相對于 object1 的局部坐標系的位置 ( a , b , c ) (a,b,c) (a,b,c) 不會改變
(3)如果變換 tn 是由函數 glScalef(u, v, w) 指定的 scaling 變換
V 在世界坐標中的位置通過縮放更改為 ( u a , v b , w c ) (ua,vb,wc) (ua,vb,wc)。但是,由于相同的縮放適用于 object1 的局部坐標系的軸,因此 V 相對于該系統的位置 ( a , b , c ) (a,b,c) (a,b,c) 不會再次改變。
例子:
淺藍色為世界坐標系、紅人和藍人有各自的局部坐標系(圖中只畫出了紅人的局部坐標系),對兩個人進行變換,可以看出兩人在世界坐標系中的坐標發生了變換,但是在他們各自的局部坐標系中均為發生變換
///
// relativePlacement.cpp
//
// This program shows composition of transformations and
// the relative placement of one object w.r.t another.
//
// Interaction:
// Press the up/down arrow keys to process code statements.
//
// Sumanta Guha.
///#define _USE_MATH_DEFINES #include <cmath>
#include <iostream>#include <GL/glew.h>
#include <GL/freeglut.h> // Globals.
static unsigned int base; // Display lists base index.
static int numVal = 0; // Step index.
static long font = (long)GLUT_BITMAP_8_BY_13; // Font selection.// Routine to draw a bitmap character string.
void writeBitmapString(void *font, char *string)
{char *c;for (c = string; *c != '\0'; c++) glutBitmapCharacter(font, *c);
}// Draw stick figure with a local co-ordinate system.
void drawMan(void)
{float angle;int i;glLineWidth(2.0);glBegin(GL_LINE_LOOP);for (i = 0; i < 20; ++i){angle = 2 * M_PI * i / 20;glVertex2f(0.0 + cos(angle) * 3.0, 7 + sin(angle) * 3.0);}glEnd();glBegin(GL_LINES);glVertex2f(0.0, 4.0);glVertex2f(0.0, -4.0);glVertex2f(0.0, -4.0);glVertex2f(6.0, -10.0);glVertex2f(0.0, -4.0);glVertex2f(-6.0, -10.0);glVertex2f(-6.0, 0.0);glVertex2f(6.0, 0.0);glEnd();glLineWidth(1.0);glRasterPos3f(0.0, 0.0, 0.0);writeBitmapString((void*)font, "O");glRasterPos3f(7.0, 0.0, 0.0);writeBitmapString((void*)font, "x");glRasterPos3f(-1.0, 6.0, 0.0);writeBitmapString((void*)font, "y");
}// Draw local co-ordinates grid.
void drawGrid(void)
{int i;glEnable(GL_LINE_STIPPLE); // Enable line stippling.glLineStipple(1, 0x00FF);glBegin(GL_LINES);for (i = -5; i < 6; ++i){glVertex2f(5 * i, 25.0);glVertex2f(5 * i, -25.0);}for (i = -5; i < 6; ++i){glVertex2f(25.0, 5 * i);glVertex2f(-25.0, 5 * i);}glEnd();glDisable(GL_LINE_STIPPLE); // Disable line stippling.
}// Draw and label world co-ordinate axes.
void drawWorldAxes(void)
{glColor3f(0.0, 1.0, 1.0);glBegin(GL_LINES);glVertex2f(-50.0, 0.0);glVertex2f(50.0, 0.0);glVertex2f(0.0, -50.0);glVertex2f(0.0, 50.0);glEnd();glRasterPos3f(48.0, -2.0, 0.0);writeBitmapString((void*)font, "x");glRasterPos3f(1.0, 48.0, 0.0);writeBitmapString((void*)font, "y");
}// Write fixed messages.
void writeFixedMessages(void)
{glColor3f(0.0, 0.0, 0.0);glRasterPos3f(-15.0, 43.0, 0.0);writeBitmapString((void*)font, "Press the up/down arrow keys!");glColor3f(0.8, 0.8, 0.8);glRasterPos3f(-44.0, -17.0, 0.0);writeBitmapString((void*)font, "glScalef(1.5, 0.75, 1.0);");glRasterPos3f(-44.0, -20.0, 0.0);writeBitmapString((void*)font, "glRotatef(30.0, 0.0, 0.0, 1.0);");glRasterPos3f(-44.0, -23.0, 0.0);writeBitmapString((void*)font, "glTranslatef(10.0, 0.0, 0.0);");glRasterPos3f(-44.0, -26.0, 0.0);writeBitmapString((void*)font, "drawRedMan; // Also draw grid in his local co-ordinate system.");glRasterPos3f(-44.0, -29.0, 0.0);writeBitmapString((void*)font, "glRotatef(45.0, 0.0, 0.0, 1.0);");glRasterPos3f(-44.0, -32.0, 0.0);writeBitmapString((void*)font, "glTranslatef(20.0, 0.0, 0.0);");glRasterPos3f(-44.0, -35.0, 0.0);writeBitmapString((void*)font, "drawBlueMan;");
}// Drawing routine.
void drawScene(void)
{glClear(GL_COLOR_BUFFER_BIT);glLoadIdentity();writeFixedMessages();drawWorldAxes();glColor3f(0.0, 0.0, 0.0);switch (numVal){case 0:goto step0;break;case 1:goto step1;break;case 2:goto step2;break;case 3:goto step3;break;case 4:goto step4;break;case 5:goto step5;break;case 6:goto step6;break;case 7:goto step7;break;default:break;}// Transformation steps.// Text drawing statements are enclosed within push/pop pairs// so that the raster position is w.r.t the identity transform.
step7:// Scale. glPushMatrix();glLoadIdentity();glRasterPos3f(-44.0, -17.0, 0.0);writeBitmapString((void*)font, "glScalef(1.5, 0.75, 1.0);");glPopMatrix();glScalef(1.5, 0.75, 1.0);step6:// Rotate. glPushMatrix();glLoadIdentity();glRasterPos3f(-44.0, -20.0, 0.0);writeBitmapString((void*)font, "glRotatef(30.0, 0.0, 0.0, 1.0);");glPopMatrix();glRotatef(30, 0.0, 0.0, 1.0);step5:// Translate.glPushMatrix();glLoadIdentity();glRasterPos3f(-44.0, -23.0, 0.0);writeBitmapString((void*)font, "glTranslatef(10.0, 0.0, 0.0);");glPopMatrix();glTranslatef(10.0, 0.0, 0.0);step4:// Draw red man.glPushMatrix();glLoadIdentity();glRasterPos3f(-44.0, -26.0, 0.0);writeBitmapString((void*)font, "drawRedMan; // Also draw grid in his local co-ordinate system.");glPopMatrix();glColor3f(1.0, 0.0, 0.0);glCallList(base);glCallList(base + 1);glColor3f(0.0, 0.0, 0.0);step3:// Rotate.glPushMatrix();glLoadIdentity();glRasterPos3f(-44.0, -29.0, 0.0);writeBitmapString((void*)font, "glRotatef(45.0, 0.0, 0.0, 1.0);");glPopMatrix();glRotatef(45, 0.0, 0.0, 1.0);step2:// Translate.glPushMatrix();glLoadIdentity();glRasterPos3f(-44.0, -32.0, 0.0);writeBitmapString((void*)font, "glTranslatef(20.0, 0.0, 0.0);");glPopMatrix();glTranslatef(20.0, 0.0, 0.0);step1:// Draw blue man.glPushMatrix();glLoadIdentity();glRasterPos3f(-44.0, -35.0, 0.0);writeBitmapString((void*)font, "drawBlueMan;");glPopMatrix();glColor3f(0.0, 0.0, 1.0);glCallList(base);step0:glFlush();
}// Initialization routine.
void setup(void)
{base = glGenLists(2);glNewList(base, GL_COMPILE);drawMan();glEndList();glNewList(base + 1, GL_COMPILE);drawGrid();glEndList();glClearColor(1.0, 1.0, 1.0, 0.0);
}// OpenGL window reshape routine.
void resize(int w, int h)
{glViewport(0, 0, w, h);glMatrixMode(GL_PROJECTION);glLoadIdentity();glOrtho(-50.0, 50.0, -50.0, 50.0, -1.0, 1.0);glMatrixMode(GL_MODELVIEW);glLoadIdentity();
}// Keyboard input processing routine.
void keyInput(unsigned char key, int x, int y)
{switch (key){case 27:exit(0);break;break;default:break;}
}// Callback routine for non-ASCII key entry.
void specialKeyInput(int key, int x, int y)
{if (key == GLUT_KEY_UP){if (numVal < 7) numVal++; else numVal = 0;}if (key == GLUT_KEY_DOWN){if (numVal > 0) numVal--; else numVal = 7;}glutPostRedisplay();
}// Routine to output interaction instructions to the C++ window.
void printInteraction(void)
{std::cout << "Interaction:" << std::endl;std::cout << "Press the up/down arrow keys to process code statements." << std::endl;
}// Main routine.
int main(int argc, char **argv)
{printInteraction();glutInit(&argc, argv);glutInitContextVersion(4, 3);glutInitContextProfile(GLUT_COMPATIBILITY_PROFILE);glutInitDisplayMode(GLUT_SINGLE | GLUT_RGBA);glutInitWindowSize(500, 500);glutInitWindowPosition(100, 100);glutCreateWindow("relativePlacement.cpp");glutDisplayFunc(drawScene);glutReshapeFunc(resize);glutKeyboardFunc(keyInput);glutSpecialFunc(specialKeyInput);glewExperimental = GL_TRUE;glewInit();setup();glutMainLoop();
}
4.4 Modelview Matrix Stack and Isolating Transformations
modelview 矩陣堆棧有助于將轉換應用于多個對象
我們描述的 modelview 矩陣是通過右側的乘法建模轉換來修改的,實際上是 modelview 矩陣堆棧中最上面的一個。這個特定的矩陣稱為當前 modelview 矩陣。事實上,OpenGL 維護了三個不同的矩陣堆棧:modelview、projection 和 texture。glMatrixMode(mode) 命令(其中 mode 為 GL_MODELVIEW、GL_PROJECTION 或 GL_TEXTURE)確定當前處于活動狀態的堆棧。
glPushMatrix() 命令的作用是在 modelview 矩陣堆棧中復制當前(即當前最頂層)矩陣,并將其放置在堆棧頂部;因此,在執行 glPushMatrix() 時,堆棧的兩個頂部矩陣立即相同。另一方面,glPopMatrix() 語句會刪除 modelview 矩陣堆棧的最頂層矩陣,以便下面的矩陣成為當前矩陣。
4.5 Animation
4.5.1 Animation Technicals
Controlling Animation
在 OpenGL 中,有三種簡單的方法可以控制動畫:
- 通過鍵盤或鼠標輸入以交互方式,借助其回調例程(callback routine)來調用轉換
- 用glutIdelFunc(idle_function)語句調用idle函數,idle 函數在沒有 OpenGL 事件時處于待處理狀態時調用。
- 通過指定一個常規定時器函數(稱為定時器函數),并調用 glutTimerFunc(period, 定時器函數, value) 即可實現半自動操作。定時器函數會在執行 glutTimerFunc() 語句后的 period 毫秒被調用,并且會將傳遞給它的參數值作為參數。
Double Buffer
顏色緩沖區是一個空間,通常位于 GPU 內存中,用于存儲光柵像素的 RGBA 值,通常每個 R、G、B 和 A 分別占用 8 位,總計每個像素 32 位。因此,顏色緩沖區保存著單幀的數據,并在該幀被繪制到顯示器時進行讀取。
在雙緩沖系統中,會提供兩個顏色緩沖區的空間,其中一個緩沖區(可查看緩沖區)保存當前顯示在顯示器上的幀,而下一幀則在第二個緩沖區(可繪制緩沖區)中繪制。當在可繪制緩沖區中繪制下一幀完成時,緩沖區會進行交換,這樣下一幀就變為可查看的,同時下一幀的繪制也開始。這個繪制和交換的循環在動畫過程中不斷重復。
可查看緩沖區(Viewable)通常被稱為前緩沖區或主緩沖區,而可繪制緩沖區(Drawable)則被稱為后緩沖區或交換緩沖區。任一緩沖區也被稱為刷新緩沖區。
雙緩沖極大地提高了動畫的質量,因為它能隱藏連續幀之間的過渡。而單緩沖則不然,觀眾會看到下一個幀在包含當前幀的同一個緩沖區中被繪制出來。其結果可能是令人不快的重影,之所以這樣稱呼,是因為在繪制下一個圖像時,之前的圖像仍會殘留。
運動的實現離不開物理,這一塊內容暫時不進行詳細了解
4.6 Viewing Transformation
管理相機位姿(相機在世界坐標系中的位置和朝向)
gluLookAt(eyex, eyey, eyez, centerx, centery, centerz, upx, upy, upz) ,注意這些坐標都是由世界坐標系描述的
模擬 OpenGL 相機第一次移動到位置 eye = (eyex, eyey, eyez),然后指向 center = (centerx, centery, centerz),并且圍繞其視線(line of sight,los)旋轉 連接eye到center的線,以便其向上方向是從 up = (upx, upy, upz)
將glTranslatef(0.0, 0.0, -15.0)替換為 gluLookAt(0.0, 0.0, 15.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0)
為什么glTranslatef(0.0, 0.0, -15.0)和 gluLookAt(0.0, 0.0, 15.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0) 渲染后效果是一樣的?
原因:使用函數glTranslatef(0.0, 0.0, -15.0)時的情況,相機和物體都在世界坐標系原點,將物體通過函數平移到了相機的視錐體內
使用函數 gluLookAt(0.0, 0.0, 15.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0) 時的情況,物體在世界坐標系原點,將相機平移到了(0,0,15)的位置,物體落到了平移后的相機視錐體內
僅修改相機的center,了解該參數的含義(相機鏡頭指向的點)
僅修改相機的up,了解該參數的含義(攝像機圍繞其視線(z 軸)旋轉,因此其向上方向每次都指向沿向上矢量 (upx, upy, upz))
upx=1,相機up方向朝向x軸正方形
相機從原來朝向基礎上向右旋轉90°
upy為-1,相機up方向朝y軸負方向
相機從原來朝向基礎上向左/右旋轉180°
4.6.1 Understanding the Viewing Transformation
基礎知識鋪墊
點積
點積的一個特別有用的應用是:當想要將給定的向量 v 拆分為 v = v1 +v2 時,其中分量 v1 和 v2 分別平行和垂直于另一個給定的非零向量 u。
將上述點積應用到計算相機的up direction上
將up向量分解為 up 1 \text{up}_1 up1?、 up 2 \text{up}_2 up2?、其中 up 1 \text{up}_1 up1?與視線los共線, up 2 \text{up}_2 up2?在平面p上,為相機的up direction
相機位置 eye = (eyex,eyey,eyez) \text{eye}=\text{(eyex,eyey,eyez)} eye=(eyex,eyey,eyez)
相機“看向” center = (centerx,centery,centerz) \text{center}=\text{(centerx,centery,centerz)} center=(centerx,centery,centerz)
相機up direction: up 2 = (upx,upy,upz) \text{up}_2=\text{(upx,upy,upz)} up2?=(upx,upy,upz)
例子:計算每個veiwing transformation的相機up direction
gluLookAt(eyex, eyey, eyez, centerx, centery, centerz, upx, upy, upz)
gluLookAt(0.0, 0.0, 5.0, 5.0, 0.0, 0.0, 0.0, 1.0, 1.0)
注意:Collectively, the modeling transformations glTranslatef(), glRot
atef() and glScalef() and the viewing transformation gluLookAt() are called modelview transformations.
4.6.2 Simulating a Viewing Transformation with Modeling Transformations
當我們引入 gluLookAt() 時,我們說它模擬了 OpenGL 相機的運動。這是完全正確的。OpenGL 相機永遠不會在原點處保留其默認姿勢,其鏡頭指向 -z 方向,頂部沿 +y 方向對齊。換句話說,視錐體(或框)保持在它第一次使用 glFrustum()(或 glOrtho())創建的位置。
也就是做第一幀相機坐標系原點和世界坐標系原點重合
實際上,通過將viewing transformation為等效的modeling transformations序列來模擬viewing transformation。
4.6.3 Orientation and Euler Angles
gluLookAt(eyex, eyey, eyez, centerx, centery, centerz, upx, upy, upz)
等價于
相機中心eye在世界坐標系中的坐標eye=(eyex,eyey,eyez),通過glTranslatef(-eyex,eyey,-eyez)將相機中心移到世界坐標系原點