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;
}
如有錯誤,請批評指正(*/ω╲*)