给别人讲了大半学期的OpenGL和图形学了,忍不住手痒自己也用OpenGL来写点什么。绘制一个太阳系作为用来练手的OpenGL项目,再合适不过了,可以做到形状绘制,相机投影,光照贴图五脏俱全。
起手写的时候尝试着从底层的那些东西来入手,手撕各种shader、图元和矩阵变换,然而创业未半而中道偷懒,shader还是太硬核了一时半会玩不来,还是觉得用现成的函数来的舒服,反复造轮子是对生产力的极大浪费不是嘛XD
好了不废话,开始主题
Requirement & Environment
- OpenGL
- freeglut
- GLEW
- SOIL(用于读取图片信息的一个库)
- Visual Studio 2015
- Win10 C++
Camera & Perspective
相机和投影视角是图形学的重要内容,我们这里先简单的使用透视投影以及可以由鼠标操控的相机,主要的两个函数就是gluPerspective()和gluLookAt(),对应到图形学里的投影矩阵和模视变换矩阵。对于相机的视点参数,也就是相机的位置,我们使用三角函数来构建球形环绕轨迹,使得通过拖动鼠标可以围绕着某一点转动相机视角。于是有:
1 | void RenderScene(void) |
其中glLoadIdentity();是对当前矩阵恢复初始化。
关于鼠标操作,用OpenGL回调函数glutMouseFunc(Mouse)和glutMotionFunc(onMouseMove)来调用如下函数:
1 | void Mouse(int button, int state, int x, int y) //处理鼠标点击 |
Sun & Earth & Moon
对于各种天体的绘制使用gluSphere()函数来实现非常简单,利用glTranslatef()来调整位置,利用glRotatef()来改变不同的公转自转以及倾斜角,由于这些矩阵操作都是对当前的矩阵进行操作的,为了不使每个星球的位移与旋转操作相互影响,我们使用glPushMatrix()来将矩阵入栈之后再修改,当前星球的操作结束后,将先前保存的矩阵glPopMatrix()出栈,来恢复位置状态。
1 | void sun() |
Texture & Material
贴图和材质直接决定了绘制图形的美观程度。上一步中构建太阳地球月亮的时候已经调用了材质和贴图函数,现在来具体看看内部实现。
材质
材质上,由于太阳在我们的系统里是充当着光源的角色,所以材质与其他天体是有区别的。太阳应该是一个发光体,所以关键要加上一句glMaterialfv(GL_FRONT, GL_EMISSION, sun_mat_emission);使其自发光
1 | void material_sun() |
对于其他星球,我们则可以用统一的材料,辐射光设为0使其不自发光,其他的根据个人喜好调节参数。
1 | void material_planet() |
不过在这个环节我曾经被一个很蠢的问题卡了很久,就是一开始不知道为什么怎么调地球明暗面都非常不明显,总以为是哪个参数设错了,折腾了好久发现,原来是选的贴图本身就很暗,导致看不出阴影效果,换了张贴图就可以了,我无语。
贴图
加载贴图数据有很多种方法,那么这里我选择使用SOIL.h这个库来读取一张图片,然后用glGenTextures(1, &name)和glBindTexture(GL_TEXTURE_2D, name)来为这张贴图绑定一个名字,glTexImage2D()生成贴图数据。通过我们绑定的名字就可以为图形进行贴图。当然这里的名字是自动生成的,可以保证唯一对应。
1 | GLuint load_texture(const char *path) { |
Skybox
多数游戏和3D场景都有一个天空盒来作为背景,简单来讲天空盒就是一个把你绘制的所有物体包裹起来的一个带贴图的立方体,在这个例子里就是星空背景。这个立方体要一个一个面画,因为实际上每个面的贴图应该是连续且不重复的全景,但是这里我就偷懒全部用一张图片了,反正背景都是星星也看不出特别违和。天空盒的中心应该是我们的相机的位置,这样无论相机怎么移动,背景都不会有相对的大小变化,来形成一种背景无限远的感觉。画天空盒时先使用glDepthMask(GL_FALSE)来取消深度,可以使我们绘制的物体永远处于前景。
1 | void draw_milky_way(float x, float y, float z, float width, float height, float len) |
To Be Continue
搞定上述这些模块,我们就已经可以画出像模像样的场景了,完整代码在这里,但是毕竟代码写得非常简陋(甚至丑陋),还存在着一些比较难以忽视的问题,比如如果使用的贴图分辨率过高会使运行卡顿等等,还有多其他几个行星还没画,这些留着下次解决。
话说图形学的代码也真是冗长啊,实现这一点点小场景就已经四百行代码了,下次把代码结构也一并优化一下。