基于 C++ OpenGL框架实现日地月运动模型动画

该文章已生成可运行项目,

1.简答

(1)你选修计算机图形学课程,想得到的是什么知识?现在课程结束,对于所得的知识是否满意?如果不满意,你准备如何寻找自己需要的知识。

答:之前选修计算机图形学是出于对游戏渲染方面的兴趣,像是各种材质布料的仿真、水面云层之类的模拟、光线追踪效果的实现等。想了解如何将现实生活中的物理现象用图形学的知识在电脑上渲染出来,以上是我选这门课程的原因。对于课程我很满意,老师的讲解很清晰易懂,PPT 上的知识点都比较基础,适合从零开始,让我获益匪浅。不过在涉及到一些数学公式的时候由于自身数学基础不太牢固,也有仍然不太清晰的地方,在课程后会选择自己感兴趣的方向看一些博客文献加深了解。

(2)你对计算机图形学课程中的哪一个部分的内容最感兴趣,请叙述一下,并谈谈你现在的认识。

答:目前对光照仿真感兴趣。光在日常生活中有很多形态,观察点、距离、观察物体的形状和材质、光源和物体的位置、光照的强度等等这些因素都会造成物体的不同呈现。而要通过计算机图形学去仿真光照变化,就要将这些都考虑进去。我们在 opengl 可以通过设置光源和观察物体的材质来营造光照的效果。比如环境光表示其它光线照射到该材质上,经过多次反射后留在环境中的光线颜色(强度);漫反射光表示其它光线照射到该材质上,经过漫反射后形成的光线颜色(强度);镜面反射光表示其它光线照射到该材质上,经过镜面反射后形成的光线颜色(强度);辐射光则是材质本身散发的光颜色(强度)。以及还有镜面指数,影响材质的粗糙光滑度,该值越大,表示材质越类似于镜面,光源照射到上面后,会产生较小的亮点。这些属性的定义就可以比较完整的定义出一个物体的被光照到后渲染的情况。在这次实验中也实践用于各个天体之间光照关系。

(3)你对计算机图形学课程的内容,教学方法有什么看法和建议。

答:我认为老师讲的很好,除了知识点外还有很多模型和动图做辅助,学习起来并不枯燥。因为老师的内容整体偏基础,包括一些基础的图形变换以及光照和纹理处理。由于是补选的课所以前两节课没有听到,我的一点点建议是:希望老师也许可以在有空时讲一些当前图形学前沿的方向和技术、图形学的各种分支、与其它信息技术的结合应用等等,也许可以引起同学们更大的学习兴趣。

2.实验内容

利用 OpenGL 框架,设计一个日地月运动模型动画。

  • 运动关系正确,相对速度合理,且地球绕太阳,月亮绕地球的轨道不能在一个平面内。
  • 地球绕太阳,月亮绕地球可以使用简单圆或者椭圆轨道。
  • 对球体纹理的处理,至少地球应该有纹理贴图。
  • 增加光照处理,光源设在太阳上面。
  • 为了提高太阳的显示效果,可以在侧后增加一个专门照射太阳的灯。

2.1实验方法和过程

2.1.1 opengl 环境配置

从网上下载 glut 包,主要是 glut.h 头文件以及 glut32.lib,将其放在 VS2017 的对应目录下面,方面项目直接引用头文件。

然后在“项目”的“管理 NuGet 程序包”中安装 nupengl.core,这一步在每次新建工程时都要重新操作。如下图 1 所示

图 1 包管理

若能够成功运行示例程序,则说明环境配置成功。

2.2.2 准备工作

首先确定需要写三个重要函数,init()、display()、timer_callback(),分别负责初始化、渲染、计时器刷新的回调函数。

在初始化之前,在 main 函数需要初始化 glut 库以及设定窗口信息。窗口设置包括显示模式、窗口大小、窗口初始位置等。然后用 glutSpecialFunc()函数设定键盘输入操作,用 glutTimerFunc()指定刷新间隔以及对应的回调函数,用 glutDisplayFunc()指定 display 为当前窗口的显示内容函数,最后用 glutMainLoop()使窗口框架运行起来。相关代码如下所示:

