这个小游戏截取自本人的计算机图形学课程设计,其中有一个关于三维场景的绘制任务,我把主要精力放在了制作这个球爆炸效果的实现上,但由于该程序还有其他内容的显示,整体代码量较大,所以这里就只展示球爆炸的实现原理。
完整代码见以下链接,包含二维和三维场景的完整绘制:
中国地质大学(武汉)计算机图形学课程设计-C++文档类资源-CSDN文库https://download.csdn.net/download/qq_58010729/85418717?spm=1001.2014.3001.5503
爆炸前的球:
爆炸效果显示:
按下“C或c”可实现复原:
比较重要的是画圆算法,其实是用多边形拟合的,这里的res默认为20,也即分为20份小四边形进行拟合,因为过高的res数会导致程序运行缓慢。此外,这里分为了光滑和粗糙两种表面,不同的表面设置的法向量是不一样的。光滑时会在每一次设定顶点前都改变法向量,粗糙时(也即有纹理时)只在第一次设定顶点前有法向量的设定:
1. void drawSphere(double x, double y, double z, double r, int res = 20)
2. {
3. int i, j;
4. for (i = 0; i < res; i++) {
5. for (j = 0; j < 2 * res; j++) {
6. float v1x = r * sin(i * M_PI / res) * cos(j * M_PI / res);
7. float v1y = r * cos(i * M_PI / res) * cos(j * M_PI / res);
8. float v1z = r * sin(j * M_PI / res);
9. float v2x = r * sin((i + 1) * M_PI / res) * cos(j * M_PI / res);
10. float v2y = r * cos((i + 1) * M_PI / res) * cos(j * M_PI / res);
11. float v2z = r * sin(j * M_PI / res);
12. float v3x = r * sin((i + 1) * M_PI / res) * cos((j + 1) * M_PI / res);
13. float v3y = r * cos((i + 1) * M_PI / res) * cos((j + 1) * M_PI / res);
14. float v3z = r * sin((j + 1) * M_PI / res);
15. float v4x = r * sin(i * M_PI / res) * cos((j + 1) * M_PI / res);
16. float v4y = r * cos(i * M_PI / res) * cos((j + 1) * M_PI / res);
17. float v4z = r * sin((j + 1) * M_PI / res);
18. glBegin(GL_POLYGON);
19. if (m_Smooth) glNormal3d(v1x, v1y, v1z);
20. else glNormal3d(sin((i + 0.5) * M_PI / res) * cos((j + 0.5) * M_PI / res), cos((i + 0.5) * M_PI / res) * cos((j + 0.5) * M_PI / res), sin((j + 0.5) * M_PI / res));
21. glVertex3d(v1x + x, v1y + y, v1z + z);
22. if (m_Smooth) glNormal3d(v2x, v2y, v2z);
23. glVertex3d(v2x + x, v2y + y, v2z + z);
24. if (m_Smooth) glNormal3d(v3x, v3y, v3z);
25. glVertex3d(v3x + x, v3y + y, v3z + z);
26. if (m_Smooth) glNormal3d(v4x, v4y, v4z);
27. glVertex3d(v4x + x, v4y + y, v4z + z);
28. glEnd();
29. }
30. }
31. }
下面是实现爆炸效果的初始化函数,可以看到爆炸位置的变化其实是由x、y、z三个数组控制的,其中的generate_random_number是另外定义的在某一区间内以某一精度产生随机数的函数;而速度则由vx、vy、vz三个数组控制,这里的ivx、ivy、ivz保存了速度的初始值,方便后续设置速度减少:
1. void init()
2. {
3. glClearColor(0, 0, 0, 1);
4. glClear(GL_COLOR_BUFFER_BIT);
5. accTime = 0;
6. for (int i = 0; i < 1000; i++)
7. {
8. float theta = (rand() % (int)36000.0) / 36000.0 * 2 * M_PI;
9. float phi = (rand() % (int)36000.0) / 36000.0 * 2 * M_PI;
10. x[i] = generate_random_number(1000.0, 0, 0) * sin(theta) * cos(phi);//位置精度1000.0
11. y[i] = generate_random_number(1000.0, 0, 0) * sin(theta) * sin(phi);
12. z[i] = generate_random_number(1000.0, 0, 0) * cos(theta);
13. float vtheta = (rand() % (int)36000.0) / 36000.0 * 2 * M_PI;
14. float vphi = (rand() % (int)36000.0) / 36000.0 * 2 * M_PI;
15. ivx[i] = vx[i] = generate_random_number(1000.0, 0, 20) * sin(vtheta) * cos(vphi);//最大速度20
16. ivy[i] = vy[i] = generate_random_number(1000.0, 0, 20) * sin(vtheta) * sin(vphi);
17. ivz[i] = vz[i] = generate_random_number(1000.0, 0, 20) * cos(vtheta);
18. }
19. }
其中用到的在某一区间内以某一精度产生随机数的函数generate_random_number定义如下,其中要注意的是返回值类型为float:
1. float generate_random_number(float precision, float lower, float upper)
2. {
3. return rand() % (int)precision / precision * (upper - lower) + lower;
4. }
然后需要的是更新函数,新的位置即由原位置+速度*时间间隔计算得出。在时间达到一秒前,速度会随着时间递减。这里的 ACTIVE_TIME是之前定义的宏——临界时间,设定的值为1秒。
1. void updateSpheres()
2. {
3. float deltaTime = (glutGet(GLUT_ELAPSED_TIME) - lastTime) / 1000.0;//返回两次调用glutGet(GLUT_ELAPSED_TIME)的时间间隔,单位为毫秒,所以要除以1000。
4. for (int i = 0; i < 1000; i++) {
5. x[i] += vx[i] * deltaTime;
6. y[i] += vy[i] * deltaTime;
7. z[i] += vz[i] * deltaTime;
8. float t = accTime / ACTIVE_TIME > 1 ? ACTIVE_TIME : accTime ;//时间是否到达1秒
9. vx[i] = ivx[i] * (1 - t);//速度逐渐变慢
10. vy[i] = ivy[i] * (1 - t);
11. vz[i] = ivz[i] * (1 - t);
12. }
13. accTime += deltaTime;
14. lastTime = glutGet(GLUT_ELAPSED_TIME);
15. }
有了前两个函数的铺垫,接下来就可以实现爆炸函数Bang了。首先通过对当前时间进行比较,如果大于两秒则进行初始化(通过这一行代码实现循环绘制)。这里将圆分为1000个小碎块,每个碎块用res=3的圆来拟合(可以说已经不是圆了,如果用过高的res数来拟合会导致程序运行缓慢),绘制一次之后调用 updateSpheres即可更新位置与速度,在每次的t到达0.9秒之前都会不断更新:
1. void Bang()
2. {
3. float t = accTime / ACTIVE_TIME > 1 ? ACTIVE_TIME : accTime ;
4. if (accTime > 2) init();
5. configureMaterials();
6. glRotatef(((glutGet(GLUT_ELAPSED_TIME) / 100.0)), 0, 1, 0);
7. for (int i = 0; i < 1000; i++) {
8. float emi = (1 - t) < 0.1 ? 0.1 : 1 - t;//光强随时间减弱,到0.1停止
9. float mat_emission[] = { emi, emi, emi, 1.0f };
10. glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);
11. drawSphere(x[i], y[i], z[i], 0.02, 3);//用3来模拟碎片
12. }
13. updateSpheres();
14. glutPostRedisplay();
15. }
还有非常重要的一点就是在main函数中设定了随机数种子:
1. srand((uint32_t)time(NULL));
这一设定保证了每次显示的爆炸效果都是不一样的(仔细观察可以发现)。
大家还有疑问的话可以私信咨询。