OpenGL 地形生成 - 简介






4.59/5 (26投票s)
2006年5月18日
10分钟阅读

337125

26815
此代码将演示如何从位图文件生成地形。
引言
我一直对计算机图形及其应用很感兴趣。数据表示和可视化是人机交互 (HCI) 的主要领域之一,人机交互越好,人和机器的生产力就越高。我在本科期间在加州理工大学学习时,有一些 OpenGL 的经验。不幸的是,考虑到我的时间和工作职责,我从未有机会深入学习 OpenGL 库的高级功能。
您可以在 www.opengl.org 上找到更多关于 OpenGL 的信息。此外,还有许多关于计算机图形学和 OpenGL 的优秀文献可供您参考。请查看“背景/参考”部分,了解我通常用于计算机图形学的一些参考资料。
以下项目是一个非常简单的示例,演示如何基于位图文件生成地形。项目的目标是基于某个数据文件生成三维地形。请注意,这可以是任何数据文件,但为了我们的示例,我们将使用一个 32x32 的位图文件。我们也可以很容易地使用文本文件,并为每个单词或字母定义逻辑以图形方式表示它。
该项目还包含一个很好的 Windows 框架,可用于您的其他 OpenGL 应用程序。当前项目允许您使用鼠标旋转相机。
再说一遍,这是一种简单的地形生成方法,在复杂环境中,地形生成可能是一项非常困难的任务。
背景/参考
由于计算机图形学是一个相对高级的主题,因此至少需要对该领域的概念和理论有一定的理解和接触。但是,这并不意味着您无法使用以下代码或理解它。我已经将其编写得尽可能简单,并希望它能为您提供一个好的起点,或一些额外的可用信息源用于您的项目。另外,请注意,您需要对 C/C++ 编程有很好的理解。
我学习计算机图形学和 OpenGL 编程时使用的一些书籍
我在加州理工大学学习时使用的书籍
- OpenGL 编程指南,或更广为人知的“红宝书”。
- 使用 OpenGL 的计算机图形学,第二版。
我在加州路德大学学习时使用的书籍
- OpenGL:入门,第二版。
- 交互式计算机图形学:自顶向下方法(使用 OpenGL),第四版。
什么是地形?
关于地形及其在游戏应用中的用途的一些背景信息:地形是渲染场景中最关键的组成部分之一。它很容易成为项目中最大的 3D 对象。渲染地形可能是一项艰巨的任务,需要花费最多时间进行渲染。要使地形引擎实时运行可能很困难,需要经过仔细的思考和建模才能使其足够高效。
为了有效,地形需要满足许多可能相互矛盾的要求。地形应该对最终用户看起来是连续的,但网格应在可能的情况下进行简化或剔除,以减少显卡的负担。
例如,在游戏系统中,一些引擎绘制的地形略超出玩家可达范围,然后使用绘制在天空盒上的地形来模拟远处的山丘或山脉。地形应该看起来与环境的设置相符,但这对显卡来说可能很耗费资源,因此需要保持平衡。细节纹理通常靠近相机使用,以便更快地渲染远处区域。
什么是高度图?
地形渲染所需的第一件事是地形形状的表示。虽然有许多结构可以完成这项工作,但最常用的是高度图。其他方法包括:NURBS,可以通过一系列控制点来维护;以及体素,它们允许悬垂和洞穴。
高度图有一个缺点,那就是,对于 XZ 平面上的每个点,只能有一个高度值。您可以看到,这限制了高度图对悬垂和洞穴的表示。可以通过使用两个单独的模型来克服这一点。
另一个缺点是高度图占用大量内存,因为每个高度都需要表示。另一方面,高度图很容易创建规则的网格。确定任何给定位置的高度也很容易,这对于与地形的碰撞以及在地形上放置动态阴影非常有用。
高度图由一个二维值数组表示,其中对于任何点 (X, Y, Z),X 和 Z 是数组的索引,数组的值是 Y 值,相当于高度值。
以下是此类表示的一个示例
int height[5][5] ={ { 0, 0, 1, 1, 2 }, { 0, 1, 2, 2, 3 }, { 0, 1, 3, 2, 3 }, { 0, 1, 2, 1, 2 }, { 0, 0, 1, 1, 1 } }; |
|
方法!
有许多高级算法可以生成地形;我正在为本项目使用一个非常简单的解决方案。
简而言之,我使用了一个 32x32 的灰度位图来表示用于生成地形的高度场。地形本身被划分为一系列高度值网格,结果是在场景中代表地形的网格。
我们创建了一个顶点网格,这些顶点间隔均匀,但具有不同的高度,基于高度场数据。每个位的颜色值用于确定每个网格位置的高度值;在这种情况下,对于 24 位灰度位图,颜色值的范围从 0 到 255。
一旦读取了位图并将值加载到内存中,我们就拥有了表示地形所需的数据。我们还使用一个名为 MAP_SCALE
的变量来允许我们放大或缩小地图。这是一个缩放因子;我们使用它来设置每个高度顶点之间的距离。这允许我们增加或减小地形的大小。
当我们实际为每个网格位置分配顶点坐标时,我们需要应用 MAP_SCALE
因子,该因子将其与基于坐标元素的网格位置索引相乘,即:
Terrain[X][Z][0] = Float(X)*MAP_SCALE; Terrain[X][Z][1] = (float) imageData[(Z*MAP_SCALE+X)*3]; Terrain[X][Z][2] = Float(Z)*MAP_SCALE;
地形图以高度值网格的形式表示,该网格在内部存储在二维顶点坐标数组中。它沿着 X 和 Z 轴延伸,Y 轴代表地形高度。
为了渲染地形图,我们为沿 Z 轴的每个网格值行使用 GL_TRIANGLE_STRIP
。为了正确渲染地形,我们需要以特定的顺序指定点。
这要求我们从行的末端开始,沿着正 X 轴移动,以 Z 字形绘制顶点。
以下是用于生成高度场的位图样本
以下是基于位图文件生成的地形输出
Using the Code
我将仅在此处列出与地形生成相关的代码。项目中还有更多代码供您查看。它文档齐全,所以您应该不会有任何问题。该解决方案是使用 MS Visual Studio 2003 编译的,因此您应该可以轻松地编译和运行它。您需要拥有 OpenGL 库和 DLL,我也会提供下载选项,以防您没有。让生活轻松一点,这样您就不必在线搜索它们了。
因此,大部分代码用于正确准备窗口以渲染场景。正如您在下面看到的,用于生成和渲染地形的代码非常简短。概述一下,首先发生的是创建窗口,然后初始化 OpenGL,读取 BMP 文件并将其分配给上面讨论的二维数组。然后,纹理被应用到网格表面,场景渲染到屏幕上。
废话不多说,以下列表是初始化和生成地形的代码部分。
. . . // InitializeTerrain() // desc: initializes the heightfield terrain data void InitializeTerrain() { // loop through all of the heightfield points, calculating // the coordinates for each point for (int z = 0; z < MAP_Z; z++) { for (int x = 0; x < MAP_X; x++) { terrain[x][z][0] = float(x)*MAP_SCALE; terrain[x][z][1] = (float)imageData[(z*MAP_Z+x)*3]; terrain[x][z][2] = -float(z)*MAP_SCALE; } } } . . .
上面的代码是我们讨论的应用 MAP_SCALE
的实现,它允许我们根据自己的喜好缩放地形。因此,它基本上为每个网格位置分配顶点坐标,MAP_SCALE
因子,该因子将其与基于坐标元素的网格位置索引相乘。它沿着 X 和 Z 轴延伸,Y 轴代表地形高度。
该函数在位图从 Initialize()
函数加载到内存后调用。
. . . // Render // desc: handles drawing of scene void Render() { radians = float(PI*(angle-90.0f)/180.0f); // calculate the camera's position // multiplying by mouseY makes the // camera get closer/farther away with mouseY cameraX = lookX + sin(radians)*mouseY; cameraZ = lookZ + cos(radians)*mouseY; cameraY = lookY + mouseY / 2.0f; // calculate the camera look-at coordinates // as the center of the terrain map lookX = (MAP_X*MAP_SCALE)/2.0f; lookY = 150.0f; lookZ = -(MAP_Z*MAP_SCALE)/2.0f; // clear screen and depth buffer glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); // set the camera position gluLookAt(cameraX, cameraY, cameraZ, lookX, lookY, lookZ, 0.0, 1.0, 0.0); // set the current texture to the land texture glBindTexture(GL_TEXTURE_2D, land); // we are going to loop through all // of our terrain's data points, // but we only want to draw one triangle // strip for each set along the x-axis. for (int z = 0; z < MAP_Z-1; z++) { glBegin(GL_TRIANGLE_STRIP); for (int x = 0; x < MAP_X-1; x++) { // for each vertex, we calculate // the grayscale shade color, // we set the texture coordinate, // and we draw the vertex. // draw vertex 0 glColor3f(terrain[x][z][1]/255.0f, terrain[x][z][1]/255.0f, terrain[x][z][1]/255.0f); glTexCoord2f(0.0f, 0.0f); glVertex3f(terrain[x][z][0], terrain[x][z][1], terrain[x][z][2]); // draw vertex 1 glTexCoord2f(1.0f, 0.0f); glColor3f(terrain[x+1][z][1]/255.0f, terrain[x+1][z][1]/255.0f, terrain[x+1][z][1]/255.0f); glVertex3f(terrain[x+1][z][0], terrain[x+1][z][1], terrain[x+1][z][2]); // draw vertex 2 glTexCoord2f(0.0f, 1.0f); glColor3f(terrain[x][z+1][1]/255.0f, terrain[x][z+1][1]/255.0f, terrain[x][z+1][1]/255.0f); glVertex3f(terrain[x][z+1][0], terrain[x][z+1][1], terrain[x][z+1][2]); // draw vertex 3 glColor3f(terrain[x+1][z+1][1]/255.0f, terrain[x+1][z+1][1]/255.0f, terrain[x+1][z+1][1]/255.0f); glTexCoord2f(1.0f, 1.0f); glVertex3f(terrain[x+1][z+1][0], terrain[x+1][z+1][1], terrain[x+1][z+1][2]); } glEnd(); } // enable blending glEnable(GL_BLEND); // enable read-only depth buffer glDepthMask(GL_FALSE); // set the blend function // to what we use for transparency glBlendFunc(GL_SRC_ALPHA, GL_ONE); // set back to normal depth buffer mode (writable) glDepthMask(GL_TRUE); // disable blending glDisable(GL_BLEND); glFlush(); // bring backbuffer to foreground SwapBuffers(g_HDC); } . . .
您在 Render()
函数中看到的第一件事是将角度转换为弧度,使用公式:radians = float(PI*(angle-90.0f)/180.0f);
。这使得使用 sin()
和 cos()
函数计算 cameraX
、cameraY
和 cameraZ
位置更容易。
下一块代码将摄像机的视点设置在地Hình中心。然后,我们使用 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
清除屏幕和深度缓冲区。我们根据摄像机 (X, Y, Z) 值和计算出的视点来设置摄像机位置。
然后我们使用 glBindTexture(GL_TEXTURE_2D, texture);
函数绑定纹理。这告诉 OpenGL 我们将使用该特定纹理来绘制我们的表面。
到目前为止,所有代码只是用于设置摄像机位置和绑定纹理,下一块代码才是实际绘制地形并将纹理应用到表面。我们有两个 for
循环,它们遍历我们创建的存储地形数据的二维数组,正如我们之前讨论过的,我们一次处理四个顶点。在此过程中,我们计算灰度色调,设置纹理,然后绘制顶点。
给定一个 24 位位图文件,这就是生成地形所需的大部分内容。
关注点
如果您正在阅读这篇文章,那么您很可能对计算机图形学感兴趣,并希望更多地了解可用于进行真正酷炫工作的技术。计算机图形学在理论上可能非常复杂,但感谢 OpenGL 等库,可以轻松实现复杂的模型/场景。在撰写本文时,我收到了最新一期的 Dr. Dobb's Journal(2006 年 6 月,第 385 期),令我惊讶的是,有一篇关于 OpenGL 和移动设备的文章。它名为 OpenGL ES,是 OpenGL 1.3 的一个子集。这将使得在手持设备上进行实时 3D 图形成为可能。想象一下为您的 PDA 或手机开发多少精美的应用程序/游戏!由于手持硬件的限制,并非所有功能都可用。但我认为,在不久的将来,您将能够在手持设备上创建与在普通台式机上一样迷人的图形。
另外,我将开始撰写一篇新文章,介绍 LoadBitmapFile(char *filename, BITMAPINFOHEADER *bitmapInfoHeader)
函数。我期待听到您对未来文章的评论和建议。