int main(int argc, char * argv[])
{
    glutInit(&argc, argv);	//使用glut库需要进行初始化
    glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);	//设定窗口显示模式,RGB颜色模型和双缓存
    glutInitWindowPosition(100, 100);	//设定窗口的初始位置,屏幕左上角为原点,单位为像素
    glutInitWindowSize(600, 600);	//设定窗口的大小
    glutCreateWindow("日地月-球体动态模型");	//创建一个窗口
    …
    initial();
    glutSpecialFunc(&keyboard);	//esc退出
    glutTimerFunc(25, timer_callback, 1);	//每25ms刷新一次
    glutDisplayFunc(&display);	//将myDisplay指定为当前窗口的显示内容函数
    glutMainLoop();	//使窗口框架运行起来
    return 0;
}

考虑到生成日地月模型系统所需要的数据,首先在全局变量中以天为单位,定义日地月的自转公转时间,以及计算球体相关数据时可能用到的参数 π,用于之后的计算。设定一个月 30 天,一年 360 天,则地球绕太阳的公转周期是 360 天,月球的自转周期和绕地球的公转周期是 30 天,地球的自转周期为 1 天,而太阳自转的天数约为 27 天。相关定义如下所示:

# define Pi 3.14159
//一年的天数
static float day_per_year = 360.0; //地球自转角度
//一月的天数
static float day_per_month = 30.0; //月球自转和公转角度
//地球自转的天数
static float earth_per_circle = 7.0; //一天转一圈完全看不出来,这里假设要7天,放慢7倍
//太阳自转的天数
static float sun_per_circle = 27.0;

然后是按照差不多的比例定义日地月的球体半径、相互之间的距离,由于真实数据过于庞大,就简单缩成更小的数据,能看出相对大小关系和相对的距离即可。相关定义如下所示:

//天体半径
static float r_sun = 70.0;
static float r_earth = 25.0;
static float r_moon = 15.0;

//天体距离
static float d_sun_earth = 200.0;
static float d_earth_moon = 50.0;

最后是设定球体的 slices 和 stacks。这是因为后面渲染球体需要用到 glutSolidSphere(radius,slices,stacks)这个方法,参数 1 是球体半径,参数 2、3 类似球体经纬线的条数,通常来说后两个值越大,球体渲染效果越逼真。所以就将其统一写到全局变量,方便随时对球体精细度进行调整。相关定义如下所示:

//球体面数
static float sphere_slices = 30.0;
static float sphere_stacks = 30.0;

2.2.3 初始化:init()

在 init 函数中,需要做一些初始化操作。考虑到三个球体在自转和公转的过程中必定会有重合遮挡,所以需要开启深度测试。然后用 glClearColor 用深灰色作为整个星空的背景色。由于模型和视图的变换都通过矩阵运算来实现,所以在进行变换之前应先设置当前操作的矩阵为投影矩阵,所以用 glMatrixMode,参数的 GL_PROJECTION 表示投影,即把三维物体投影到二维平面上,就是让 OpenGL 按照三维方式来处理图像。相关代码如下所示:

//初始化函数,启动光源、材质贴图
void initial(void)
{
    glEnable(GL_DEPTH_TEST);	//启动深度测试
    glClearColor(0.2f, 0.2f, 0.2f, 1.0f);
    glMatrixMode(GL_PROJECTION);	//OpenGL按照三维方式来处理图像
}

2.2.4 渲染:display()

display 函数负责窗口渲染内容。整体内容大致分为三步:设置可视空间、绘制三个天体、刷新缓冲。

首先按照之前的定义,假设太阳半径为 70,地球半径为 25,月球半径为 15,太阳和地球的距离为 200,月球和地球的距离为 50,所以用 gluLookAt 将观察点守则只在(0,-300,300),达到一个 45 度角俯视的效果。为了得到透视效果,使用 gluPerspective 来设置可视空间,令高宽比为 1:1,设可视角为 75 度(多次调试后的最佳数据),最远可视距离为 600。如下所示:

