读取Honolulu地形的txt文件,并为其添加上天空盒和模型反光效果
实现效果如下所示:
Honolulu地形读取
完成地形读取的思想路线:
由于txt文本中的前两个是矩阵的行列值,因此我们读取地形矩阵的大小,之后按照对应位置遍历得到相应位置的高度,这样我们便能获取真个地形图。
完成过程可以有一下几步:
- 1.我们读取txt文件的信息。
我们读取前两个获取地形矩阵的大小,然后我们使用vector方法将地形的高度值记录下来。这里我们用到了c++的ifstream类的方法,并且向右移位操作,当遇见空格的时候回停止,然后读取每个空格间隔的数据,并使用atof转为数字,但是该函数得到的结果为double类型,这时候强制转为float。最后完成操作,关闭文件。
具体代码如下:
ifstream infile;
infile.open(filename, ios::in);
assert(infile.is_open()); //若失败,则输出错误消息,并终止程序运行
char buf[1024] = { 0 };
vector<float> height;
int map_size[] = { 0,0 };//行+列
int num_data = 0;
while (infile >> buf)//>>操作会在空白处停止
{
if (num_data < 2) {
map_size[num_data] = (float)atof(buf);
}
else
{
height.push_back(atof(buf));
}
++num_data;
}
infile.close();
- 2.构建地形数据。
由于我们想要通过光照看到该地形,就需要对其法向量进行计算,即反射阳光得到我们想要的地形效果。
在计算时,由于该地形数据为436465大小的,边缘和四个角的法向量带来的视觉效果非常小,因此只计算了中间的434463个点的法向量值,避免巨大的计算带来的微弱提升。
计算法向量时,例如我们要计算a点的法向量,我们找到其上下左右四个邻域的值,然后计算四邻域到中心点a的四个向量相加,即可得到,如在a、a0、a2三个点组成的平面中通过计算出v1、v2,另外的同理计算出另外两个,最后相加得到结果。
同时为了更好的显示效果,可以对其纹理进行赋值,即每个点赋予(0~1)的x、y坐标,使得整个地形的2万多个点映射到整个矩形图片中,以此完成纹理赋值。
具体代码如下:
int r_map = map_size[1], c_map = map_size[0];
num_vertices = r_map * c_map;
num_indices = (r_map - 1) * (c_map - 1) * 2 * 3;
CMeshVertex *vertices=new CMeshVertex [num_vertices];
GLuint *indices=new GLuint [num_indices];
unsigned int i = 0;
for (int row = 0; row < r_map; ++row) {
for (int column = 0; column < c_map; ++column) {
vertices[row * c_map + column].pos = vec3(column*scale_size, row*scale_size , height[row * c_map + column]*scale_height);
vertices[row * c_map + column].color = vec4(1.0f, 1.0f, 1.0f, 1.0f);
vertices[row * c_map + column].texcoord.x =(float) (r_map-row) / (float)r_map;
vertices[row * c_map + column].texcoord.y = (float)(c_map-column) / (float)c_map;
vertices[row * c_map + column].normal = (row != 0 && column != 0 && row <= (c_map - 1) && column <= (r_map - 1))?
normalize(4 * vec3(0,0,vertices[row * c_map + column].pos.z )
- vec3(-scale_size,0,vertices[row * c_map + column - 1].pos.z)
- vec3(0, scale_size,vertices[(row - 1) * c_map + column].pos.z)
- vec3(0, -scale_size, vertices[(row + 1) * c_map + column].pos.z)
- vec3(scale_size,0,vertices[row * c_map + column + 1].pos.z) )
: vertices[row * c_map + column].normal;
indices
if (row != 0 && column != 0) {
indices[i * 6] = row * c_map + column;
indices[i * 6 + 1] = row * c_map + column - 1;
indices[i * 6 + 2] = (row - 1) * c_map + column - 1;
indices[i * 6 + 3] = row * c_map + column;
indices[i * 6 + 4] = (row-1) * c_map + column-1;
indices[i * 6 + 5] = (row - 1) * c_map + column;
++i;
}
}
}
- 3.替换掉长方形地形并变换其他模型
为了更好地显示效果,我缩放了地形,并移动了其他几个模型(例如地球放大并移动到honolulu的火山口、圆锥变小移动到盒子上方),为了更好的观看整体的效果。
同时在显示了honolulu的地形后,为了配合显示更好的场景,使用ps合成了一张其专属的纹理贴图。(原始图片在texture中也有,都为honolulu为前缀保存)
最后效果如下:
天空盒和模型对其反光
天空盒
立方体贴图是和其它纹理一样的,所以如果想创建一个立方体贴图的话,我们需要生成一个纹理并将其绑定到纹理目标上。这次要绑定到GL_TEXTURE_CUBE_MAP,在这里我们可以使用“面”的概念完成,即立方体展开的六个面,我们分别对其进行赋值,老师的代码里从第一个right开始,然后加一循环,即面的赋值要遵循一下的顺序。
为了方便查看修改,我初始化了独立的shader。使用了单独的skybox纹理渲染程序,可以更直观明白的查看相应的渲方式。
在顶点着色器中,我们获取顶点的位置,如ppt中所说,我们获取的值是局部坐标中的值。用于贴图3D立方体的立方体贴图可以使用立方体的位置作为纹理坐标来采样。当立方体处于原点(0, 0, 0)时,它的每一个位置向量都是从原点出发的方向向量。这个方向向量正是获取立方体上特定位置的纹理值所需要的。正是因为这个,我们只需要提供位置向量而不用纹理坐标了。即我们在天空盒内部时,盒子的坐标就是其局部坐标。
**注意:**我们在使用view矩阵时,由于之前的是带有移动的,我们要去除此部分,使天空盒子不会随着我们移动出现穿出盒子等问题,即构建出一个【非常大的场景】,即其矩阵的第四行和第四列我们是不需要的,因此我们取出其左上角3*3的矩阵,再传给shader:
如下所示为我的顶点着色器:
注意,顶点着色器中很有意思的部分是,我们将输入的位置向量作为输出给片段着色器的纹理坐标。片段着色器会将它作为输入来采样samplerCube。
在片段着色器中,将顶点属性的位置向量作为纹理的方向向量,并使用它从立方体贴图中采样纹理值。
至此,我们便完成了天空盒子的添加
最后的实现效果:
模型的反光
与上边天空盒添加类似的,我们将该纹理再传给模型,然后将模型本身的纹理和该纹理混合便能得到我们想要的追加反光效果。
同时,在模型的shader 中,我们根据观察方向向量I¯和物体的法向量N¯,来计算反射向量R¯。
在采样了两个纹理后,可以通过相乘完成两张纹理的叠加,由于纹理叠加使得模型变暗,因此再乘以系数使得其不至于太暗。
最后的效果如下:
调整了点光源位置使其和天空盒阳光位置一致后,模型和本次天空盒达到完美适配的结果。