1. 程式人生 > >opengl三維觀察

opengl三維觀察

1.      相機

產生目標場景檢視的變換過程類似於用照相機進行拍照。用照相機進行拍照的步驟大致如下:

·把照相機固定在三腳架上,並讓它對準場景(檢視變換);

·對場景進行安排,使各個物體在照片中的位置是我們所希望的(模型變換)

·選擇照相機鏡頭,並調整放大倍數(投影變換)

·確定最終照片的大小(視口變換)

完成這些步驟之後,就可以進行拍照(或者繪製場景)了。

2.      投影方式

openGL提供了兩種投影方式:正射投影和透視投影。

a)      正射投影(Orthographic Projection)

又叫平行投影。這種投影的視景體是一個矩形的平行管道,因此無論物體距離相機多遠,投影之後物體的大小尺寸不變。這也正是為什麼在正射投影模式下前後移動茶壺看不出來。此種模式下,不需要設定照相機位置、方向以及視點的位置,也就是說不需要gluLookAt函式。

正射投影函式void glOrtho(GLdoubleleft, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdoublefar)建立一個平行視景體,實際上這個函式的操作是建立一個正射投影矩陣,並且用這個矩陣乘以當前矩陣。其中近裁剪平面是一個矩陣,矩形左下角點三維空間座標是(left, bottom,-near),右上角點(right, top,-near);遠裁剪平面也是一個矩形,左下角點空間座標是(left, bottom,far),右上角點是(right, top,-far)。所有的near和far值同時為正或者同時為負,物體在視點後面時far和near都為正值。

b)      透視投影(Perspective Projection)

透視投影符合人的心理習慣,即離視點近的物體大,離視點遠的物體小,遠到極點即為消失,成為滅點。它的視景體類似於一個頂部和底部被切除掉的稜錐,也就是稜臺。

透視投影函式voidgluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar),建立一個對稱透視視景體。其操作是建立一個對稱的透視投影矩陣,並且用這個矩陣乘以當前矩陣。引數fovy定義視野在X-Z平面的角度,範圍是[0.0,180.0];引數aspect是投影平面寬度與高度的比率;引數zNear和Far分別是遠近裁剪面到眼睛的距離,它們總為正值。

3.      深度測試

深度其實就是畫素點在3D世界中距離攝像機的距離,深度快取中儲存著每個畫素點的深度之,深度值越大,則離攝像機越遠。

在不使用深度測試的時候,如果我們先繪製一個距離較近的物體,再繪製距離較遠的物體,則距離遠的物體因為後繪製,會把距離近的物體覆蓋掉,這不符合現實世界表現。而有了深度緩衝以後,繪製物體的順序就不那麼重要了,都能夠按照遠近順序進行現實。

啟用深度測試的程式碼如下:

glEnable(GL_DEPTH_TEST);//啟用深度測試,根據座標遠近自動隱藏被遮住的物體

4.      設定光源

沒有光照效果的物體看起來是二維的,沒有立體的感覺。通過新增光照,更容易表現三維的物體。

在openGL中,僅支援有限數量的光源。GL_LIGHT0~7f分別代表八個光源。使用glEnable函式可以開啟它們,例如:

glEnable(GL_LIGHTING);//啟用燈光

glLightfv函式具有三個引數,第一個引數指明設定哪一個光源的屬性,第二個引數指明設定該光源的哪個屬性,第三個引數則是指明把該屬性值設定成多少。GL_AMBIENT屬性表示該光源發出的光,經過非常多次的反射後,最終遺留在整個光照環境中的顏色。GL_POSITION屬性表示光源所在的位置。同樣適用glEnable函式開啟光源。

GLfloat white[] = { 1.0, 1.0, 1.0, 1.0 };

GLfloat light_pos[] = {5,5,5,1};

//Opengl中總共可以設定8個光源

glLightfv(GL_LIGHT0, GL_POSITION, light_pos);//設定0號光源的位置屬性

glLightfv(GL_LIGHT0, GL_AMBIENT, white);//設定0號光源的環境光屬性

glEnable(GL_LIGHT0);//啟用0號光源