//展示日地月
void display(void)
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);//清空深度缓冲
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();   //重置当前指定的矩阵为.
    gluPerspective(75, 1, 1, 600);
    glMatrixMode(GL_MODELVIEW);  //把当前矩阵设置为单位矩阵
    glLoadIdentity();
    gluLookAt(0, -300, 300, 0, 0, 0, 0, 0, 1);
    // 绘制红色的太阳
    …
    // 绘制蓝色的地球
    …
    // 绘制黄色的月亮
    …
    // 刷新缓冲
    …
}

首先需要绘制太阳,glPushMatrix()和 glPopMatrix()两个函数可以用于将矩阵入栈和出栈,保证各个天体的独立运动情况不互相干涉,能够精确保存和获取我们需要的状态。太阳会自转,计算自转角度的公式就是 day(当前日期)/sun_per_circle(太阳自转周期)*360(度),与 360 再取个模可以得到,由于太阳是自西向东逆时针自转的,所以旋转方向为(0.0,0.0,1.0)。

然后进入 sun()函数,用于绘制球体以及之后添加纹理和材质等作用。同样先保存矩阵,然后使用 glColor 调出红色,用 glutSolidSphere 绘制实心球体,参数分别为球体半径和经纬线条数。

相关代码如下所示:

void display() {
    …
    // 绘制红色的太阳,太阳自转约25天
    glPushMatrix();
    glRotatef(fmod(day / sun_per_circle * 360.0, 360.0), 0.0f, 0.0f, 1.0f);	//太阳是自西向东自转,逆时针
    sun();
    glPopMatrix();
    …
}

//太阳
void sun() {
    glPushMatrix();
    //渲染球体
    glColor3f(1.0f, 0.0f, 0.0f);
    glutSolidSphere(r_sun, sphere_slices, sphere_stacks);
    //渲染经纬线
    if (is_show_wiresphere) show_wire_sphere(r_sun);
    //自转情况
    if (is_show_rotate) show_self_rotate(r_sun);
    glPopMatrix();
}

第二步需要绘制地球,首先渲染地球公转,glRotatef 表示地球公转角度的计算公式为:day(当前日期)/day_per_year(地球公转周期=1 年)*360(度),与 360 再取个模可以得到。由于地球也是是自西向东逆时针公转的,所以旋转方向同样为(0.0,0.0,1.0)。由于公转需要按照一定半径运动,所以后面再使用 glTranslatef,以 x 轴平移对应距离,就能达到地球绕着太阳公转的效果。

由于地球自转需要单独调整,所以仍旧是使用 glPushMatrix()保证不干扰月球运动。自转角度与前面不同,地球自转一圈刚好为 1 天,所以自转角度的公式为:day(当前日期)/earth_per_circle(地球自转周期=1 天)*360(度),与 360 再取个模可以得到。由于按天流逝的话,1 天自转 1 次的动画会和静止没有差别,所以为了显得相对速度较合理,之前在定义中把这里的地球自转周期改为了 7 天,也就是将地球自转速度放慢了 7 倍,使得观感更好。

由于题目要求月地的公转轨道与日地不能在同一平面上,所以地球自转的角度会有些倾斜。

然后进入 earth()函数,用于绘制球体以及之后添加纹理和材质等作用。同样先保存矩阵,然后使用 glColor 调出蓝色,用 glutSolidSphere 绘制实心球体,参数分别为球体半径和经纬线条数。在没有添加纹理和材质前,和渲染太阳的处理方式没什么区别。

相关代码如下所示:

void display() {
    …
// 绘制蓝色的地球,地球自转为1天,日地公转约365天
    //地球公转
    glRotatef(fmod(day / day_per_year * 360.0, 360.0), 0.0f, 0.0f, 1.0f);	//地球是自西向东公转,逆时针
    glTranslatef(d_sun_earth, 0.0f, 0.0f);
    //地球自转
    glPushMatrix();
    glRotatef(fmod(day / earth_per_circle * 360.0, 360.0), 0.1f, 0.0f, 1.0f);
    earth();
    glutSolidSphere(r_earth, sphere_slices, sphere_stacks);
    //地月轨道绘制
    if (is_show_track) show_track(d_earth_moon);
    glPopMatrix();
    …
}

