原文地址:http://www.videotutorialsrock.com/opengl_tutorial/terrain/home.php
视频下载:http://www.videotutorialsrock.com/opengl_tutorial/terrain/video.flv
文本格式:http://www.videotutorialsrock.com/opengl_tutorial/terrain/text.php
源码下载:http://www.videotutorialsrock.com/opengl_tutorial/terrain/terrain.zip
译文:
表示和加载3D地形
在游戏和其他3D程序中经常需要表现一个3D地形。本课我们将制作下面的地形图:
问题是我们如何描述一个地形。一个最直接而有效的方法是在x-z平面上创建2D网格,并在每个网格点保存地形的高度。这并不能让我们创建任何地形,比如我们不能创建一个完全垂直的墙,或者向后倾斜的墙。但是这种方法还是能做许多工作。
我们自己在代码中给出每个点的高度是困难的。最好是将高度信息保存为文件。最直接的文件类型是灰度图,其中白色表示允许的最大高度,黑色表示允许的最低高度,这种图片叫做高度图(heightmap)。这也是一种很好的方法,因为即使不通过3D的绘制也可以通过图片可以让我们看出地形的大概相貌。下面是我们程序的高度图的放大版本。
虽然我不熟悉,但有一些工具可以修改高度图。我是怎样制作这个高度图的呢?秘密……
代码分析
现在看我们程序如何载入和显示地形的。你会注意到在顶部,我们有#include "vec3f.h",这个文件包含了我自己写的一个特别的向量类"Vec3f",包含3个浮点数。它做所有你希望数组做的事情。你可以使用+和-进行加减,使用*和/进行乘除,使用vec[0],vec[1]和vec[2]获得成员,还有其他的事情,你甚至可以cout一个Vec3f。在vec3f.h文件中你可以看到Vec3f类能做到所有事情。我们将使用Vec3f类保存法向量。
//Represents a terrain, by storing a set of heights and normals at 2D locations
class Terrain {
private:
int w; //Width
int l; //Length
float** hs; //Heights
Vec3f** normals;
bool computedNormals; //Whether normals is up-to-date
这是我们的地形类。它保存宽度和长度,分别表示在x和z方向网格点的数量。使用一个2维数组保存每点的高度和法向量。最后有一个bool变量告诉我们法向量数组是否是正确的法向量。我们想先设置所有的高度值,然后计算所有的法向量,因此法向量还没有计算。
Terrain(int w2, int l2) {
w = w2;
l = l2;
hs = new float*[l];
for(int i = 0; i < l; i++) {
hs[i] = new float[w];
}
normals = new Vec3f*[l];
for(int i = 0; i < l; i++) {
normals[i] = new Vec3f[w];
}
computedNormals = false;
}
这是地形类的构造函数,初始化所有的变量。
~Terrain() {
for(int i = 0; i < l; i++) {
delete[] hs[i];
}
delete[] hs;
for(int i = 0; i < l; i++) {
delete[] normals[i];
}
delete[] normals;
}
接下来是析构函数,删除两个hs数组和normals数组。
int width() {
return w;
}
int length() {
return l;
}
返回地形长和宽的方法。
//Sets the height at (x, z) to y
void setHeight(int x, int z, float y) {
hs[z][x] = y;
computedNormals = false;
}
//Returns the height at (x, z)
float getHeight(int x, int z) {
return hs[z][x];
}
这两个方法可以让我们在特定点设置和获取高度值。
//Computes the normals, if they haven't been computed yet
void computeNormals() {
//...
}
这个函数计算每个点的法向量,我们稍后再看。
//Returns the normal at (x, z)
Vec3f getNormal(int x, int z) {
if (!computedNormals) {
computeNormals();
}
return normals[z][x];
}
};
这里是返回某些点的法向量的方法。
//Loads a terrain from a heightmap. The heights of the terrain range from
//-height / 2 to height / 2.
Terrain* loadTerrain(const char* filename, float height) {
Image* image = loadBMP(filename);
Terrain* t = new Terrain(image->width, image->height);
for(int y = 0; y < image->height; y++) {
for(int x = 0; x < image->width; x++) {
unsigned char color =
(unsigned char)image->pixels[3 * (y * image->width + x)];
float h = height * ((color / 255.0f) - 0.5f);
t->setHeight(x, y, h);
}
}
delete image;
t->computeNormals();
return t;
}
这是从一个图像载入地形的函数。首先调用loadBMP函数从文件载入位图。然后遍历像素数组,并将其值设为地形的高度。颜色为0对应-height/2,颜色255对应高度height/2。我们使用哪种颜色并不重要,比如红色。然后删除图像,计算所有的发法向量。
现在跳到drawScene函数中。
float scale = 5.0f / max(_terrain->width() - 1, _terrain->length() - 1);
glScalef(scale, scale, scale);
glTranslatef(-float(_terrain->width()) / 2,
0.0f,
-float(_terrain->length()) / 2);
缩放我们的地形,使之最多5个单位宽和长。然后做变换,将其放到中央。