4.      在控制整體進行上下左右移動的時候,我想到了幾種解決方案:

        i.           相機在世界座標系中右移,即eye[0]+=0.1f。

      ii.           物體在世界座標系中左移,即center[0]+=0.1f.

Figure 1 i操作方式

       在上圖所示的解決方案中,隨著視點右移,物體呈現出旋轉的效果,但是位置仍然位於整個視野中央。

Figure 2 ii操作方式

       在上圖所示的解決方案中,隨著物體的左移,它在視野中的位置的確也向左移動了,但是此時不再是正視,而是略微傾斜。

以上兩種解決辦法都沒有達到我想要的效果,於是我將二者進行結合,在改變物體位置的同時改變視點,效果如下圖:


main.cpp

#include <stdlib.h>
#include "glut.h"

float fTranslate;//整體平移因子
float fRotate = 0.0f;//整體旋轉因子
float tRotate = 0.0f;//茶壺旋轉因子

bool tAnim = false;//茶壺旋轉
bool bPersp = false;//渲染
bool bAnim = false;//整體旋轉
bool bWire = false;//填充、線框

int wHeight = 0;
int wWidth = 0;
float place[] = { 0,0,5 };

void Draw_Leg()
{
	glScalef(1, 1, 3);
	glutSolidCube(1.0);
}

void Draw_Scene()
{
	glPushMatrix();//當前矩陣壓棧
	glTranslatef(place[0],place[1],place[2]);//平移,放在桌面上的高度
	glRotatef(90, 1, 0, 0); //茶壺繞x軸旋轉的角度
	glRotatef(tRotate, 0, 1, 0);
	glutSolidTeapot(1);//size
	glPopMatrix();
	//繪製桌面
	glPushMatrix();
	glTranslatef(0, 0, 3.5);
	glScalef(5, 4, 1);
	glutSolidCube(1.0);
	glPopMatrix();
	//四條桌子腿
	glPushMatrix();
	glTranslatef(1.5, 1, 1.5);
	Draw_Leg();
	glPopMatrix();

	glPushMatrix();
	glTranslatef(-1.5, 1, 1.5);
	Draw_Leg();
	glPopMatrix();

	glPushMatrix();
	glTranslatef(1.5, -1, 1.5);
	Draw_Leg();
	glPopMatrix();

	glPushMatrix();
	glTranslatef(-1.5, -1, 1.5);
	Draw_Leg();
	glPopMatrix();
}

void updateView(int width, int height)
{
	glViewport(0, 0, width, height);						// Reset The Current Viewport

	glMatrixMode(GL_PROJECTION);						// Select The Projection Matrix
	glLoadIdentity();									// Reset The Projection Matrix

	float whRatio = (GLfloat)width / (GLfloat)height;

	if (bPersp)
		gluPerspective(45, whRatio, 1, 100);
	//透視模式下,物體近大遠小,引數分別為(視角,寬高比,近處,遠處)
	else
		glOrtho(-3, 3, -3, 3, -100, 100);

	glMatrixMode(GL_MODELVIEW);//對模型視景矩陣堆疊應用隨後的矩陣
}

void reshape(int width, int height)
{
	if (height==0)										// Prevent A Divide By Zero By
	{
		height=1;										// Making Height Equal One
	}

	wHeight = height;
	wWidth = width;
	updateView(wHeight, wWidth);
}

void idle()
{
	glutPostRedisplay();
}

float eye[] = { 0, 0, 8 };
float center[] = { 0, 0, 0 };


