♻️ 资源大小13.0MB➡️资源下载https://download.csdn.net/download/s1t16/87430283绘制太阳系一、实验任务绘制出一个太阳系︰要求:1有详细的计算步骤2.至少包含太阳、地球和月亮3.用 OpenGL 进行绘制Bonus :1用代码实现出可执行的实例2绘制出行星的轨道二、原理和分析1.OpenGL 材质和光照OpenGL 在处理光照时把光照系统分为三部分分别是光源、材质和光照模型。光源、材质和光照模式都有各自的属性尽管属性种类繁多但这些属性都只用很少的几个函数来设置:使用 glLight函数可设置光源的属性使用 glMaterial函数可设置材质的属性使用 glLightModel*函数可设置光照模式。GL_AMBIENT、GL_DIFFUSE、GL_SPECULAR 这三种属性是光源和材质所共有的如果某光源发出的光线照射到某材质的表面则最终的漫反射强度由两个 GL_DIFFUSE 属性共同决定最终的镜面反射强度由两个 GL_SPECULAR 属性共同决定。在 OpenGL 中仅仅支持有限数量的光源。使用 GL_LIGHTO 表示第 О 号光源GL_LIGHT1 表示第 1 号光源依次类推OpenGL 至少会支持 8 个光源即 GL_LIGHTO 到 GL_LIGHT7。使用 glEnable 函数可以开启它们。例如glEnable(GL_LIGHTO);可以开启第 О 号光源。使用 gIDisable 函数则可以关闭光源。一些 OpenGL 实现可能支持更多数量的光源但总的来说开启过多的光源将会导致程序运行速度的严重下降。OpenGL 场景中模型颜色的产生大致为如下的流程图所描述︰光源设置设置环境光:glLightfv(GL_LIGHTO,GL_AMBIENT,ambientLight);设置漫射光成分:glLightfv(GL_LIGHTO,GL_DIFFUSE,DiffuseLight)设置镜面光成分:glLightfv(GL_LIGHTO,GL_SPECULAR,SpecularLight);光源的属性 GL_SPECULAR 影响镜面反射区域的颜色一般物体的镜面反射区域的颜色为入射光线的颜色要实现真实感应该将它的值设置成与 GL_DIFFUSE 相同。设置光源的位置:glLightfv(GL_LIGHTO,GL_POSITION, sun_light_position);GL_POSITION 属性表示光源所在的位置。由四个值(XY,Z,W)表示。方向性光源 ∶ 第四个值 W 为零则表示该光源位于无限远处前三个值表示了它所在的方向。通常太阳可以近似的被认为是方向性光源。位置性光源 ∶ 第四个值 W 不为零则 X/W,Y/WZ/W 表示了光源的位置。这种光源称为位置性光源。定位光源需要对其发射的光进行衰减可以设置各种衰减因子。环境光散射光和镜面反射光的贡献都是衰减的只有发射光和全局环境光不会衰减。材质设置openGL 用材料对光的红、绿、蓝三原色的反射率来近似定义材料的颜色。像光源一样材料颜色也分成环境、漫反射和镜面反射成分它们决定了材料对环境光、漫反射光和镜面反射光的反射程度。在进行光照计算时材料对环境光的反射率与每个进入光源的环境光结合对漫反射光的反射率与每个进入光源的漫反射光结合对镜面光的反射率与每个进入光源的镜面反射光结合。对环境光与漫反射光的反射程度决定了材料的颜色并且它们很相似。对镜面反射光的反射率通常是白色或灰色即对镜面反射光中红、绿、蓝的反射率相同)。镜面反射高光最亮的地方将变成具有光源镜面强度的颜色。材质的颜色与光源的颜色有些不同。对于光源R、G、B 值等于 R、G、B 对其最大强度的百分比。若光源颜色的 R、G、B 值都是 1.0则是最强的白光;若值变为 0.5颜色仍为白色但强度为原来的一半于是表现为灰色;若 RG1.0B0.0则光源为黄色。对于材质R、G、B 值为材质对光的 R、G、B 成分的反射率。比如一种材质的 R1.0、0.5、B0.0则材质反射全部的红色成分一半的绿色成分不反射蓝色成分。也就是说若 OpenGL 的光源颜色为(LR、LG、LB)材质颜色为(MR、MG、MB)那么在忽略所有其他反射效果的情况下,最终到达眼睛的光的颜色为( LRMR、LGMG、LB*MB )指定了图元的法线之后,我们还需要为其指定相应的材质以决定物体对各种颜色的光的反射程度这将影响物体表现为何种颜色。函数 glMaterialfv(GL_FRONT,GL_DIFFUSE,Diffuse);可以设定物体的材质属性。GL_AMBIENT、GL_DIFFUSE、GL_SPECULAR 属性。这三个属性与光源的三个对应属性类似每一属性都由四个值组成。GL_AMBIENT 表示各种光线照射到该材质上,经过很多次反射后最终遗留在环境中的光线强度颜色)GL_DIFFUSE 表示光线照射到该材质上经过漫反射后形成的光线强度颜色)GL_SPECULAR 表示光线照射到该材质上经过镜面反射后形成的光线强度颜色)通常GL_AMBIENT 和 GL_DIFFUSE 都取相同的值可以达到比较真实的效果。使用GL_AMBIENT_AND_DIFFUSE 可以同时设置 GL_AMBIENT 和 GL_DIFFUSE 属性。GL_SHININESS 属性。该属性只有一个值称为“镜面指数”取值范围是 О 到 128。该值越小,表示材质越粗糙,点光源发射的光线照射到上面也可以产生较大的亮点。该值越大表示材质越类似于镜面光源照射到上面后产生较小的亮点。GL_EMISSION 属性。该属性由四个值组成表示一种颜色。OpenGL 认为该材质本身就微微的向外发射光线以至于眼睛感觉到它有这样的颜色但这光线又比较微弱以至于不会影响到其它物体的颜色。OpenGL 中的坐标系OpenGL 中总共分为 5 个空间 ∶局部空间(Local Space或者称为物体空间(Object Space))世界空间(World Space)观察空间(View Space或者称为视觉空间(Eye Space))裁剪空间(Clip Space)屏幕空间(Screen Space)世界坐标系世界坐标系始终是固定不变的。OpenGL 中使用右手坐标。进行旋转操作时需要指定的角度 0 的方向则由右手法则来决定即右手握拳大拇指直向某个坐标轴的正方向那么其余四指指向的方向即为该坐标轴上的 O 角的正方向(即 O 角增加的方向)对象坐标系对象坐标系是对象在被应用任何变换之前的初始位置和方向所在的坐标系,也就是当前绘图坐标系。该坐标系不是固定的且仅对该对象适用。在默认情况下该坐标系与世界坐标系重合。这里能用到的函数有 glTranslatef() , glScalef(), glRotatef()当用这些函数对当前绘图坐标系进行平移、伸缩、旋转变换之后世界坐标系和当前绘图坐标系不再重合。改变以后再用 glVertex3f()等绘图函数绘图时都是在当前绘图坐标系进行绘图所有的函数参数也都是相对当前绘图坐标系来讲的。观察坐标系GL_MODELVIEW 矩阵是模型变换矩阵和视变换矩阵的组合(Mview*Mmodel)并不存在单独的模型变换(Model)和视点变换(View)。所以使用 GL_MODELVIEW 矩阵就可以使对象从对象坐标系转换到观察坐标系。默认情况下,眼坐标系与世界坐标系也是重合的。使用函数 gluLookAt()则可以指定眼睛(相机)的位置和眼睛看向的方向。该函数的原型如下:void gluLookAt(GLdouble eyex,GLdouble eyey, GLdouble eyez,GLdouble centerx,GLdouble centery, GLdouble centerz,GLdouble upx, GLdouble upy, GLdouble upz);函数参数中点(eyex,eyey, eyez)代表眼睛所在位置;点(centerx, centery.centerz)代表眼睛看向的位置;向量(upx, upy, upz)代表视线向上方向其中视点和参考点的连线与视线向上方向要保持垂直关系。只需控制这三个量便可定义新的视点。裁剪坐标系观察坐标系到裁剪坐标是通过投影完成的。眼坐标通过乘以 GL_PROJECTION 矩阵变成了裁剪坐标。GL_PROJECTION 矩阵定义了视景体( viewing volume)即确定哪些物体位于视野之内位于视景体外的对象会被剪裁掉。除了视景体投影变换还定义了顶点是如何投影到屏幕上的是透视投影(perspective projection)还是正交投影(orthographicprojection)。屏幕坐标系屏幕上的设备坐标称为屏幕坐标。设备坐标又称为物理坐标,是指输出设备上的坐标。设备坐标用对象距离窗口左上角的水平距离和垂直距离来指定对象的位置,是以像素为单位来表示的,设备坐标的 × 轴向右为正,Y 轴向下为正,坐标原点位于窗口的左上角。从 NDC 坐标到屏幕坐标基本上是一个线性映射关系。通过对 NDC 坐标进行视口变换得到。这时候就要用到函数 glViewport()该函数用来定义渲染区域的矩形也就是最终图像映射到的区域。键盘和鼠标事件鼠标事件检测鼠标 clicks 函数:void glutMouseFunc(void(*func)(int button,int state,int x,int y));它在程序初始化阶段被调用。函数一共有 4 个参数。第一个参数表明哪个鼠标键被按下或松开这个变量可以是下面的三个值中的一 : GLUT_LEFT_BUTTON ,GLUT_MIDDLE_BUTTON ,GLUT_RIGHT_BUTTON。第二个参数表明,函数被调用发生时鼠标的状态也就是是被按下或松开可能取值如下 ∶GLUT_DOWNGLUT_UP。当函数被调用时,state 的值是 GLUT_DOWN 那么程序可能会假定将会有个 GLUT_UP 事件甚至鼠标移动到窗口外面也如此。然而,如果程序调用 glutMouseFunc 传递 NULL 作为参数那么 GLUT 将不会改变鼠标的状态。剩下的两个参数(x,y)提供了鼠标当前的窗口坐标以左上角为原点。检测动作( motion)函数GLUT 提供鼠标 motion 检测能力。有两种 GLUT 处理的 motion : active motion 和 passive motion。Active motion 是指鼠标移动并且有一个鼠标键被按下。Passive motion 是指当鼠标移动时并有没鼠标键按下。如果一个程序正在追踪鼠标那么鼠标移动期间每一帧将产生一个结果。函数原型 ∶void glutMotionFunc(void(*func)(int x,int y)); void glutPassiveMotionFunc(void (*func)(int x,int y));参数︰Func:处理各自类型 motion 的函数名。处理 motion 的参数函数的参数(xy是鼠标在窗口的坐标。以左上角为原点。键盘事件 ∶GLUT 提供了两个函数为这个键盘消息注册回调。glutKeyboardFunc:告诉窗口系统哪一个函数将会被调用来处理普通按键消息。普通键是指字母数字和其他可以用 ASCII 代码表示的键。函数原型如下 ∶void glutKeyboardFunc(void(*func)(unsigned char key,int x,int y));参数 ∶func:处理普通按键消息的函数的名称。如果传递 NULL 则表示 GLUT 忽略普通按键消息。这个作为 glutKeyboardFunc 函数参数的函数需要有三个形参。第一个表示按下的键的 ASCI 码其余两个提供了当键按下时当前的鼠标位置。鼠标位置是相对于当前客户窗口的左上角而言的。glutSpecialFunc :处理普通按键消息的函数函数原型如下 ∶void glutSpecialFunc(void(*func)(int key,int x,int y));参数︰func:处理特殊键按下消息的函数的名称。传递 NULL 则表示 GLUT 忽略特殊键消息。三、图形绘制创建行星类class Star { private: int ID; double radius; //半径 double rotation_period; double rotation_angle; double revolution_radius; double revolution_period; double revolution_angle; double angle 0; double rotationAngle 0; public: GLfloat rotaVector[NUMBER][3]; public: //名称半径自转周期自转角度公转半径公转周期公转角度 Star(int, double, double, double, double, double, double,double); Star(); //设置行星的材质 void Mertial(GLfloat*, GLfloat*, GLfloat*, GLfloat*, GLfloat); void DrawStar(bool run); //绘制行星 void DrawStaellite(Star Planet,bool run); //绘制卫星 void DrawOrbit(); //绘制轨道 void DrawSatelliteOrbit(Star Planet); //绘制卫星轨道 void SetValue(GLfloat* , GLfloat , GLfloat , GLfloat ); void DrawRing(GLfloat R,GLfloat Width); //绘制行星环 };创建一个类用于储存星球的半径自转周期自转角度公转半径公转周期公转角度等。 定义关于行星运动的方法等。行星运动由于矩阵乘法的性质和 OpenGL 中的设定实际绘制的顺序和绘制时调用函数的顺序是相 反的。由绘制卫星的例子描述 OpenGL 中的绘制过程。//绘制卫星时必须在行星后面因为要用到行星的参数 void Star::DrawStaellite(Star Planet,bool run) { if (run) { //计算卫星公转角度 if (this-revolution_period ! 0) this-angle (float)(1 / this-revolution_period); while (this-angle 360) this-angle this-angle - 360; //计算卫星自转角度 bool flag (rotation_period 0) ? false : true; if (this-rotation_period ! 0) this-rotationAngle (float)(1 / this-rotation_period * flag); while (this-rotationAngle 360) this-rotationAngle this-rotationAngle - 360; } glPushMatrix(); //行星公转轨道倾角 glRotatef(Planet.revolution_angle, rotaVector[Planet.ID][0], rotaVector[Planet.ID][1], rotaVector[Planet.ID][2]); glRotatef(Planet.angle, 0.0f, 1.0f, 0.0f); //行星公转 glTranslatef(Planet.revolution_radius, 0.0f, 0.0f);//行星公转半径 //卫星公转轨道倾角 glRotatef(this-revolution_angle, rotaVector[this-ID][0], rotaVector[this-ID][1], rotaVector[this-ID][2]); glRotatef(angle, 0.0f, 1.0f, 0.0f); //卫星公转 glTranslatef(this-revolution_radius, 0.0f, 0.0f); //卫星公转半径 glutSolidSphere(this-radius, 80.0f, 80.0f); glPopMatrix(); }首先计算出卫星本次公转和自转的角度位置。随后调用 glPushMatrix();函数将矩阵压栈。由于顺序相反故调用函数顺序从下向上观察︰首先绘制球体 ∶glutSolidSphere(this-radius 80.0f80.0f);随后将卫星平移至公转半径位置︰glTranslatef(this-revolution_radius0.0f0.0f);然后根据计算出的角度将伟星管旋转至公转位置 ∶glRotatef(angle 0.0f1.0f0.Of);由于卫星具有公转倾角故须将其公转进行旋转 ∶glRotatef(this-revolution_angle,rotaVector[this-ID][0],rotaVector[this-ID][1]rotaVector[this-ID][2]);由于卫星围绕行星公转随后将其平移至行星公转的半 ∶glTranslatef(Planet.revolution_radius0.0f0.0f);随后将其旋转行星公转的角度︰glRotatef(Planet.angle0.0f1.0f0.Of);由于行星公转同时具有轨道倾角故将其旋转行星公转的轨道倾角︰glRotatef(Planet.revolution_angle,rotaVector[Planet.ID][0],rotaVector[Planet.ID][1], rotaVector[Planet.ID][2]);行星材质void Star::Mertial(GLfloat* emission,GLfloat* ambient, GLfloat* diffuse, GLfloat* specular, GLfloat shininess) { glMaterialfv(GL_FRONT, GL_EMISSION, emission); //自己发光 glMaterialfv(GL_FRONT, GL_AMBIENT, ambient); //环境光 glMaterialfv(GL_FRONT, GL_DIFFUSE, diffuse); //漫反射 glMaterialfv(GL_FRONT, GL_SPECULAR, specular); //镜面反射 glMaterialf(GL_FRONT, GL_SHININESS, shininess); //镜面指数 }行星的材质由传入的参数设定每个行星需要在其绘制时传入相应的材质的参数。轨道绘制//绘制公转轨道 void Star::DrawOrbit() { glPushMatrix(); //公转轨道倾角 glRotatef(this-revolution_angle, rotaVector[this-ID][0], rotaVector[this-ID][1], rotaVector[this-ID][2]); glDisable(GL_LIGHTING); glColor3f(1.0f,1.0f, 1.0f); //设置线段绘制颜色 GLfloat r this-revolution_radius; GLfloat x1, z1, x2, z2, y 0; int NUM (((int)r / 8000) 1 ) * 360; for (int i 0; i NUM; i 2) { x1 cos(1.0 * i / NUM * 2 * M_PI) * r; z1 sin(1.0 * i / NUM * 2 * M_PI) * r; x2 cos(1.0 * (i1) /NUM * 2 * M_PI) * r; z2 sin(1.0 * (i1) / NUM * 2 * M_PI) * r; glBegin(GL_LINES); glVertex3f(x1, y, z1); glVertex3f(x2, y, z2); glEnd(); } glEnable(GL_LIGHTING); glPopMatrix(); }绘制行星轨道时只需要将绘制号的轨道旋转行星的公转轨道倾角即可。行星的轨道由轨道上的一些线段组成整体效果为轨道上的虚线。绘制行星轨道时根据行星的轨道的长度计算出要绘制线段的数量(NUM )每次绘制时只需要计算出轨道上两点的坐标。由于要绘制没有颜色的线段所以在绘制时需要关掉环境光( glDisable(GL_LIGHTING))绘制完成后再打开。绘制环境光void EnvironmentLight() { // 光源 1 GLfloat sun_light_position1[] { 0.0f, 20000.0f * sizeRate, 0.0f, 1.0f }; GLfloat sun_light_ambient1[] { 0.2f, 0.2f, 0.2f, 1.0f }; GLfloat sun_light_diffuse1[] { 1.0f, 1.0f, 1.0f, 1.0f }; GLfloat sun_light_specular1[] { 1.0f, 1.0f, 1.0f, 1.0f }; glLightfv(GL_LIGHT0, GL_POSITION, sun_light_position1); glLightfv(GL_LIGHT0, GL_AMBIENT, sun_light_ambient1); glLightfv(GL_LIGHT0, GL_DIFFUSE, sun_light_diffuse1); glLightfv(GL_LIGHT0, GL_SPECULAR, sun_light_specular1); // 光源 2 GLfloat sun_light_position2[] { 0.0f, -20000.0f * sizeRate, 0.0f, 1.0f }; GLfloat sun_light_ambient2[] { 0.2f, 0.2f, 0.2f, 1.0f }; GLfloat sun_light_diffuse2[] { 1.0f, 1.0f, 1.0f, 1.0f }; GLfloat sun_light_specular2[] { 1.0f, 1.0f, 1.0f, 1.0f }; glLightfv(GL_LIGHT1, GL_POSITION, sun_light_position2); glLightfv(GL_LIGHT1, GL_AMBIENT, sun_light_ambient2); glLightfv(GL_LIGHT1, GL_DIFFUSE, sun_light_diffuse2); glLightfv(GL_LIGHT1, GL_SPECULAR, sun_light_specular2); glEnable(GL_LIGHT0); //glEnable(GL_LIGHT1); glEnable(GL_LIGHTING); glEnable(GL_DEPTH_TEST); glDisable(GL_COLOR_MATERIAL); }将环境光设置为点光源但是位置至于 y 轴的正方向上效果就像在地图上方放置一个灯泡鼠标控制void ActiveMotion(int x, int y) { if (MouseX 0 || MouseY 0) { MouseX[MouseBuffer] x; MouseY[MouseBuffer] y; return; } Move[RIGHT] x - MouseX[MouseBuffer] - 4; Move[LEFT] x - MouseX[MouseBuffer] 4; Move[UP] y - MouseY[MouseBuffer] - 4; Move[DOWN] y - MouseY[MouseBuffer] 4; std::cout x y std::endl; MouseBuffer (MouseBuffer 1) % MOUSEBUFFERSIZE; MouseX[MouseBuffer] x; MouseY[MouseBuffer] y; SetCerma(); } void SetCerma() { if (Move[RIGHT] 0) { eyeAngle1 2; while (eyeAngle1 360) eyeAngle1 - 360; } if (Move[LEFT] 0) { eyeAngle1 - 2; while (eyeAngle1 360) eyeAngle1 360; } if (Move[UP] 0) { if(eyeAngle2 89) eyeAngle2 2; } if (Move[DOWN] 0) { if(eyeAngle2 -89) eyeAngle2 - 2; } GetCermaLocation(); } void GetCermaLocation() { eyeY eyeR * sin(eyeAngle2 / 360 * 2 * M_PI); eyeZ eyeR * cos(eyeAngle2 / 360 * 2 * M_PI) * cos(eyeAngle1 / 360 * 2 * M_PI); eyeX eyeR * cos(eyeAngle2 / 360 * 2 * M_PI) * sin(eyeAngle1 / 360 * 2 * M_PI); }有一个全局变量的数组用来记录此时用户对鼠标的操控的状态。例如:如果检测出用户向右移动鼠标则将 Move[RIGHT]置为正。如果用户向左移动鼠标则将 Move[LEFT]的值置为负等。由于 GLUT 提供的函数对鼠标的检测时每步都会进行的但是用户在向上移动鼠标时可能会不小心左右波动鼠标微小的波动应当忽略。故设置一鼠标缓冲区每次检测鼠标之前 5 格的位置如果用户的鼠标在之前位置的 4 个像素之内则不视为用户移动了鼠标。随后调用 GetCermaLocation 函数跟新相机的位置坐标。效果用户按下鼠标任意一个键同时移动鼠标镜头会向用户移动的方向移动。键盘控制void NormalKey(unsigned char key, int x, int y) { switch (key) { case w: { if (eyeR 2500.0f * sizeRate) eyeR - eyeR / 50; } break; case s: { if (eyeR 80000.0f * sizeRate) eyeR eyeR / 50; }break; case r: { Run !Run; }break; default: break; } GetCermaLocation(); } void SpecialKey(int key, int x, int y) { switch (key) { case GLUT_KEY_LEFT: eyeAngle1 - 2;break; case GLUT_KEY_RIGHT: eyeAngle1 2; break; case GLUT_KEY_UP: if(eyeAngle2 89) eyeAngle2 2;break; case GLUT_KEY_DOWN: if(eyeAngle2 -89) eyeAngle2 - 2; break; default: break; } GetCermaLocation(); }OpenGL 将一些特殊键和能用 ASCII 码表示的键运用了不同的回调函数。当用户按下普通键‘w’和‘s’则代表拉近和拉远视角每次拉近或拉远的距离为离世界中心位置的 2%。用户按下上、下、左、右方向键时视角会向上下左右移动。按下‘r 键则此时行星移动停止再次按下则重新开始移动。数据设定在资料中查到的太阳系的实际数据经过转换后实际绘制采用的数据四、结果与分析