//地球
void earth() {
    glPushMatrix();
    //渲染球体
    glColor3f(0.0f, 0.0f, 1.0f);
    glutSolidSphere(r_earth, sphere_slices, sphere_stacks);
    //渲染经纬线
    if (is_show_wiresphere) show_wire_sphere(r_earth);
    //显示自转情况
    if (is_show_rotate) show_self_rotate(r_earth);
    glPopMatrix();
}

最后一步需要绘制月球,由于月球不再会影响其它天体的运动,所以没再使用 glPushMatrix()和 glPopMatrix()。

由于月球的自转与公转周期是一样的,glRotatef 表示月球公转角度的计算公式为:day(当前日期)/day_per_month (月球公转周期=1 个月)*360(度),与 360 再取个模可以得到。由于公转需要按照一定半径运动,所以后面再使用 glTranslatef,以 x 轴平移对应距离 d,就能达到月球绕着地球以 z 轴公转的效果。

然后进入 moon()函数,用于绘制球体以及之后添加纹理和材质等作用。使用 glColor 调出黄色,在没有添加纹理和材质前,和渲染其它天体的处理方式没什么区别。

相关代码如下所示:

void display() {
    …
    glRotatef(fmod(day / day_per_month * 360.0, 360.0), 0.0f, 0.0f, -1.0f);
    glTranslatef(d_earth_moon, 0.0f, 0.0f);
    moon();
    …
}

//月球
void moon() {
    glPushMatrix();
    //渲染球体
    glColor3f(1.0f, 1.0f, 0.0f);
    glutSolidSphere(r_moon, sphere_slices, sphere_stacks);
    //渲染经纬线
    if (is_show_wiresphere) show_wire_sphere(r_moon);
    //显示自转情况
    if (is_show_rotate) show_self_rotate(r_moon);
    glPopMatrix();
}

最后强制刷新缓冲,保证画面能被正确绘制。

//刷新缓冲
glFlush();
glutSwapBuffers();

至此,三个天体球的相对位置已经绘制完毕,但依旧还是静态的画面,需要使其动起来。一个很直接的想法就是,每隔一段时间刷新一次画面,每次刷新代表天数的参数 day 会加 1。所以接下来需要处理 timer_callback 函数。

2.2.5 动画:timer_callback()

在计时器回调函数中,每次刷新都让当前天数 day 递增 1,然后用 glutPostRedisplay 标记当前窗口需要重新绘制。由于这个刷新行为不是只执行一次,所以在函数体最后要加上 glutTimerFunc 的调用。代码如下所示:

//动态绘制
void timer_callback(int value) {
    day++;
    if (day >= day_per_year) {
        day = 0;
    }
    glutPostRedisplay();
    glutTimerFunc(25, timer_callback, 1);
}

2.2.6 可视化功能:keyboard()

在实验 1 中,由于三个实心球体的自转与公转表现的不够明显,所以利用 glutSpecialFunc(&keyboard)增加了键盘操作识别。三个功能分别是:显示自转情况、显示经纬线、显示天体运行轨迹。分别通过按键“↑”“↓”“←”可以切换和取消,keyboard 函数如下所示:

void keyboard(GLint key, GLint x, GLint y)
{
    switch (key)
    {
    case GLUT_KEY_UP:
        is_show_rotate = !is_show_rotate;
        break;
    case GLUT_KEY_DOWN:
        is_show_wiresphere = !is_show_wiresphere;
        break;
    case GLUT_KEY_LEFT:
        is_show_track = !is_show_track;
        break;
    default:
        break;
    }
}