void key(unsigned char k, int x, int y)
{
	switch(k)
	{
	case 27://ESC
	case 'q': {exit(0); break; }//退出
	case 'p': {bPersp = !bPersp; updateView(wHeight, wWidth);break; }//切換投影方式

	case ' ': {bAnim = !bAnim; break;}//空格控制旋轉
	case 'o': {bWire = !bWire; break;}//o控制顯示模式:填充\線框

	case 'a': {//左移
		center[0] += 0.1f;
		eye[0] += 0.1f;
		break;
			  }
	case 'd': {//右移
		center[0] -= 0.1f;
		eye[0] -= 0.1f;
		break;
			  }
	case 'w': {//上移
		center[1] -= 0.1f;
		eye[1] -= 0.1f;
		break;
			  }
	case 's': {//下移
		center[1] += 0.1f;
		eye[1] += 0.1f;
		break;
			  }
	case 'z': {//前移
		center[2] += 0.1f;
		eye[2] += 0.1f;
		break;
			  }
	case 'c': {//後移
		center[2] -= 0.1f;
		eye[2] -= 0.1f;
		break;
			  }

			  //茶壺相關操作
	case 'l': {//右移
		place[0] += 0.1f;
		place[0] = (place[0] > 1.5f) ? 1.5f : place[0];
		break;
			  }
	case 'j': {//左移
		place[0] -= 0.1f;
		place[0] = (place[0] < -1.5f) ? -1.5f : place[0];
		break;
			  }
	case 'i': {//後移
		place[1] += 0.1f;
		place[1] = (place[1] > 1.5f) ? 1.5f : place[1];
		break;
			  }
	case 'k': {//前移
		place[1] -= 0.1f;
		place[1] = (place[1] < -1.5f) ? -1.5f : place[1];
		break;
			  }
	case 'e': {//旋轉茶壺
		tAnim = !tAnim;
		break;
			  }
	}
}


void redraw()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//清除顏色快取以及深度快取
	glLoadIdentity();//重置為單位矩陣
	//gluLookAt定義一個檢視矩陣,並與當前矩陣相乘
	gluLookAt(eye[0], eye[1], eye[2],
		center[0], center[1], center[2],
		0, 1, 0);				// 場景(0,0,0)的視點中心 (0,5,50),Y軸向上
	//三個陣列代表的分別是:相機在世界座標中的位置
	//					相機對準的物體在世界座標中的位置
	//					相機朝上的方向在世界座標中的位置
	if (bWire) 
		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);//線框模式
	else
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);//填充模式

	glEnable(GL_DEPTH_TEST);//啟用深度測試,根據座標遠近自動隱藏被遮住的物體
	glEnable(GL_LIGHTING);//啟用燈光
	GLfloat white[] = { 1.0, 1.0, 1.0, 1.0 };
	GLfloat light_pos[] = {5,5,5,1};
	//在Opengl中總共可以設定8個光源
	glLightfv(GL_LIGHT0, GL_POSITION, light_pos);//設定0號光源的位置屬性
	glLightfv(GL_LIGHT0, GL_AMBIENT, white);//設定0號光源的環境光屬性
	glEnable(GL_LIGHT0);//啟用0號光源

	//	glTranslatef(0.0f, 0.0f,-6.0f);			// Place the triangle at Center
	glRotatef(fRotate, 0, 1.0f, 0);			// Rotate around Y axis
	glRotatef(-90, 1, 0, 0);//繞x軸逆時針90
	glScalef(0.2, 0.2, 0.2);
	Draw_Scene();						// Draw Scene

	if (bAnim)
		fRotate += 0.5f;//旋轉
	if (tAnim)
		tRotate += 0.5f;
	
	glutSwapBuffers();//交換緩衝區
}

int main (int argc,  char *argv[])
{
	glutInit(&argc, argv);//對glut函式庫進行初始化
	glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE);//指定glutCreateWindow函式將要建立的視窗顯示模式 RGB 深度快取 雙快取模式
	glutInitWindowSize(480,480);//設定視窗大小
	int windowHandle = glutCreateWindow("Ex 3");//開啟設定好的視窗,進入glutMainLoop之前這個視窗不會顯示
	
	glutDisplayFunc(redraw);//指定當前視窗需要重新繪製時呼叫的函式
	glutReshapeFunc(reshape);	//當註冊視窗大小改變時回撥函式
	glutKeyboardFunc(key);//為當前視窗指定鍵盤迴調
	glutIdleFunc(idle);//可以執行連續動畫

	glutMainLoop();//進入glut時間處理迴圈,永遠不會返回
	return 0;
}

如有錯誤,請批評指正(*/ω╲*)