座標系、變換及緩衝區及操作矩陣的通用變換函式(openGL)
之前一直糾結於座標系與變換呼叫的順序,但到目前為止:
a.使用到的只是一個全域性座標和繪圖座標
b.會呼叫變換就行,無關順序
0.物件的點在出現在螢幕的過程中:各種變換呼叫順序和各種座標系出現順序
物件 \ 建模座標系下,轉化到標準的世界座標系下(方便轉換到眼(視點)座標),再把標準的世界座標系轉換為視點座標(因為要觀察物件,對於(觀察者)照相機而言不是所有物件都能觀察(拍)到)。如果一個物件位於視見體外,那麼就在光柵化之前把它從場景裡裁剪掉。為了更有效率的裁剪,OpenGL先執行投影變換,這個變換把所有可能是可見的物件變換到一個立方體中,該立方體的中心是裁剪座標系(clip coordinate)的原點。在投影變換之後,頂點的表示仍然是齊次座標。頂點的齊次座標表示再經過透視除法(perspective division),即用w分量去除其他分量,就得到了規範化的裝置座標系(normalized device coordinate)下的三維表示。最後一步變換根據視口提供的資訊,把規範化的裝置座標系下的表示變換為視窗座標系(window coordinate)下的三維表示。視窗座標系是用顯示器上的畫素來度量的,但仍然保留了深度資訊。如果去掉深度座標,就得到了二維的螢幕座標(screen coordinate)。
總結:
1、目標:將物件展現在觀察者的視線內,需要將點的物件建模座標轉化成視點座標(物件建模座標->世界座標->視點座標。opengl把這個過程弄成了一個,模視矩陣);
2、轉換成視點座標後:由於有些點的視點座標在觀察者的視口之外,所以需要裁剪掉。為了裁剪效率,需要用投影變換將視點座標轉化成裁剪座標;
3、裁剪完成之後,就需要將三維的裁剪座標轉化成螢幕上的視窗座標展現在視窗中(裁剪座標->標準化裝置座標->視窗座標);
總結2:
為了把一個物體的三維座標轉換成螢幕上的畫素座標,需要完成如下三步:
1、變換:包括模型、檢視和投影操作,它們是由矩陣乘法表示的。這些操作包括旋轉、移動、縮放、反射、正投影和透視投影
2、由於場景是在一個矩陣視窗中渲染的,因此位於視窗之外的物體必須才裁剪掉
3、最後,經過變換的座標和螢幕畫素之間必須建立對應關係。這個過程叫視口變換
opengl中存在的六種座標系
1)object or model coordinate(物件 \ 建模座標(空間))(繪圖過程中輸入,建模變換有用)
2)world coordinate(世界座標(空間))(標準座標,對於多個物件而言,計算機有個統一的標準)
3)eye coordinate(眼座標(空間))(視覺座標相對於觀察者的視角而言,可以視為絕對的螢幕座標,通常作為參考座標系使用)
4)clip coordinate(裁剪座標(空間))(應用程式設計師不涉及)
5)normalized device coordinate(裝置座標(空間))(應用程式設計師不涉及)6)window coordinate(螢幕座標(空間))(應用程式設計師不涉及)
3D數學基礎書中對一些常用座標系的功用介紹
1.從繪圖使用的這個角度來看世界座標系
它建立了描述其他座標系所需要的參考框架。從另一方面來說,能夠用世界座標系描述其他座標的位置。
世界座標系的典型問題都是關於初始位置和環境的,如:
1、每個物體的位置和方向
2、攝像機的位置和方向
3、世界中每一個點的地形是什麼
4、各物體從哪裡來、到哪裡去(NPC的運動策略)
物體座標系
物體座標系是和特定物體相關聯的座標系。每個物體都有它們獨立的座標系。當物體移動或改變方向時,和該物體相關聯的座標系將隨之移動或改變方向。
物體座標系中可能遇到的問題,如:
1、周圍有需要互相作用的物體嗎?(我要攻擊它嗎)
2、哪個方向?在我前面嗎?我左邊一點?右邊?(我應該向它射擊還是轉身就跑)
攝像機座標系
攝像機座標系是和觀察者密切相關的座標系。攝像機座標系和螢幕座標系相似,差別在於攝像機座標系處於三維空間中而螢幕座標系在二維平面裡。
1、物體是否在螢幕上
2、兩個物體,誰在前面
慣性座標系
為了簡化世界座標系到物體座標系的轉換,人們引入了一種新的座標系,成為慣性座標系。慣性座標系的原點在物體,但座標軸與世界座標系平行。
慣性座標系為了簡化世界座標系到物體座標系的轉換?:
1、物體座標系到慣性座標系只需要旋轉,慣性座標系到世界座標系只需要平移;分開考慮要比把這兩件事糅合在一起簡單。
座標系轉換
線代向量基之間的轉換,乘轉換矩陣(回家後把線性代數再複習一下)。
世界座標:
世界座標系一開始以螢幕中心為原點(0, 0, 0)。你面對螢幕,你的右邊是x正軸,上面是y正軸,螢幕指向你的為z正軸。長度單位這樣來定: 視窗範圍按此單位恰好是(-1,-1)到(1,1)。
當前繪圖座標(物件 \ 建模座標):
2.變換(各個變換順序如下)當前繪圖座標系是 繪製物體時的座標系。程式剛初始化時,世界座標系和當前繪圖座標系是重合的。當用glTranslatef(),glScalef(), glRotatef()對當前繪圖座標系進行平移、伸縮、旋轉變換之後, 世界座標系和當前繪圖座標系不再重合。改變以後,再用glVertex3f()等繪圖函式繪圖時,都是在當前繪圖座標系進行繪圖,所有的函式引數也都是相 對當前繪圖座標系來講的。
a.檢視變換(定義觀察者的位置和方向)
b.模型變換改變觀察者的位置和觀察方向。檢視變換可以理解為在繪製物體之前,先把模型變換應用於觀察者。的那個場景中防止更多的物體時,需要重新指定新的變換。所有其他的變換都是以最初的那個變換作為參考系的。
該函式定義了視點矩陣,並用該矩陣乘以當前矩陣。void gluLookAt(GLdouble eyex,GLdouble eyey,GLdouble eyez,GLdouble centerx,GLdouble centery, GLdouble centerz ,GLdouble upx,GLdouble upy,GLdouble upz);//eyex,eyey,eyez定義了視點的位置;centerx、centery和centerz變數指定了參考點的位置;upx、upy、upz變數指定了向上向量的方向。
模型變換:改變被觀察者的位置、形狀、大小,角度等。模型變換包括移動物體,旋轉物體,縮放物體。用到三個子函式:
glTranslate*(x, y, z)
glRotate*(x, y, z)
每個函式都會產生一個矩陣,並右乘當前矩陣。glScale*(x, y, z)
a~b(模型檢視的對偶性):不改變模型(被觀察者的角度),改變檢視(觀察者的角度);和改變檢視,不改變模型;或者都改變;都能達到相同的效果。
glMatrixMode(GL_MODELVIEW); glLoadIdentity();
c.投影變換(為了更效率的裁剪視口外的物件)(glFrustum、glOrtho)
投影變換定義了可視區域(視景體)和裁剪平面。裁剪面決定了幾何圖元是否能被觀察者看到。投影面決定了在所有變換做完之後的場景投影到螢幕的最終影象。投影變換有兩種:正交投影和透視投影。
c.1正射投影
平行投影,所有多邊形按指定大小出現在螢幕上。不管物體有多遠,都按照相同的比例大小繪製。通常用於CAD和二維影象渲染。
正射投影的兩個函式:
void glOrtho(GLdouble left,GLdouble right,GLdouble bottom,GLdouble top, GLdouble near,GLdouble far) void gluOrtho2D(GLdouble left,GLdouble right,GLdouble bottom,GLdouble top)
c.2透視投影
顯示的場景更加逼真。遠處的物體看上去比相同的大小的近處物體小一些。
透視投影的兩個函式:
void glFrustum(GLdouble left,GLdouble Right,GLdouble bottom,GLdouble top,GLdouble near,GLdouble far); void gluPerspective(GLdouble fovy,GLdouble aspect,GLdouble zNear, GLdouble zFar);
c.3裁剪平面
除了視景體定義的六個裁剪平面(上、下、左、右、前、後)外,使用者還可自己再定義一個或多個附加裁剪平面,以去掉場景中無關的目標。
附加平面裁剪函式:
void glClipPlane(GLenum plane,Const GLdouble *equation);
投影變換操作時:
glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(0.0,(GLdouble)w,0.0,(GLdouble)h);
d.視口變換
在所有變換進行完成之後,獲得的是將被對映到螢幕上的某個視窗的二維投影。這種對映到物理視窗座標的變換是最後做的變換——視口變換。上述變換後得到一個場景的二位投影,用視口變換對映到螢幕某處的物理視窗上。這裡其實是硬體的事情,opengl的事情只到投影變換,一個場景就算繪製完成了。
glViewport(0, 0, w, h);//所謂視口變換就是規定螢幕上顯示場景的範圍和尺寸
e.矩陣棧
有時我們需要儲存當前的變換狀態,然後繪製物體,再恢復它。那我們可以使用OpenGL提供的矩陣棧來實現。glPushMatrix可以儲存當前矩陣到矩陣棧中,glPopMatrix從矩陣棧彈出矩陣。
f.綜合應用
void display() { glClear(GL_COLOR_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); gluLookAt(1.0,1.0,1.0,0.0,0.0,0.0,0.0,1.0,0.0);//檢視變換 glutWireCube(0.5); glutSwapBuffers(); } void reshape(int w,int h) { glViewport(0,0,w,h);//視口設定 glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(-4.0,4.0,-4.0,4.0,-4.0,4.0);//投影變換 } void init() { glClearColor(1.0,1.0,1.0,1.0); glColor3f(0.0,0.0,0.0); } int main(int argc,char** argv) { glutInit(&argc,argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB); glutInitWindowSize(500,500); glutInitWindowPosition(0,0); glutCreateWindow("cube"); glutReshapeFunc(reshape); glutDisplayFunc(display); init(); glutMainLoop(); }
該例子來自於此:http://my.oschina.net/sweetdark/blog/163305#OSC_h3_1
#include"grapg.h" static GLfloat whitelight[]={0.2f,0.2f,0.2f,1.0f}; static GLfloat sourcelight[] = {0.8f, 0.8f, 0.8f, 1.0f}; static GLfloat lightpos[] = {0.0f, 0.0f, 0.0f, 1.0f}; //所有glEnable都是開啟了某個效果,但使不使用,還要看後面相應的函式有沒有被呼叫 void setupRC() { glEnable(GL_DEPTH_TEST); //開啟深度測試 glFrontFace(GL_CCW); //逆時針設定為正面 /************************************************************************** 1、glEnable(GL_CULL_FACE);開啟剔除效果,但前、後並沒有呼叫void glCullFace 來剔除正面或者反面 2、剔除永遠都看不到的面,用以減少不必要的渲染,計算等 **************************************************************************/ glEnable(GL_CULL_FACE); //啟用裁剪面 /*******************************光照設定*********************************** 1、glEnable(GL_LIGHTING);開啟光照效果 2、glLightModelfv設定光照模型 3、glLightfv設定光照的顏色,位置,編號等 4、glEnable(GL_LIGHT0);開啟相應識別符號的光源 5、glMaterialfv或glColorMaterial設定材質 **************************************************************************/ glEnable(GL_LIGHTING); //開啟光照效果 glLightModelfv(GL_AMBIENT_AND_DIFFUSE, whitelight); //設定光照引數 /************************************************************************** 建立光源 1、glLightfv(GLenum light,GLenum pname,TYPE param) light:所建立的光源標識號。GL_LIGHT0 ,GL_LIGHT1 ,GL_LIGHT2 ... ... pname:指定光源特性 GL_AMBIENT:環境光的顏色 GL_POSITION:光源位置座標 param:引數 **************************************************************************/ glLightfv(GL_LIGHT0, GL_AMBIENT, sourcelight); glLightfv(GL_LIGHT0, GL_POSITION, lightpos); glEnable(GL_LIGHT0); //開啟先前定義的光源標號為LIGHT0的光源們 glEnable(GL_COLOR_MATERIAL); //設定材料屬性 /************************************************************************** 1、除了採用glMaterialfv()設定材質外,還可以使用glColorMaterial()設定材質屬性 glColorMaterial()對材質的設定是實時的,在繪圖時使用glColor*()來實時改變材質顏色, 或用glMaterial()來實時改變材質成分 2、void glColorMaterial(GLenum face,GLenum mode) face:哪一面需要設定材質 mode:設定成什麼樣的模式 **************************************************************************/ glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE); glClearColor(0.0f, 0.0f, 0.0f, 1.0f); } void alterbantion_000() { static GLfloat fEarth = 0.0f; static GLfloat fMoon = 0.0f; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); //模視變換 glPushMatrix(); //畫日 glDisable(GL_LIGHTING); //先關閉光照 glTranslatef(0.0f, 0.0f, -300.0f); //平移操作,往螢幕裡面平移 //畫日 glColor3ub(255, 255, 0); //設定日的顏色 glutSolidSphere(20.0, 15, 15); //畫日,引數分別為:半徑,經線條數,緯線條數 glEnable(GL_LIGHTING); //開啟光照 glLightfv(GL_LIGHT0, GL_POSITION, lightpos); //LIGHT0的發光位置,上面不是設定過了麼??? //畫地球 glRotatef(fEarth, 0.0f, 1.0f, 0.0f); //設定繞z軸旋轉 glColor3ub(0,0,255); glTranslatef(105.0f, 0.0f, 0.0f); //平移出z軸,因為畫球是預設球心是原點 glutSolidSphere(15.0, 15, 15); //球心在原點 //畫月球 glRotatef(fMoon, 0.0f, 1.0f, 0.0f); //設定 glColor3ub(200, 200, 200); glTranslatef(30.0f, 0.0f, 0.0f); glutSolidSphere(10.0, 15, 15); fMoon += 15.0f; //月球的旋轉 if (fMoon > 360.0f) { fMoon = 0.0f; } fEarth += 10.0f; //地球的旋轉 if (fEarth > 360.0f) { fEarth = 0.0f; } glPopMatrix(); //矩陣棧 glutSwapBuffers(); } void reshape_alterbantion_000(GLsizei w, GLsizei h) { if (h == 0) { h = 1; } glViewport(0, 0, w, h); //設定視口 glMatrixMode(GL_PROJECTION); //開啟投影矩陣,設定投影和可視區域 glLoadIdentity(); GLfloat aspect = (GLfloat)w / (GLfloat)h; //設定可視區域 gluPerspective(45.0, aspect, 1.0, 400.0); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } void TimerFunc(int value) { glutPostRedisplay(); glutTimerFunc(100, TimerFunc, 1); } int main_alteration() { glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH); glutInitWindowSize(500,500); glutCreateWindow("test"); glutReshapeFunc(reshape_alterbantion_000); glutTimerFunc(100, TimerFunc, 1); glutDisplayFunc(alterbantion_000); setupRC(); glutMainLoop(); return 0; }
3.緩衝區
a.目標緩衝區
opengl並不是直接在螢幕上繪製圖元,而是先渲染到緩衝區中,然後再交換到螢幕上來。
顏色緩衝區有兩個,一個是前顏色緩衝區,一個是後顏色緩衝區。雙緩衝區時,預設是現在後緩衝區中繪製圖元,然後再用glutSwapBuffers交換到前緩衝區來。
在使用單緩衝區的時候,是用glFlush()來將繪製結果繪製到螢幕上的。
單緩衝區,定時繪製使用:
繪製函式
void colorbuffer() { static GLdouble radius=5; //需要設定成靜態變數,不然下次呼叫時還是5 static GLdouble angle=0.0; if(angle==0.0) { glClear(GL_COLOR_BUFFER_BIT); } glColor3f(0.0f,1.0f,0.0f); glBegin(GL_POINTS); glVertex2f(radius * cos(angle), radius * sin(angle)); glEnd(); radius *= 1.01; angle += 0.1; if (angle > 30.0) { radius = 5; angle = 0.0; } glFlush(); //glFlush用在單緩衝 }
定時器
void timer(int value) //定時器:value區別是哪一個定時器 { glutPostRedisplay(); glutTimerFunc(50,timer,0); //引數:毫秒數、回撥函式指標、區別值 //標記需要重新繪製 }
視窗管理
void reshape_000(int w,int h) { GLfloat nRange = 100.0f; if (h == 0) { h = 1; } glViewport(0, 0, w, h); glMatrixMode(GL_PROJECTION); glLoadIdentity(); GLfloat aspect = (GLfloat)w/(GLfloat)h; if (w<=h) { glOrtho(-nRange, nRange, -nRange/aspect, nRange/aspect, -nRange, nRange); } else { glOrtho(-nRange*aspect, nRange*aspect, -nRange, nRange, -nRange, nRange); } glMatrixMode(GL_MODELVIEW); glLoadIdentity(); }
main
int main(int argc,char ** argv) { glutInit(&argc,argv); glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE); glutInitWindowSize(500,500); glutInitWindowPosition(100,100); glutCreateWindow("test"); glutDisplayFunc(colorbuffer); glutReshapeFunc(reshape_000); glutTimerFunc(50,timer,0); glutMainLoop(); return 0; }
b.深度緩衝區
z值。開啟時只需glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);打上GLUT_DEPTH標記就行
c.使用剪刀裁剪
裁剪的時候似乎沒有使用者座標系,世界座標系那麼一說,預設螢幕左下角為(0,0)、螢幕寬高為裁剪最大寬高
void scissor() { glEnable(GL_SCISSOR_TEST); //啟用裁剪功能 glClear(GL_COLOR_BUFFER_BIT); glScissor(0, 0, 400, 400); //引數為左下角x座標,左下角y座標,width寬,height高 //設定裁剪框 glClearColor(0.0f, 1.0f, 0.0f,1.0f); glClear(GL_COLOR_BUFFER_BIT); //清除顏色緩衝區 glScissor(-25.0f, -25.0f, 55, 55); glClearColor(1.0f, 1.0f, 0.0f,1.0f); glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_SCISSOR_TEST); glFlush(); }
從結果看reshape_000並沒有起到作用
d.模板緩衝區使用暫待
4.通用的變換函式
a.opengl的矩陣
前三列是在視覺座標系下,物件座標系x,y,z在視覺座標系中的表示,最後一列用於平移變換。
b.矩陣的載入和變換
glLoadMatrix*();:用於將矩陣載入到模型檢視矩陣、投影矩陣或者紋理矩陣棧中。
glMultMatrix*(M);:當前矩陣C*指定矩陣M,並儲存到當前矩陣中去。