显示自转情况的函数想法很简单,参数为对应球体的半径 r,用 glSolidSphere 新建一个白色小球,平移 r 后刚好在对应球体的面上。如果在对应球体自转代码后面,矩阵 pop 之前调用这个函数,就会出现一个小球以球体自转的速度在球面上转动,可以更直观地看出速度比对。

//显示自转情况
void show_self_rotate(float r)
{
    glTranslatef(-r, 0.0f, 0.0f);
    glColor3f(1.0f, 1.0f, 1.0f);
    glutSolidSphere(r*0.05, 12, 12);//渲染球体
}

当开启该功能时,结果如下图 2 所示:

图 2 有自转显示的日地月系

显示球体经纬线主要是为了判断球体精度是否合适,操作也很简单,用 glutWireSphere 新增一个只有线的球体,半径为原来的 1.01 倍,在球体渲染完后,矩阵 pop 之前使用即可达到想要的显示效果,效果如下所示:

//显示球体的经纬线
void show_wire_sphere(float r)
{
    glColor3f(0.6f, 0.6f, 0.6f);
    glutWireSphere(r*1.01, sphere_slices, sphere_stacks);
}

当开启经纬线时,效果如下所示:

图 3 有经纬线的日地月系

最后是显示天体运行轨道的功能。通过 glBegin(GL_LINE_LOOP)可以画出一条闭合折线,参数 d 是所画圆的半径,使用时只要传入日地距离和月地距离即可。

//轨道显示
void show_track(float d)
{
    glBegin(GL_LINE_LOOP);
    glColor3f(0.0f, 1.0f, 1.0f);
    for (int i = 0; i < 100; i++) {
        glVertex3f(d *cos(2 * i*Pi / 100), d*sin(2 * i*Pi / 100), 0.0f);
    }
    glEnd();
}

当开启轨道显示时,效果如下图 4 所示:

图 4 有运行轨道的日地月系

至此,实验 1 已经全部完成。接下来要调整代码,对天体进行纹理还有光照处理,进行实验 2。

2.2.7 纹理处理

首先在全局变量中声明纹理所需要的数据,分别是三个 GLuint 型变量,用于存储三个天体的纹理 ID。以及一个 GLUquadricObj*类型的变量,用于生成二次曲面,方便贴纹理。

//纹理
GLuint texture_sun;
GLuint texture_earth;
GLuint texture_moon;
GLUquadricObj *quadObj;

首先在初始化函数中需要读取三个天体的纹理。此时需要一个 load_bmp 函数,期望是输入参数:图片地址、材质 ID,就能够成功输入位图并联系到对应 ID。

//初始化函数,启动光源、材质贴图
void initial(void)
{
    glClearColor(0.2f, 0.2f, 0.2f, 0.0f);
    glShadeModel(GL_SMOOTH);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_DEPTH_TEST);	//启动深度测试
    glEnable(GL_COLOR_MATERIAL);

    quadObj = gluNewQuadric();
    //读取太阳纹理
    load_bmp("img/sun.bmp", texture_sun);
    //读取地球纹理
    load_bmp("img/earth.bmp", texture_earth);
    //读取月球纹理
    load_bmp("img/moon.bmp", texture_moon);
}

加载位图函数主要利用 glaux 库中的 auxDIBImageLoad 函数,将位图解析成了对应数据结构。然后通过 glGenTexture 对纹理命名,引入纹理对象 ID,并通过 glBindTexture 将已命名的 texure 绑定到一个纹理目标上。最核心的函数是 gluBuild2DMipmaps,将之前读取的图片数据部分作为参数,就能成功实现纹理目标与 ID 的绑定。最后再进行两边过滤函数即可。

//加载位图
bool load_bmp(const char* filename, GLuint &texture) {
    AUX_RGBImageRec * imageData = NULL;
    imageData = auxDIBImageLoadA(filename);
    if (imageData == NULL) {
        printf("%s图片数据读取失败!\n");
        return false;
    }
    glGenTextures(1, &texture);	//表示纹理对象的名称
    glBindTexture(GL_TEXTURE_2D, texture);
    gluBuild2DMipmaps(GL_TEXTURE_2D, 4, imageData->sizeX, imageData->sizeY, GL_RGB, GL_UNSIGNED_BYTE, imageData->data);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);//纹理过滤函数,需要两遍
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    free(imageData->data);
    free(imageData);
    return true;
}

