上次已经基本把太阳系模型的雏形搞出来了,但是还是太简陋。拖沓了一个多星期以后,总算是把一些必要的东西给补全了。相较于之前的版本,此次把太阳系八大行星都画上了,而且为行星们创建了一个行星类方便使用。其次,解决了之前加载贴图资源导致运行卡顿的问题。然后为场景添加了两种不同的相机模式。以下细述。
Planet类
我们把所有行星的通用属性都定义到一个planet类中写在头文件里就可以随处调用并实例化了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| #pragma once #ifndef PLANET_H #define PLANET_H
class Planet { public: float srot; float arot; float radius; GLuint texture_name; float distance;
float counter_a = 0.0f; float counter_s = 0.0f; public: Planet(float sr, float ar, float r, GLuint tex, float dis) { this->srot = sr; this->arot = ar; this->radius = r; this->texture_name = tex; this->distance = dis; }
~Planet() {
} };
#endif
|
对于planet构造函数的实现,其实和我们之前画地球的步骤差不多,这里直接上代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| void draw_planet(Planet &p,GLuint tex) {
glPushMatrix(); material_planet(); glEnable(GL_LIGHTING);
glRotatef(p.counter_a, 0.0f, 1.0f, 0.0f); glTranslatef(p.distance, 0.0f, 0.0f);
glRotatef(90, 1.0f, 0.0f, 0.0f);
glRotatef(p.counter_s, 0.0f, 0.0f, 1.0f);
gluQuadricTexture(e_tex, GLU_TRUE); glPushAttrib(GL_ENABLE_BIT | GL_TEXTURE_BIT); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, tex);
gluSphere(e_tex, p.radius, 15.0f, 15.0f); glBindTexture(GL_TEXTURE_2D, 0);
glPopAttrib(); gluQuadricTexture(e_tex, GLU_FALSE);
p.counter_a += 360.0/(p.arot*24.0); if (p.counter_a >= 360.0f) p.counter_a = int(p.counter_a)%360; p.counter_s += (360.0/p.srot) * 0.2; if (p.counter_s >= 360.0f) p.counter_s = int(p.counter_s) % 360;
glPopMatrix(); }
|
接着我们只要在主函数中定义好八大行星的各个参数并绘制他们就可以了(PS: 这里的参数是以地球的参数作为基本单位的,并且为了方便观察做出了一些比例上的调整)
1 2 3 4 5 6 7
| Planet mars = Planet(25.19f, 686.0f, 15 * 0.532, all_texture[4], 300.0*1.52); Planet mercury = Planet(58.64f, 87.0f, 15 * 0.382, all_texture[5], 300.0*0.38); Planet venus = Planet(-243.0f, 224.7f, 15 * 0.95, all_texture[6], 300.0*0.72); Planet jupiter = Planet(12.13f, 433.6f, 15 * 11, all_texture[7], 300.0*5.2); Planet saturn = Planet(26.73f, 1075.2f, 15 * 9.14, all_texture[8], 300.0*9.5); Planet uranus = Planet(97.73f, 3068.0f, 15 * 4.0, all_texture[9], 300.0*19.2); Planet neptune = Planet(28.32f, 6018.0f, 15 * 3.8, all_texture[10], 300.0* 25);
|
BTW,我们知道土星最出名的就是漂亮的土星环,那么就可以使用gluDisk()这个内置函数来实现,只要在画球体的地方顺便画一个环就可以了。
优化内存
上次留下来的一个很大的问题就是每次我们加载纹理贴图的时候都是动态从图片中加载数据的,这样就导致当我们使用的贴图数量比较多或者图片比较大的时候,运行效率非常低下,画面一顿一顿的,不能忍啊。解决方法就是在绘制之前先把纹理数据存起来,然后释放图片资源。因为在OpenGL里每一份贴图数据都是用一个GLuint类型的数来标识的,而这些数存在一个数组里。对于贴图数据,我们自定义一个texture_data数据结构,只需要保存图片的宽和高,以及其中的字节数据即可,然后依次为每一份数据绑定到唯一的id上,每次根据不同的贴图id找到对应的数据就行了。具体实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| typedef struct texture_data { unsigned char* data; int width, height; } texture_data;
texture_data load_texture2(const char *path) { texture_data t; int width, height; t.data = SOIL_load_image(path, &width, &height, 0, SOIL_LOAD_RGB); t.width = width; t.height = height; return t; }
GLUquadricObj* e_tex = gluNewQuadric(); const int img_num = 11; GLuint all_texture[img_num]; texture_data TextureImage[img_num];
void add_textures() {
memset(TextureImage, 0, sizeof(void *) * img_num); TextureImage[0] = load_texture2("sun.jpg"); TextureImage[1] = load_texture2("earth.jpg"); TextureImage[2] = load_texture2("moon.jpg"); TextureImage[3] = load_texture2("sky.png"); TextureImage[4] = load_texture2("mars.jpg"); TextureImage[5] = load_texture2("mercury.jpg"); TextureImage[6] = load_texture2("venus.jpg"); TextureImage[7] = load_texture2("jupiter.jpg"); TextureImage[8] = load_texture2("saturn.jpg"); TextureImage[9] = load_texture2("uranus.jpg"); TextureImage[10] = load_texture2("neptune.jpg");
glGenTextures(img_num, &all_texture[0]); for (int i = 0; i < img_num; i++) { glBindTexture(GL_TEXTURE_2D, all_texture[i]); glTexImage2D(GL_TEXTURE_2D, 0, 3, TextureImage[i].width, TextureImage[i].height, 0, GL_RGB, GL_UNSIGNED_BYTE, TextureImage[i].data); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); }
for (int i = 0; i < img_num; i++) { SOIL_free_image_data(TextureImage[i].data); }
}
|
相机漫游
上一次我们已经实现了一种相机,即视觉中心固定在太阳,使用鼠标拖动可以改变观察角度。但是在三维场景中更为普遍的是漫游相机,也就是所谓的第一人称视角,我们可以通过鼠标拖动观察四周,同时键盘控制移动。这一部分较多的涉及了数学中空间向量的概念。我们知道OpenGL大多数的关于相机的操作都是归于gluLookAt()这个函数来实现的。这个函数接受9个参数,也即三个三维向量eye、at、和up,分别代表相机在空间中的位置,相机注视的点,以及相机朝上的方向。我们要做的就是计算得到这些向量的值来更改相机的位置和角度。对于相机位置的移动,我们使用键盘进行控制,只需对eye向量的x、z分量做修改即可,不做垂直方向的变化,于是在camera类中我们实现两个方法moveCamera()和yawCamera():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| void Camera::moveCamera(float speed) { vec3 vector = at - eye; vector = normalize(vector);
eye.x += vector.x * speed; eye.z += vector.z * speed; eye.y += vector.y * speed; at.x += vector.x * speed; at.z += vector.z * speed; at.y += vector.y * speed;
} void Camera::yawCamera(float speed) { vec3 yaw; vec3 crossProduct = at - eye; crossProduct = cross(crossProduct, up);
yaw = normalize(crossProduct);
eye.x += yaw.x * speed; eye.z += yaw.z * speed;
at.x += yaw.x * speed; at.z += yaw.z * speed;
}
void keyboard(unsigned char key, int x, int y) {
rad = float(PI*s_angle / 180.0f); switch (key) { case 'w': cam.moveCamera(10); break; case 's': cam.moveCamera(-10); break; case 'a': cam.yawCamera(-10); break; case 'd': cam.yawCamera(10); break; case 'm': camera_mode = !camera_mode; break; case 033: exit(EXIT_SUCCESS); break; } glutPostWindowRedisplay(mainWindow); }
|
接下来使用鼠标修改相机看向的方向,首先是获得鼠标在窗口中的点击位置,然后固定这一位置计算每一时刻鼠标与这一初始位置的偏移量,然后根据偏移的角度和方向去更新at向量,由于这里我段位还不够高,于是借鉴了网上一些其他文章的做法
https://blog.csdn.net/u010223072/article/details/53379231 来实现这个功能,虽然看不太懂但是意外地巧妙且可行,做了一些修改之后代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117
| void Camera::rotateView(float angle, float x, float y, float z) { vec3 newView;
vec3 view = at - eye; float cosTheta = (float)cos(angle); float sinTheta = (float)sin(angle);
newView.x = (cosTheta + (1 - cosTheta) * x * x) * view.x; newView.x += ((1 - cosTheta) * x * y - z * sinTheta) * view.y; newView.x += ((1 - cosTheta) * x * z + y * sinTheta) * view.z;
newView.y = ((1 - cosTheta) * x * y + z * sinTheta) * view.x; newView.y += (cosTheta + (1 - cosTheta) * y * y) * view.y; newView.y += ((1 - cosTheta) * y * z - x * sinTheta) * view.z;
newView.z = ((1 - cosTheta) * x * z - y * sinTheta) * view.x; newView.z += ((1 - cosTheta) * y * z + x * sinTheta) * view.y; newView.z += (cosTheta + (1 - cosTheta) * z * z) * view.z;
at = eye + newView;
}
void Camera::setViewByMouse(int x, int y) { POINT mousePos; float angleY = 0.0f; float angleZ = 0.0f; static float currentRotX = 0.0f;
int middleX = x; int middleY = y;
GetCursorPos(&mousePos); ShowCursor(TRUE); if ((mousePos.x == middleX) && (mousePos.y == middleY)) return; SetCursorPos(x, y);
angleY = (float)((middleX - mousePos.x)) / 1000.0f; angleZ = (float)((middleY - mousePos.y)) / 1000.0f;
static float lastRotX = 0.0f; lastRotX = currentRotX;
currentRotX += angleZ; if (currentRotX > 1.0f) { currentRotX = 1.0f;
if (lastRotX != 1.0f) { vec3 vAxis = at - eye; vec3 vAxis2 = cross(vAxis, up); vec3 vAxis3 = normalize(vAxis2);
rotateView(angleZ, vAxis3.x, vAxis3.y, vAxis3.z); } } else if (currentRotX < -1.0f) { currentRotX = -1.0f;
if (lastRotX != -1.0f) {
vec3 vAxis = at - eye; vec3 vAxis2 = cross(vAxis, up); vec3 vAxis3 = normalize(vAxis2);
rotateView(angleZ, vAxis3.x, vAxis3.y, vAxis3.z); } } else { vec3 vAxis = at - eye; vec3 vAxis2 = cross(vAxis, up); vec3 vAxis3 = normalize(vAxis2);
rotateView(angleZ, vAxis3.x, vAxis3.y, vAxis3.z); }
rotateView(angleY, 0, 1, 0);
}
|
最后把所有变化后的值给到gluLookAt():
1 2 3 4 5
| void Camera::view2() { glLoadIdentity(); gluLookAt(eye.x, eye.y, eye.z, at.x, at.y, at.z, up.x, up.y, up.z); }
|
这样就可以像游戏中一样在场景中漫游了。
下一次打算实现的内容是物体的选取和UI制作,可能吧:P