根据下文链接下载源代码后,再阅读下文,将有对源代码以及整个实现过程的详细解释,同时对该文理解通透的同学,可将这份代码进行修改,构造属于自己的3D场景,模板性极强。
下载链接:https://download.csdn.net/download/weixin_41824716/12090877
按照说明书,将“FreeImage所需文件“里的文件导入到相应位置;
将头文件和cpp文件导入项目中,记得"include"文件夹也要导入。
(还没配置基本OpenGL库的童鞋,请去翻我的第一篇关于OpenGL的博客。)
先不说那么多,估计现在各位最想先看到的就是效果图:
目录
一、3D模型导入
1.1 读取obj文件
1.2将读取的obj文件信息存进顶点数组中
1.3绘制3D模型
二、读取纹理贴图
三、场景渲染
3.1 读取模型并纹理贴图
3.2 初始化人物位置和视点
3.3 绘制地面
3.4 绘制城堡
3.5 绘制天空盒
3.6 绘制太阳和月亮
3.7 绘制左右守卫
3.8 实时光照
3.9 实时阴影(很重要哟)
3.9.1 守卫阴影
3.9.2 城堡阴影
四、用户交互操作
4.1 按键盘w前进、s后退
4.2 按键盘a向左转换视角,d向右转换视角
4.3 按键盘i向上转换视角,k向下转换视角
4.4 按键盘小写p可加速一天进程,大写p减速一天进程
4.5 按键盘q可上升地面,e可下降地面
obj文件的格式:开头为v时,后面跟着的是顶点xyz坐标;开头为vn时,后面跟着的是发现xyz坐标;开头为vt时,后面跟着的是xyz坐标(有些obj坐标没有z坐标,则默认为0);开头为f时,后面跟着的是一组顶点、纹理、法线索引,当组数为3时,表示这是一个三角面,当组数为4时,表示这是一个四角面。
根据obj文件格式进行信息拆分读取,将信息储存在相应数组中,如以下代码:顶点、法线、纹理依次存储在临时数组vertexs、normals、textures中,而三角面存储在trivertexsList中,四角面存储在quadvertexsList中。
//读取obj文件
void ObjLoader::loadObjFile(std::string filename){......}
按照三角面和四角面的索引,将临时数组中对应的顶点、法线、纹理坐标存储在triVertexs和quadVertexs中,这一步相当于生成VAO。
//将读取的obj文件信息存进顶点数组中
void ObjLoader::transferVAO(){......}
调用glEnableClientState启用顶点、纹理、法线数组,将对应三角面和四角面的顶点、纹理、法线数组和点各处传入,绘制3D模型。
//绘制3D模型
void ObjLoader::renderObj(){......}
//读取纹理贴图
void Texture::load_texture_FreeImage(std::string file_name, GLuint& m_texName){......}
调用loadObjFile读取模型,并调用transferVAO将数据传给openGL。之后调用glGenTextures生成对应纹理贴图标号,然后调用load_texture_FreeImage生成纹理。
void init()
{
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//城堡模型及纹理
objLoader[0].loadObjFile("texture/cartooncastle.obj");
objLoader[0].transferVAO();
glGenTextures(1, &textures[0]);//生成纹理贴图标号
loadTexture.load_texture_FreeImage("texture/cartooncastle.jpg", textures[0]);//调用FreeImage生成纹理
//地形模型及纹理
。。。
//守卫模型
。。。
//太阳模型及纹理
。。。
//月亮纹理
。。。
//天空盒模型
。。。//星空纹理
。。。//晴天纹理
。。。//黄昏纹理
glEnable(GL_LIGHTING);//开启光照
glEnable(GL_DEPTH_TEST);//开启深度测试
}
调用glMatrixMode指明操作投影矩阵或模型矩阵,传入对应的投影矩阵或模型矩阵,每操作一次后,都要调用glLoadIdentity指定当前矩阵为单位矩阵,即等同于多个矩阵相乘。后序所有模型都在视点变换之后进行生成。
//初始化人物位置和视点
void originalPosition(){......}
(下面的模型绘制均在main函数中,下文已给出大致代码,请自行对应查找)
//绘制复杂地面//
if (groundFlag == 1)
{
glEnable(GL_TEXTURE_2D);//启用纹理贴图
。。。
。。。
。。。
objLoader[1].renderObj();//生成地面模型
glPopMatrix();
glDisable(GL_TEXTURE_2D);
glDisable(GL_COLOR_MATERIAL);
}
现将模型通过translate或scale或rotate变换到指定位置,然后绑定指定纹理,随后调用renderObj生成对应模型(接下来的模型绘制都是这个步骤,下面不再叙述)。
//绘制城堡//
if (roomFlag == 1)
{
glEnable(GL_TEXTURE_2D);//启用纹理贴图
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);//设置纹理与材质的关系
。。。
。。。
。。。
objLoader[0].renderObj();//生成城堡模型
glPopMatrix();
glDisable(GL_TEXTURE_2D);
glDisable(GL_COLOR_MATERIAL);
}
由day变量控制一天的进程,skyFlag会被赋予不同值,使得天空在不同时间段赋予不同的纹理贴图,实现一天内天空变化的效果。
//天空盒
glEnable(GL_TEXTURE_2D);//启用纹理贴图
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);//设置纹理与材质的关系
glPushMatrix();
。。。
。。。
。。。
//黄昏
if (skyFlag == 1)
glBindTexture(GL_TEXTURE_2D, textures[7]);
objLoader[6].renderObj();
glPopMatrix();
glDisable(GL_TEXTURE_2D);
glDisable(GL_COLOR_MATERIAL);
//太阳和月亮随着时间围绕z轴旋转
glPushMatrix();
glRotatef(day / 360.0*360.0, 0.0f, 0.0f, -1.0f);//太阳和月亮随着时间围绕z轴旋转
glScalef(0.1, 0.1, 0.1);
//绘制太阳
。。。
。。。
。。。
objLoader[3].renderObj();//生成月亮模型
glPopMatrix();
glDisable(GL_TEXTURE_2D);
glDisable(GL_COLOR_MATERIAL);
//左边守卫
glPushMatrix();
。。。
。。。
//右边守卫
glPushMatrix();
。。。
。。。
objLoader[2].renderObj();//生成守卫模型
glPopMatrix();
在绘制太阳模型时,获取到了太阳的实时位置now,将其作为光照。
而day用来控制1天,这里用角度360代表一天。
//实时光照
position0[0] = now.x; position0[1] = now.y; position0[2] = now.z; position0[3] = now.w;
glLightfv(GL_LIGHT0, GL_POSITION, position0);
glLightfv(GL_LIGHT0, GL_SPECULAR, light0s);
glLightfv(GL_LIGHT0, GL_DIFFUSE, light0d);
glLightfv(GL_LIGHT0, GL_AMBIENT, light0a);
//自动计时
void myIdle(void)
{.......}
提供投影面的法向量以及光源位置,进行如下代码所示的计算,得到阴影矩阵matrix。
//生成阴影矩阵
void generate_shadow_matrix(GLfloat matrix[4][4], const GLfloat ground[4], const GLfloat light[4])
{......}
代码大致上与绘制守卫模型相同,不同的是要关闭光照和纹理映射,以及在变换和绘制守卫模型之前,生成实时阴影矩阵,调用glMultMatrixf将阴影矩阵传入,并设置阴影颜色为黑色,则投影面会显示其阴影。
//左边守卫阴影
glDisable(GL_LIGHTING);
glPushMatrix();
...
...
//右边守卫阴影
glDisable(GL_LIGHTING);
glPushMatrix();
...
...
glPopMatrix();
glEnable(GL_LIGHTING);
if (roomFlag == 1)
{
//城堡阴影
glDisable(GL_LIGHTING);
...
...
...
glPopMatrix();
glEnable(GL_LIGHTING);
}
先计算人物位置与视点的距离,再得到xyz方向的角度值,乘以步长,再加上现有位置,得到的结果便是向视点方向移动后的位置,同时视点也移动同样距离。
定义r和theta,使视点在指定半径内旋转,而加上现有位置后,则使视点以现有位置为圆心。
只改变视点y方向上的值,xz方向上的不变。
控制daySpeed,是day增加到360的速度变慢即可。
//人机交互操作
void keyboard(unsigned char button, int x, int y)
{......}
PS:有不懂的童鞋可给博主留言噢!博主有空的话会帮各位解答!