1. 程式人生 > >座標系、變換及緩衝區及操作矩陣的通用變換函式(openGL)

座標系、變換及緩衝區及操作矩陣的通用變換函式(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、每個物體的位置和方向
2、攝像機的位置和方向
3、世界中每一個點的地形是什麼
4、各物體從哪裡來、到哪裡去(NPC的運動策略)
物體座標系
物體座標系是和特定物體相關聯的座標系。每個物體都有它們獨立的座標系。當物體移動或改變方向時,和該物體相關聯的座標系將隨之移動或改變方向。
物體座標系中可能遇到的問題,如:
1、周圍有需要互相作用的物體嗎?(我要攻擊它嗎)
2、哪個方向?在我前面嗎?我左邊一點?右邊?(我應該向它射擊還是轉身就跑)
攝像機座標系
攝像機座標系是和觀察者密切相關的座標系。攝像機座標系和螢幕座標系相似,差別在於攝像機座標系處於三維空間中而螢幕座標系在二維平面裡。
1、物體是否在螢幕上
2、兩個物體,誰在前面
慣性座標系
為了簡化世界座標系到物體座標系的轉換,人們引入了一種新的座標系,成為慣性座標系。慣性座標系的原點在物體,但座標軸與世界座標系平行。
慣性座標系為了簡化世界座標系到物體座標系的轉換?:
1、物體座標系到慣性座標系只需要旋轉,慣性座標系到世界座標系只需要平移;分開考慮要比把這兩件事糅合在一起簡單。
座標系轉換
線代向量基之間的轉換,乘轉換矩陣(回家後把線性代數再複習一下)。

1.從繪圖使用的這個角度來看

世界座標:

世界座標系一開始以螢幕中心為原點(0, 0, 0)。你面對螢幕,你的右邊是x正軸,上面是y正軸,螢幕指向你的為z正軸。長度單位這樣來定: 視窗範圍按此單位恰好是(-1,-1)到(1,1)。

當前繪圖座標(物件 \ 建模座標):

當前繪圖座標系是 繪製物體時的座標系。程式剛初始化時,世界座標系和當前繪圖座標系是重合的。當用glTranslatef(),glScalef(), glRotatef()對當前繪圖座標系進行平移、伸縮、旋轉變換之後, 世界座標系和當前繪圖座標系不再重合。改變以後,再用glVertex3f()等繪圖函式繪圖時,都是在當前繪圖座標系進行繪圖,所有的函式引數也都是相 對當前繪圖座標系來講的。

2.變換(各個變換順序如下)

a.檢視變換(定義觀察者的位置和方向)

改變觀察者的位置和觀察方向。檢視變換可以理解為在繪製物體之前,先把模型變換應用於觀察者。的那個場景中防止更多的物體時,需要重新指定新的變換。所有其他的變換都是以最初的那個變換作為參考系的。

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變數指定了向上向量的方向。
該函式定義了視點矩陣,並用該矩陣乘以當前矩陣。
b.模型變換

模型變換:改變被觀察者的位置、形狀、大小,角度等。模型變換包括移動物體,旋轉物體,縮放物體。用到三個子函式:

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,並儲存到當前矩陣中去。