将已实现的材质渲染为球体则需要在三个天体函数中插入新的内容。首先用 gluNewQuadric()新建一个二次曲面物体,然后用 glBindTexture 建立一个绑定到目标纹理的纹理,这时候的参数就是之前定义的纹理 ID。用 gluQuadricTexture 启用该二次曲面的纹理,并将 style 设定为 GLU_FILL,表示面填充模式。最后使用 gluSphere 即可生成一个球体的二次曲面。三个天体的函数大同小异,太阳部分的代码如下:

//太阳
void sun() {
    glPushMatrix();
    quadObj = gluNewQuadric();	//创建一个二次曲面物体
    //渲染球体
    glBindTexture(GL_TEXTURE_2D, texture_sun);	//建立一个绑定到目标纹理的有名称的纹理
    gluQuadricTexture(quadObj, GL_TRUE);	//启用该二次曲面的纹理
    gluQuadricDrawStyle(quadObj, GLU_FILL);	//启用面填充
    //太阳材质
    …
    gluSphere(quadObj, r_sun, sphere_slices, sphere_stacks);	//二次曲面
    glPopMatrix();
}

2.2.8光照处理

光照处理需要使用 glLightfv 函数设定光源情况。目前的思路是,有一个整体的环境光,三个天体各自不同的材质会导致光照的变化。此次使用的是 0 号光源,所以在 init 函数中要启用光源。

void initial(void) {
    …
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    …
}

首先是环境光,建立一个在原点位置的纯黑色环境光,可以模拟整体黑暗的情况,便于后面的明暗对比。环境光的调用在 display 函数中,渲染天体之前的位置。相关代码如下所示:

//创建环境光
void light()
{
    GLfloat light_position[] = { 0.0, 0.0, 0.0, 1.0 };
    GLfloat light_ambient[] = { 0.0, 0.0, 0.0, 1.0 };
    GLfloat light_diffuse[] = { 1.0, 1.0, 1.0, 1.0 };
    GLfloat light_specular[] = { 1.0, 1.0, 1.0, 1.0 };
    glLightfv(GL_LIGHT0, GL_POSITION, light_position);	//光源位置,原点
    glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient);	//环境光,黑
    glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse);	//漫反射光	,白
    glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular);	//镜面光
}

接下来就是各个天体的材质设定。这里以地球为例。

环境光表示其它光线照射到该材质上,经过多次反射后留在环境中的光线颜色。对应参数为 GL_AMBIENT,在宇宙背景中,即设置为 0。

漫反射光表示其它光线照射到该材质上,经过漫反射后形成的光线颜色。对应参数为 GL_DIFFUSE,这里指的是太阳照射到地球上的漫反射光,漫反射光相对镜面反射光照面积会大一点,所以这里设置为白色。

镜面反射光表示其它光线照射到该材质上,经过镜面反射后形成的光线颜色。对应参数为 GL_SPECULAR,这里指的是太阳照射到地球上的镜面反射光,镜面反射光相对小一点,所以这里设置为红光,方便看出区别。

辐射光则是材质本身散发的光颜色,这里对应参数为 GL_EMISSION,指的是地球发出的光,在这里将地球发出的光定义为灰光偏蓝,不影响其它。

镜面指数就是影响材质的粗糙光滑度,该值越大,表示材质越类似于镜面,光源照射到上面后,产生较小的亮点。这里折中取了 50。

相关代码示例如下:

