這個程序屬於上一篇文章的擴展吧,星球旋轉的那個程序主要是去了解它是怎麼旋轉的,很多函數是第一次見,學完之後可能有點懵。而且因爲建模的問題,畫圓的位置,視角位置,相機位置,設計的都不是太直觀,不太好觀察,並不便於理解函數的各個參數對呈現效果的影響。
所以這篇文章的程序會建出更容易理解和觀察的模型:在x和z軸上繪製一個大四邊形作爲陸地,在陸地上繪製36個雪人,他們可以作爲參照物。用戶可以通過鍵盤調整相機(人眼位置),使其向前向後移動,向左向右移動,向上向下移動,以及繞着y軸旋轉相機角度。
注意理解每種變換,是如何更改函數參數的,理解這個程序之後,一定會對gluLookAt()等函數有更好的理解,對視角有更好的理解。
學習要點
通過相機的變換呈現效果,以及相對應的gluLookAt()參數的變化去理解這個函數。
繪製場景的時候,多次用到glPushMatrix()和glPopMatrix()。
要點簡單解析
矩形入棧出棧
對於矩形的入棧出棧,可以先這樣理解,OpenGL在矩形所在的位置繪圖,如果想要在其他位置繪圖,需要對矩形做一個變換。然而繪製完之後,再次繪圖是以當前這個矩形位置爲基礎的,如果想回到原位置還要對它進行方向操作。那麼問題來了,如果對矩形的變換過多的話,還原起來就非常麻煩。所以解決辦法就是:把原矩形入棧保護起來,當矩形變換完畢後,再出棧就可以恢復到原先狀態。
視線變換思路
gluLookAt()函數使用的要點在於兩個點的座標,一個人眼點座標,一個看的點座標,所以需要正確設置這兩個點的座標。
下面簡單講一下本程序是如何設置這兩個點座標的。
設置xyz作爲相機位置座標,設置三個偏移量lx,ly,lz作爲視線方向(偏移量,大小爲0,0,-1),相機位置座標加入視線方向,就是看的點的座標。
旋轉相機可能不太好理解,我畫個圖幫忙理解下吧。
關於雪人是如何繪製的,這個有關具體繪製的操作,先不細究了吧,反正到時候貼圖可能更多一點,不過要是有興趣可以看看代碼咋畫的。這個代碼還是蠻值得學學的哈,場景漫遊我們回頭也得做。最後有啥不太理解的一定要問哈。
#include <gl/glut.h>
#include <math.h>
static float angle = 0.0;//angle繞y軸的旋轉角
static float x = 0.0f, y = 1.75f, z = 5.0f;//相機位置
static float lx = 0.0f, ly = 0.0f, lz = -1.0f;//視線方向,初始設爲沿着Z軸負方向
//定義觀察方式
void changeSize(int w, int h)
{
glMatrixMode(GL_PROJECTION); //投影變換
glLoadIdentity();
//設置視口爲整個窗口大小
glViewport(0, 0, w, h);
//設置可視空間
gluPerspective(45, 1.0f*w / h, 1, 1000); //角度45, 窗口縱橫比, 眼睛所及距離(近和遠)
glMatrixMode(GL_MODELVIEW); //模型變換
glLoadIdentity();
//相機(人眼)位置, 眼睛看的點(相機位置+視線方向), 觀察者本身方向(角度,比如正立)
gluLookAt(x, y, z, x + lx, y + ly, z + lz, 0.0f, 1.0f, 0.0f);
}
//繪製雪人
void drawSnowMan()
{
glColor3f(1.0f, 1.0f, 1.0f); //白色
//畫身體
glTranslatef(0.0f, 0.75f, 0.0f);
glutSolidSphere(0.75f, 20, 20); //畫圓
//畫頭
glTranslatef(0.0f,1.0f,0.0f);
glutSolidSphere(0.25f,20,20); //畫圓
//畫眼睛
glPushMatrix();
glColor3f(0.0f,0.0f,0.0f); //黑色
glTranslatef(0.05f,0.10f,0.18f);
glutSolidSphere(0.05f,10,10); //畫圓
glTranslatef(-0.1f,0.0f,0.0f);
glutSolidSphere(0.05f,10,10); //畫圓
glPopMatrix();
//畫鼻子
glColor3f(1.0f,0.5f,0.5f); //有點像粉色
glRotatef(0.0f,1.0f,0.0f,0.0f);
glutSolidCone(0.08f,0.5f,10,2); //畫圓錐 參數::半徑,高
}
//渲染場景,畫地面,畫雪人
void renderScene(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清空顏色和深度緩衝
//畫地面
glBegin(GL_QUADS); //四邊形
glVertex3f(-100.0f, 0.0f, -100.0f);
glVertex3f(-100.0f, 0.0f, 100.0f);
glVertex3f(100.0f, 0.0f, 100.0f);
glVertex3f(100.0f, 0.0f, -100.0f);
glEnd();
//畫36個雪人
for (int i= -3; i< 3; i++)
for (int j = -3; j< 3; j++) {
glPushMatrix(); //把當前矩形壓棧,這樣後面的操作不會影響到原矩形(標準位置/變換的矩形)
//問題:那現在操作的是哪個矩形呢??壓棧保存了原矩形,但是當前的矩形還是標準(原)矩形,可以操作
glTranslatef(i*10.0, 0, j*10.0);
drawSnowMan(); //繪製雪人
glPopMatrix(); //當把標準矩形移到了預期位置,再把原矩形彈棧恢復,給下次操作提供標準矩形
}
glutSwapBuffers(); //交換緩衝區
}
//旋轉相機,繞y軸旋轉
void orientMe(float ang)
{
lx = sin(ang);
lz = -cos(ang);
glLoadIdentity();
gluLookAt(x, y, z, x + lx, y + ly, z + lz, 0.0f, 1.0f, 0.0f);
//把所看的點(即視線方向上的點)理解爲在一個圓上旋轉,那設置的點的座標應該是旋轉的,通過圓半徑計算座標
//!!!畫圖好理解!!!
}
//前後移動相機
void move_Front_Back(int direction)
{
x = x + direction*(lx)*0.1;
z = z + direction*(lz)*0.1;
glLoadIdentity();
gluLookAt(x, y, z, x + lx, y + ly, z + lz, 0.0f, 1.0f, 0.0f);
//同時移動相機和所看的點座標,只用修改x和z,視線參數不用修改,
}
//左右移動相機,一定要畫圖理解
void move_Left_Right(int direction)
{
x = x + direction*(lz)*0.1;
z = z - direction*(lx)*0.1;
glLoadIdentity();
gluLookAt(x, y, z, x + lx, y + ly, z + lz, 0.0f, 1.0f, 0.0f);
}
void move_High_Low(int direction)
{
y = y + direction * 0.1;
glLoadIdentity();
gluLookAt(x, y, z, x + lx, y + ly, z + lz, 0.0f, 1.0f, 0.0f);
}
//鍵盤響應
void inputKey(unsigned char key, int x, int y) {
switch (key)
{
case 'q':
angle -= 0.03f;
orientMe(angle);
break;
case 'e': //q,e鍵調用相機旋轉
angle += 0.03f;
orientMe(angle);
break;
case 'w':
move_Front_Back(1);
break;
case 's':
move_Front_Back(-1);
break;
case 'a':
move_Left_Right(1);
break;
case 'd':
move_Left_Right(-1);
break; //wasd調整相機前後左右移動
case '1':
move_High_Low(1);
break;
case '2':
move_High_Low(-1);
break; //1,2鍵上下移動相機
default:
break;
}
}
int main(int argc,char **argv)
{
//初始化,建立窗口
glutInit(&argc, argv);
//深度緩衝,雙緩衝,顏色緩衝
glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);
glutInitWindowPosition(100, 100);
glutInitWindowSize(640, 360);
glutCreateWindow("snowman test");
glEnable(GL_DEPTH_TEST); //開啓深度緩衝區
glutKeyboardFunc(inputKey); //鍵盤響應事件
glutDisplayFunc(renderScene); //繪製回調函數
glutIdleFunc(renderScene); //閒置時回調函數
glutReshapeFunc(changeSize); //調整窗口大小回調函數
glutMainLoop();
return 0;
}
//有待深入學習,理解
//glClear()的清楚深度緩衝區啥意思
//具體的繪圖細節,需要搭建模型,具體設計
//關於矩形的彈出彈入
//changeSize的用法,窗口回調函數