opengl學習筆記④——繞啊繞的行星系統(旋轉,光照)
月亮繞著地球轉,地球繞著太陽轉。多層旋轉關係在opengl裡是個啥玩意?讓我們來看一看。
在opengl中,我們的各種操作會最終產生一個矩陣,矩陣與代表頂點的向量相乘得到最終的頂點資訊。不過有趣的是,如果我們依次寫下操作1,2,對應矩陣A,B,如果頂點的列向量是V,則變換的結果V'=ABV。可以看到的是,後定義的B操作先應用在了V上。這是因為opengl內的矩陣是右乘的(具體問度娘)。
我不是很喜歡這種反著看的方法。所以我推薦把這些操作看成對座標系的操作,那就正常了。對於上面的例子,就可以認為是先把座標系做A操作再做B操作(因為原點和繪製物體的頂點是對立的關係,很有意思)。有點抽象,我們上具體例子。
要繪製一個簡單的日地月模型。我們有了一個基本的思路,先在原點畫太陽,再把座標系旋轉一定角度,再沿著某個方向平移,畫個地球,再旋轉座標系,然後平移,繪製月亮。程式碼如下:
glutSolidSphere(50, 100, 100);//太陽
glRotatef(clock()/10, 0, 0, 1);
glTranslated(300, 0, 0);
glutSolidSphere(30, 100, 100);//地球
glRotated(clock()/10, 0, 0, 1);
glTranslated(-100, 0, 0);
glutSolidSphere(10, 30, 30);//月亮
我們如果按照對頂點的操作來看,由上到下,平移操作產生的矩陣定義為T1,T2,旋轉的為R1,R2。我們來看看月亮經歷了什麼。
R1T1R2T2:沿X移動-100,旋轉,沿著X移動300,旋轉。還是能夠理解的,但是和平常的習慣不一樣,所以還是推薦看成對座標系的操作。
但是這樣弄出來的不過是三個圓在轉,所以我們在太陽處新增額外光照,但是要設定太陽變成發光物體的話,我們需要對太陽的材質的GL_EMISSION屬性做出修改
glMaterialfv(GL_FRONT, GL_EMISSION, sun);
sun是一個數組,代表發光物體的RBGA。
這樣太陽就看起來像是發光了,但是實際上我們設定在太陽的點光源對太陽發光沒任何貢獻。也不會出現太陽會過濾點光源的光,使它變成太陽紅。所以我們對點光源的顏色,地球,月亮的材質做出設定,並且再在相機位置繫結一個微弱光源,這樣我們看行星背部也不會太黑。這樣這個行星系統看起來有點樣子。於是更新上面的程式碼如下
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glLightfv(GL_LIGHT1, GL_POSITION, l_position);
gluLookAt(b, c, a,
0, 0, 0,
0, 1, 0);
glLightfv(GL_LIGHT0, GL_POSITION, l_position);
glMaterialfv(GL_FRONT, GL_COLOR_INDEXES, color_index);
glMaterialfv(GL_FRONT, GL_EMISSION, sun);
glutSolidSphere(50, 100, 100);
glMaterialfv(GL_FRONT, GL_EMISSION, re_set);
glMaterialfv(GL_FRONT, GL_DIFFUSE, earth);
glRotatef(clock() / 10, 0, 0, 1);
glTranslated(300, 0, 0);
glutSolidSphere(30, 100, 100);
glRotated(clock() / 10, 0, 0, 1);
glTranslated(-100, 0, 0);
glMaterialfv(GL_FRONT, GL_DIFFUSE, moon);
glutSolidSphere(10, 30, 30);
收工。原始碼如下,我們還是繼續使用了上一次裡的一些使用者互動操作函式。
#include<iostream>
#include<fstream>
#include<GL/glew.h>//使用glew庫使用VBO
#include <GL/glut.h>
#include<cmath>
#include<ctime>
#include<vector>
#include<string>
#define BUFFER_OFFSET(bytes) ((GLubyte*)NULL+bytes)
#define T 100
#pragma comment(lib, "glew32.lib")//加個glew使用VBO,嘖
using namespace std;
GLfloat l_position[4] = { 0,0,0,1 };
GLfloat r_x1, r_y1, r_x2, r_y2;//滑鼠拖動引數
GLint tag = 2;//初始表示畫多邊形
GLfloat mov_x1, mov_y1, mov_x2, mov_y2;//平移變數
GLfloat trans_x, trans_y;
GLfloat a, b, c, d, e, f;
GLint flag;//0表示滑鼠左鍵按下,1表示中鍵按下
GLfloat sun[] = { 1,0.2,0.2,1 };
GLfloat sun_light[] = { 1,0.8,0.8,1 };
GLfloat re_set[] = { 0,0,0.0,0 };
GLfloat earth[] = { 0,0.6,1,1 };
GLfloat earth_[] = { 0,0,0.1,0 };
GLfloat color_index[] = { 1,1,1 };
GLfloat moon[] = { 0.8,0.8,0.8,1 };
GLfloat light_1[] = { 0.2,0.2,0.2,1 };
void init(void)
{
glShadeModel(GL_SMOOTH);
glEnable(GL_DEPTH_TEST);
glEnable(GLUT_MULTISAMPLE);
glEnable(GL_LIGHTING);
glLightfv(GL_LIGHT0, GL_DIFFUSE, sun_light);
glLightfv(GL_LIGHT1, GL_DIFFUSE, light_1);
glEnable(GL_LIGHT0);
glEnable(GL_LIGHT1);
glMatrixMode(GL_PROJECTION);//設定投影矩陣
glLoadIdentity();
gluPerspective(60, 1, 0.1, 100000);
glClearColor(0, 0, 0, 1);
a = 500;
b = 20.0;
c = 50.0;
d = e = f = 0;
trans_x = trans_y = 0;
r_x1 = r_y1 = r_x2 = r_y2 = 0;
mov_x1 = mov_y1 = mov_x2 = mov_y2 = 0;
}
void timerFunc(int value)
{
glutPostRedisplay();
glutTimerFunc(10, timerFunc, 1);
}
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear Screen And Depth Buffer
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glLightfv(GL_LIGHT1, GL_POSITION, l_position);
gluLookAt(b, c, a,
0, 0, 0,
0, 1, 0);
glLightfv(GL_LIGHT0, GL_POSITION, l_position);
glMaterialfv(GL_FRONT, GL_COLOR_INDEXES, color_index);
glMaterialfv(GL_FRONT, GL_EMISSION, sun);
glutSolidSphere(50, 100, 100);
glMaterialfv(GL_FRONT, GL_EMISSION, re_set);
glMaterialfv(GL_FRONT, GL_DIFFUSE, earth);
glRotatef(clock() / 10, 0, 0, 1);
glTranslated(300, 0, 0);
glutSolidSphere(30, 100, 100);
glRotated(clock() / 10, 0, 0, 1);
glTranslated(-100, 0, 0);
glMaterialfv(GL_FRONT, GL_DIFFUSE, moon);
glutSolidSphere(10, 30, 30);
glutSwapBuffers();
}
void s_input(int key, int x, int y)//方向鍵改變相機Z
{
if (key == GLUT_KEY_DOWN)
a += 10;
if (key == GLUT_KEY_UP)
a -= 10;
}
void input(unsigned char key, int x, int y)
{
switch (key)//你還是可以通過鍵盤控制引數
{
case'w':c += 10; break;//相機x,y移動
case's':c -= 10; break;
case'a':b -= 10; break;
case'd':b += 10; break;
case'h':d -= 5; break;//三個旋轉度設定
case'k':d += 5; break;
case'u':e -= 5; break;
case'j':e += 5; break;
case'i':f += 5; break;
case'y':f -= 5; break;
default:
break;
}
}
void start(int button, int state, int x, int y)
{
if (state == GLUT_DOWN && button == GLUT_LEFT_BUTTON)//記錄旋轉拖動起始點
{
flag = 0;
r_x1 = r_x2 = x;
r_y1 = r_y2 = y;
}
if (state == GLUT_DOWN && button == GLUT_MIDDLE_BUTTON)//記錄旋轉拖動起始點
{
flag = 1;
mov_x1 = mov_x2 = x;
mov_y1 = mov_y2 = y;
}
if (state == GLUT_UP && button == GLUT_LEFT_BUTTON)//記錄旋轉拖動最終點並將旋轉量儲存進d,e
{
d += (r_x2 - r_x1) / 3;
e -= (r_y2 - r_y1) / 3;
r_x1 = r_x2 = r_y1 = r_y2 = 0;
}
if (state == GLUT_UP && button == GLUT_MIDDLE_BUTTON)//記錄平移最終點,並將平移量存入trans_x,trans_y
{
trans_x += mov_x2 - mov_x1;
trans_y += mov_y1 - mov_y2;
mov_x1 = mov_y1 = mov_x2 = mov_y2 = 0;
}
}
void end(int x, int y)
{
if (!flag)
{
r_x2 = x;
r_y2 = y;
}
if (flag == 1)
{
mov_x2 = x;
mov_y2 = y;
}
}
void menu(int value)
{
tag = value;
}
int main(int argc, char** argv)
{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(500, 500);
glutInitWindowPosition(100, 100);
glutCreateWindow(argv[0]);
GLenum err = glewInit();//初始化glew
if (GLEW_OK != err)
{
exit(0);
}
init();
glutDisplayFunc(display);
glutTimerFunc(10, timerFunc, 1);
glutSpecialFunc(s_input);
glutKeyboardFunc(input);
glutMouseFunc(start);
glutMotionFunc(end);
glutMainLoop();
return 0;
}