//地球
void earth() {
    glPushMatrix();
    //渲染球体
    glBindTexture(GL_TEXTURE_2D, texture_earth);	//建立一个绑定到目标纹理的有名称的纹理
    gluQuadricTexture(quadObj, GL_TRUE);	//启用该二次曲面的纹理
    //地球材质
    GLfloat earth_mat_ambient[] = { 0.0f, 0.0f, 0.0f, 1.0f };  //定义材质的环境光颜色,为0
    GLfloat earth_mat_diffuse[] = { 1.0f, 1.0f, 1.0f, 1.0f };  //定义材质的漫反射光颜色,白光
    GLfloat earth_mat_specular[] = { 1.0f, 0.0f, 0.0f, 1.0f };   //定义材质的镜面反射光颜色,红光
    GLfloat earth_mat_emission[] = { 0.1f, 0.1f, 0.2f, 1.0f };   //定义材质的辐射光颜色,灰光偏蓝
    GLfloat earth_mat_shininess = 50.0f;
    glMaterialfv(GL_FRONT, GL_AMBIENT, earth_mat_ambient);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, earth_mat_diffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, earth_mat_specular);
    glMaterialfv(GL_FRONT, GL_EMISSION, earth_mat_emission);
    glMaterialf(GL_FRONT, GL_SHININESS, earth_mat_shininess);

    gluSphere(quadObj, r_earth, sphere_slices, sphere_stacks);	//二次曲面
    glPopMatrix();
}

2.2.9其它

最后在实验 2 将 keyboard 输入改为了加速和减速。通过按“↑”“↓”可以对天体的运动速度进行调整。相关代码如下所示:

void keyboard(GLint key, GLint x, GLint y)
{
    switch (key)
    {
    case GLUT_KEY_UP:
        day_per_year = day_per_year *0.9;
        day_per_month = day_per_month *0.9;
        earth_per_circle = earth_per_circle *0.9;
        sun_per_circle = sun_per_circle *0.9;
        break;
    case GLUT_KEY_DOWN:
        day_per_year = day_per_year *1.1;
        day_per_month = day_per_month *1.1;
        earth_per_circle = earth_per_circle *1.1;
        sun_per_circle = sun_per_circle *1.1;
        break;
    case GLUT_KEY_LEFT:
        is_show_track = !is_show_track;
        break;
    default:
        break;
    }
}

2.2 实验结果

开启了所有功能,包括旋转标记、球体经纬线、天体运行轨迹的实验 1 截图如下图 5 所示。

图 5 实验1截图-全功能

仅球体运动,无其他功能的实验 1 如下图 6 所示。

图 6 实验1截图-无功能

实验 2 实现了贴图与光照效果,如下图 7、图 8 所示。

图 7 实验2截图1

图 8 实验2截图2

2.3 心得体会

在完成实验的过程中,我从原本的一知半解,到慢慢了解 OpenGL 的渲染原理,感受到自己获得了很大的成长。

在实现过程中遇到了很多问题,比如在导入位图文件的时候,虽然 auxDIBImageLoad 函数并没有报错,但是一旦运行就会弹出一个 Error 弹窗“Unknown DIB file format”。然后查了很多网上资料才发现是位图位数不统一的问题。我之前的操作是:双击打开一张 png 图片,启用的是 win10 自带的“照片”软件,然后直接另存为 bmp 类型。但是这样会导致各个图片的位数不统一,而且有要求宽和高都需要是 2 的指数。后来找到的解决方法是,右击图片用 win10 自带的“画图”软件打开,然后选择保存类型为“24 位位图”即可。

除此之外在如何设置地月之间的运动关系也是困扰了很久。如何让地球的自转不影响月球的运行轨迹,后来才发现使用矩阵的栈操作 push 和 pop 就能轻松实现想要的效果。

通过这次实验我收获了很多,从课堂上的理论知识真正落实到了一个小成品中,在看到日地月系成功运动起来的时候、在纹理第一次成功贴上的时候、在光照终于符合预期的时候,我都感觉到了极大的成就感。

真心感谢老师的悉心教导!

 ♻️ 资源

大小: 82.5MB

➡️ 资源下载:https://download.csdn.net/download/s1t16/87404316

注:更多内容可关注微信公众号【神仙别闹】,如当前文章或代码侵犯了您的权益,请私信作者删除!

本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

神仙别闹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值