第一章 绪论
1.1概述
众所周知,近几年以电子游戏为主的计算机互动娱乐产业迅猛发展,已成为国民经济的重要组成部分。然而,由于我国的软件业起步较晚,3D游戏的核心技术被欧美、日韩等牢牢占据。如何掌握相关核心技术已成为中国电子娱乐产业的当务之急。
室外场景渲染是指对户外所有景物的真实绘制,相对于室内场景渲染,它更为复杂。在3D游戏特别是网络在线游戏中,室外大场景渲染是一块非常重要的内容,它也是3D图形引擎的核心(见图1.1)。游戏引擎中大场景的渲染技术是图形学和图像处理理论的最直接应用,其涉及的技术还可以应用于其它领域,比如虚拟现实、3D GIS、数据可视化等,其重要性不容置疑。
1.1.1室外场景渲染研究的内容和难点
真实感的室外场景除包括地表的基本几何形状绘制,还包括地表上的生物如:草、岩石、树等的渲染以及对光影效果的表现。其中的难点主要在大规模地表的数据处理和真实感的仿真。地形场景中模型图元的数量是以场景大小平方的速度增长的。一个8193×8193维数的地形,如果不考虑减低细节程度和裁剪,绘制一帧将要渲染128M个三角形,这样的三角形量在PC级别的机器上目前还是远不能实现交互式帧率的。所以,如何减少渲染地形的图元数目一度成为室外场景实时渲染的关键问题。随着硬件技术的发展,每秒钟处理上亿个三角形已不再困难。很多计算如几何变换和光栅处理都可以交给GPU去计算。在GPU数据吞吐量很大的情况下,如果~个算法在剔除渲染图元的过程中占用了太多CPU资源,出现GPU等待CPU的情况,那么即使算法在剔除多余顶点方面做得很好,总体绘制效率也不是高效的。所以在目前的显卡硬件架构下,能充分发挥CPU与GPU性能,消除局部效率瓶颈的算法才是最好的算法。
在真实感仿真上,比如对草丛的模拟,如果全部用细节三角形面片,那即使一片草地需要渲染的三角形个数也是难以想像的。目前对物体细节的表现还主要是运用纹理贴图的方式来实现。光影效果的表现涉及局部光照模型、全局光照模型、光照贴图、阴影生成等相关算法和技术。
1.1.2国内外技术发展历程和现状
在国外,3D游戏中场景渲染技术的发展大致可以分为三个阶段:
三维真实感场景渲染领域的研究在国外起步很早,很多相关算法在80—90年代就已提出,并建立了非常严密的理论体系。比如对场景渲染非常重要的多边形LOD算法,场景空间管理的BSP、四叉树、八叉树等算法,光照模型,阴影算法等。但是这些算法在资源有限的微机上受到各方面限制。当时的这些算法还仅限于在大型机和图形工作站上实现。
92年,随着ID Soft公司一款游戏Quake(雷神之锤)的发布,标志着第一款支持多边形模型、动画、粒子系统的全三维游戏在个人计算机上正式诞生。在随后发布的QuakeII、Unreal、Half Life等游戏中,场景渲染相关的LOD技术、BSP空间管理技术、光照贴图、凹凸贴图、多重贴图等相继应用在3D游戏中。随后出现的Quake、Unrea]游戏引擎,更是标志着3D游戏引擎技术走向成熟。
现阶段,随着显卡技术的发展,GPU不但支持标准的(固定的)变换与光照(T&L)管道线,还支持顶点着色(vertex shader)和像素着色(pixel shader) 方式,开发人员因此有了更大的自主空间,可以实现更真实的绘制功能。在此环境下,各大公司都着手开发新一代3D游戏引擎。其中商业引擎以QuakeIII、UnrealII最为出名。此外,软件开源社区的不断壮大,也诞生出一些比较好的开源3D引擎,比如OGRE等。运用这些引擎中的场景渲染技术能生成真实感更强的水波、天气系统、茂密的森林、流熔岩的火山等效果,场景的规模也更大,顶点动态光照技术也开始在室外场景中应用。
3D游戏的趋势是往超大规模场景,真实感表现的虚拟世界发展。大规模场景表现和游戏的虚拟仿真,自然也成为了世界上3D游戏行业非常热门和前沿的研究领域与方向。比如美国暴雪公司的“魔兽世界"和韩国NCSOFT公司的“天堂2"已经在这两个方而取得了相当的突破和进展。暴雪的“魔兽世界"按人的比例来计算,世界地图已达到35km×35km。
随着电子娱乐业的迅猛发展,国内目前从事3D引擎研究和开发的公司也多了起来。比如目标软件的GFX3D引擎,盛大公司的3D引擎,网易公司的3D引擎,锦大科技的AURORA引擎等,还有一些游戏工作组的3D引擎,比如WIN3D系列,Origo系列,TUAM9系列引擎等。但总体上来说,国内还处在使用和模仿优秀引擎的阶段,很多都是在国外优秀引擎上做一些延伸。真正严格意义上自主开发产品还几乎没有,或者说自主开发产品的质量和欧美日韩还有一定差距。这种差距在一定程度上反映在3D大场景渲染技术方面,因此深入的展开这方面的研究十分必要。
1.2课题研究的目的
本课题希望通过剖析多款成熟3D游戏、引擎,对3D引擎的核心部分一室外场景渲染技术进行探讨,并设计和实现自己的一些方案,给我国商业游戏引擎的开发带来一些启示。
1.3论文的篇章结构
本文本着注重实际应用的前提,通过剖析开源3D引擎并运用推测验证的方法,对室外场景渲染的主要技术进行了研究和实现,在很多章节也提出了自己的方案和优化方法。以下对各章的内容进行简要描述。
第一章绪论。介绍3D室外场景渲染的基本概念,介绍国内外在这一领域的研究现状,阐述课题的意义和研究内容。
第二章介绍3D图形渲染的基础知识,包括图形渲染的原理、流程以及实现图形渲染可以应用的类库OpenGL。
第三章研究室外超大场景地形网格的生成与简化算法,比较各种基于LOD思想的网格简化算法的优劣,提出基于几何多重映射(GeoMipMap)的地形绘制优化方案,并得以实现。研究也涉及到地形的空间管理算法、可见性剔除算法、超大场景的数据加载方式研究等。
第四章研究场景的真实感渲染技术。主要涉及应用多层纹理混合贴图模拟融合性地表技术,室外场景的光影效果处理技术,天空、树、草、雾等的渲染方案和技术。
第五章描述本设计实现的室外渲染软件Demo OSRender,以及程序编写过程中可用的优化技术。
第六章对全文进行总结,介绍开题论文的完成情况,客观评价优点和不足,并给出改进的方向。
第二章 3D游戏场景渲染的基础知识
室外场景渲染从基本原理上来说可以分为两类:基于体素的渲染方法和基于多边形的渲染方法。早期的3D游戏,如三角洲特种部队就是采用的体素渲染法。体素法类似光线跟踪渲染,它从屏幕空间出发,找到地形与屏幕像素发出的射线交点,然后确定该像素的颜色。这种方法不依赖具体的图形硬件,整个渲染过程完全使用CPU处理,因此它不能使用图形卡硬件来加速,并且对于一个场景来说,往往不只是地形,还有其他使用多边形描述的物体,体素法渲染的图像很难与硬件渲染的多边形进行混合,因此这种方法现在用得极少,而多边形渲染方法则成为一种主流。选择多边形来描述和渲染地形有很多优点,最主要的是它能够很好地使用硬件加速,并且能够和其他多边形对象一起统一管理。因此本课题仅对基于这种多边形面片的场景渲染技术进行研究。
2.1基于多边形面片的3D渲染原理
2.1.1基于多边形面片的图形绘制流程
用多边形面片来建立物体的三维模型有容易表示、计算简单、容易绘制几个方面的好处,因此在游戏三维图形渲染中被广泛采用(通常是三角形面片)。从模型的顶点送入渲染管道到最后形成二维图像的过程如下图2.1所示:
2.1.2变换
渲染管道中大部分工作是把对象模型的顶点在一个坐标系中的表示转化为另一坐标系中的表示,一般需要经过模型变换和相机变换两个过程。
通常,几何模型被保存在自身的建模空间,即每个模型拥有单独的局部坐标系统。为了建立场景几何关系,模型将统一放置到世界坐标系中,从建模坐标系变换到世界坐标系叫模型变换。几何模型的最终成像是在摄像机坐标下,将场景物体从世界坐标系变换到摄像机坐标系叫相机变换。相机的外部参数决定了相机坐标系,因此场景在屏幕上的成像位置与形状和相机的外部参数有关。模型和相机变换采用4×4齐次矩阵表示,其形式如下:
比如平移变换可表示如下:
点(x,y,z)平移(tx,ty,tz)后的坐标为(x",y",z")。此外还有旋转、缩放变换,这三种基本的变换可以合成任意几何变换。
通常把模型变换和相机变换的矩阵复合成一个矩阵处理,便于提高效率。在所有的图形绘制库中,都提供了程序接口供应用程序设置模型和相机变换对应的矩阵。其中,模型变换由场景物体的平移和旋转、缩放变换组成,相机变换则通过设置相机的位置、相机方向和向上向量来决定。
2.1.3裁剪
相机的参数包括投影方式、近平面、远平面、视野和屏幕的长宽比率,它们决定了物体从相机坐标系投影变换到屏幕坐标系的位置。这些参数实际上定义了一个视域四棱锥,也叫做视锥体。
位于视锥体之外的场景部分不需要送入后续阶段处理。因此,对完全位于规一化的设备空间之外的几何元素,可简单地舍弃。而部分位于规一化的设备空间之外的几何元素则需要进行裁剪操作。由于裁剪的面就是立方体的6个表面,实现起来非常简便。应用程序也可以定义额外的平面对场景进行裁剪。视域裁剪通常由底层图形AP]自动完成。注意这里所说的裁剪是比后面章节要介绍的视锥体剔除更底层的裁剪。
2.1.4投影
把三维物体变为二维图形表示的过程称为投影,其又分为平行投影和透视投影。透视投影是所有投影线交于投影中心;而平行投影是投影线平行,投影中心在无穷远。在游戏虚拟场景模拟中主要应用透视投影。透视投影可分为一点透视、二点透视和三点透视。
针对一点透视投影如下图:
从上图P点在观察平面上的投影我们可以得到描述尸点的参数方程:
2.1.5光栅化
从顶点组成的几何模型变换到像素的过程称为光栅化(Rasterization)。它的机理与得名来源于CRT显示器的电子枪发射方式。光栅化可分为四个子阶段,即消隐、逐像素光照明计算、纹理映射和颜色融合。
(1)消隐
消隐的目的是解决场景的可见性问题。所谓可见性计算,是指计算物体投射到投影平面如果有交叠,观察者应该看到哪个投影点。图形学中经典的解决方案是物体空间Z一缓冲器算法和图像空间的光线跟踪算法。由于Z-缓冲器算法易于在图形硬件中实现,逐渐演化称标准的图形硬件消隐技术。在深度缓冲器中,每个像素上始终保留最接近视点的深度。当光栅化产生新的像素后,该像素的深度和保存在深度缓冲器的像素深度进行比较,如果小于已有的像素深度,则用像素的颜色和深度替换分别保存在颜色缓冲器的像素颜色和深度缓冲器中的像素深度,反之保持不变。在绘制之前,深度缓冲器必须初始化为最远的深度,以保证可见性计算的正确性。
(2)光照计算
光照计算影响物体的外观。进行光照计算的几个要素包括光源位置、光源属性、光照模型、物体表面材质属性、纹理和物体表面几何属性(包括法向、微几何结构)等。最简单的光照明计算技术是在物体建模时指定每个顶点的颜色和纹理坐标,在绘制时直接利用颜色和纹理映射融合为最终颜色。这种方法称为平坦渲染(Flat shading)模式,它的速度快,但效果欠佳,是早期游戏中最常用的技术。
真正意义上的光照计算必须指定每个光源本身的属性,包括光源的类型(点、线、面光源)、位置和光源的漫反射/镜面反射的颜色,然后根据光照模型(分局部光照明模型和全局光照明模型,前面所述直接指定顶点颜色的方法可看作最简单的局部光照明模型)在物体的每个顶点计算每个光源对该顶点的光亮度贡献,最后在光栅化层插值顶点上的颜色。这种处理模式称为Gouraud渲染模式,著名游戏guakeEi就使用了Gouraud渲染模式。
逐顶点的光照计算对应于Gouraud渲染模式,而逐像素光照计算则对应于法向渲染模式(也称为Phong渲染模式)。Phong模式在游戏场景渲染中也被广泛采用。在游戏三维引擎设计中,必须根据图形硬件配置和场景复杂度选择合适的光照渲染模式。
(3)纹理映射
纹理映射是增强场景真实感的一种简单有效的技术。它将预生成的图像直接贴在物体表面,模拟物体表面外观,因此也叫贴图法。纹理映射的扩展技术有很多,包括环境映射、光照图、球面映射、立方体映射、凹凸映射、位移映射等,是图形渲染加速中最重要的手段。在真实感渲染章节会有更详细的讨论。
(4)颜色融合
对于每一个像素,前面步骤可能产生光照计算和纹理映射两类颜色值。不仅如此,光照明计算的结果可能来自多个光源,而每个光源可导致漫反射和镜面反射的光亮度。此外,同一像素也可能采集来自多个纹理的值,如多通道纹理映射和单通道多重纹理映射。所有这些颜色值将根据各自的不透明度融合出最终结果。颜色融合不仅能加强场景真实感,还能产生半透明绘制、景深、基于alpha缓冲器的反走样、软阴影等特效。
2.2.1 OpenGL的基本理解
图形渲染引擎中的最底层可以自己实现,但是没有显卡硬件厂商的支持,自己实现的往往不能得到硬件加速特性。所以游戏中的图形渲染通常需要利用0penGL和DirectX等成熟图形库。现在很多游戏引擎对两个库都提供支持,作为学术研究OpenGL是更好的选择,所以本设计在实现算法时选用了OpenGL。
OpenGL是一个封装了硬件图形加速器的软件接口口7|,几乎全部显卡对它都提够良好支持。它也是图形库的业界标准。OpenGL包括了100多个图形操作函数,开发者可以利用这些函数来构造景物模型、进行三维图形交互软件的开发。也可以说,OpenGL是一个高性能的图形软件开发包。OpenGL支持网络,在网络系统中用户可以在不同的图形终端上运行程序显示图形。OpenGL作为一个与硬件独立的图形接口,它不提供与硬件密切相关的设备操作函数,同时,它也不提供描述类似于飞机、汽车、分子形状等复杂形体的图形操作函数。用户必须从点、线、面等最基本的图形单元开始构造自己的三维模型。当然,象3DS Max那样更高一级的基于OpenGL的三维图形建模开发软件包将提供方便的建模工具。所以,OpenGL的图形操作函数十分基本和灵活。例如OpenGL中的模型绘制过程就包括网格线绘图方式、反走样网格线绘图方式、平面消隐绘图方式、光滑消隐绘图方式、加阴影和纹理的绘图方式等。总的说来,OpenGL的功能包括以下几个层面:
(1)模型绘制
OpenGL能够绘制点、线和多边形。应用这些基本的形体,可以构造出几乎所有的三维模型。
(2)模型观察
在建立了三维景物模型后,需要用OpenGL描述如何观察所建立的三维模型。
(3)颜色模式的指定
OpenGL应用了一些专门的函数来指定三维模型的颜色。
(4)光照应用
用OpenGL绘制的三维模型必须加上光照才能与客观物体更加相似。OpenGL提供了管理四种光(辐射光、环境光、镜面光和漫反射光)的方法,另外还可以指定模型表面的反射特性。
(5)图象效果增强
通过反走样、混合和雾化等函数来增强图象的效果。
(6)位图和图象处理
OpenGL还提供了专门对位图和图象进行操作的函数。
(7)纹理映射
OpenGL提供的一系列纹理映射函数使得开发者可以十分方便地把真实图象贴到景物的多边形上,从而可以绘制逼真的三维景观。
(8)实时动画
为了获得平滑的动画效果,需要先在内存中生成下一幅图象,然后把已经生成的图象从内存拷贝到屏幕上,这就是OpenGL的双缓存技术(double buffer)。OpenGL提供了双缓存技术的一系列函数。
(9)交互技术
OpenGL提供了方便的三维图形人机交互接口,用户可以选择修改三维景观中的物体。
2.2.2 OpenGL的工作流程
整个OpenGL的基本工作流程如2.4图:
其中几何顶点数据包括模型的顶点集、线集、多边形集,这些数据经过流程图的上部,包括运算器、逐个顶点操作等;图像数据包括象素集、影像集、位图集等,图像象素数据的处理方式与几何顶点数据的处理方式是不同的,但它们都经过光栅化、逐个片元(Fragment)处理直至把最后的光栅数据写入帧缓冲器。在OpenGL中的所有数据包括几何顶点数据和象素数据都可以被存储在显示列表中或者立即可以得到处理。OpenGL中,显示列表技术是一项重要的技术。
OpenGL要求把所有的几何图形单元都用顶点来描述,这样运算器和逐个顶点计算操作都可以针对每个顶点进行计算和操作,然后进行光栅化形成图形碎片;对于象素数据,象素操作结果被存储在纹理组装用的内存中,再像几何顶点操作一样光栅化形成图形片元。
在整个流程操作的最后,对图形片元进行一系列的逐个片元操作,最后的象素值送入帧缓冲器实现图形的显示。
2.2.3 OpenGL的程序结构
第一部分是初始化部分。主要是设置一些OpenGL的状态开关,如颜色模式(RGBA或ALPHA)的选择,是否作光照处理(若有的话,还需设置光源的特性),深度检验,裁剪等等。
第二部分设置观察坐标系下的取景模式和取景框位置大小。主要利用了三个函数:函数void glViewport(1eft,top,right,bottom):设置在屏幕上的窗口大小,四个参数描述屏幕窗口四个角上的坐标(以象素表示);函数void glOrtho(1eft,right,bottom,top,near,far):设置投影方式为正交投影(平行投影),其取景体积是一个各面均为矩形的六面体;函数void gluPerspective(fovy,aspect,zNear,zFar):设置投影方式为透视投影,其取景体积是一个平截头体(frustum)。
第三部分是OpenGL的主要部分,使用OpenGL的库函数构造几何物体对象的数学描述,包括点线面的位置和拓扑关系、几何变换、光照处理等等。
以下是第三部分的一个简单例程:
2.3本章小结
场景渲染总的来说分为基于多边形面片和基于体素两种方法。由于基于体素的方法不适应现代硬件的渲染流程,故本设计主要研究的是基于多边形面片的场景渲染。本章从图形学原理出发,讨论了基于多边形面片的3D渲染的基本流程以及其中涉及的数学模型。另外,还介绍了应用广泛的图形库OpenGL,为下面章节的具体算法讨论和软件实现做基础铺垫。
地形的绘制是指读取虚拟世界的地图信息,绘制出场景的地表,并实现角色在场景中实时漫游。它是室外场景实时绘制中最重要的部分,也一直是计算机图形学中一个重要的研究领域。尽管地形的绘制在不同的游戏中所采用技术会有所不同,但是他们总体上还是遵从一定的流程,如图3.1所示:
以下章节会逐步分析相关技术。需要说明的是本章探讨的“绘制”还不包括真实感的表现,可以理解为线框模式下的绘制。
3.1地形绘制所需数据
地形绘制所涉及的数据主要有:地形的高度图、缩放标尺、地表纹理图、地表纹理索引等。在游戏设计中,表现一个场景所需要的一系列数据往往打包放在一起。
3.1.1高度图
对基于三角形面片渲染的3D场景来说,地形的顶点信息就是指组成地形的所有三角形面片每个顶点的三维坐标。最简单最有效的地形顶点表示方法是使用高度图(heightmap)u利。
通常高度图是一张灰度图,它的长宽通常满足(2^n+1)。每个像素的灰度值表示地形相应位置的高度值,用连续的三角形面片来连接这些三维空间中的顶点就构成了地形的面片。高度值的值域范围0--255足以表现游戏中场景的地形起伏,如果需要也可以使用双字节,四字节或更高来描述高度值。在设计中很多游戏由于封装数据的需要,通常自定义高度图的格式,而不采用灰度图,但是其存储的数据本质上是一样的。
3.1.2缩放标尺
地形信息还应包括缩放标尺,用来表示在绘制时高度图中相邻两个灰度值之间相隔的X,Z方向上的距离值。比如一张33×33的高度图的缩放标尺是l米,则在游戏中我们可以看到一个32MX 32M大小的场景。此外,在Y方向上也有一个缩放标尺,负责地形高度的缩放。
3.1.3顶点法向量
地形网格上的各点都需要一个表面法向量。它可以用来计算光照,进行背面剔除,检测与表面的碰撞等。一个三角形的法向量可以通过三角形上两向量叉乘的方法轻松获得,而顶点级法向量可以通过共享此顶点的所有三角形的法向量求平均值来模拟,在很多情况下,这样的效果已经能够达到要求了。顶点处其实是没有法向量定义的,因为此处网格表面不连续。
3.1.4多种地表纹理及光照贴图
为了表现地形的真实感,目前游戏中的做法是通过多重纹理混合贴图来实现的。其中用到的贴图通常以各种图片格式保存。关于这种技术的讨论在真实感渲染章节会详细介绍。
3.1.5单个场景地形的数据结构
由以上的分析我们就可以得到单个场景地形的数据结构,如下所示:
3.1.6面片的构成
任何多边形模型都可以转换成三角形的集合,所以地形网格也是三角形的集合。如果三角形被各自独立地送至图形硬件进行绘制,共享的顶点数据就需要执行重复冗余的运算,并且相同的数据还被传送至少两次以上。降低这些额外开销的一个方法就是把彼此相邻的三角形构建成三角带(strip)。首先,把第一个三角形的三个顶点放至strip之中,然后将其余的三角形顶点依照相邻顺序依次放至strip中,每个三角形只需要加入二个顶点。缺省条件下,在strip中彼此相邻的顶点都构成了连接两个相邻三角形的公共边。如果连接规则(顺时针或者逆时针顺序)需要发生改变,则可以使用swap命令交换顶点顺序,或者重新将某一个顶点放入strip之中。扇形三角形带(Triangle fans)可以看作是三角带的一种退化形式,只是其中所有的三角形都共享一个公共顶点。图3.2是三角形带的表示方法:
V0,V1,V2,V3,V4五个顶点构成了表示三个三角形的三角形带。注意描述三角形带时,顶点的顺序很重要,因为是遵循一定连接规则(顺时针或逆时针)的。在OpenGL中生成最右方的三角形带的代码如下:
3.2 LOD地形网格简化算法的基本思想及意义
所谓地形网格的简化是指通过算法减少提交到显卡的顶点,以减少每帧同屏渲染的三角形数量,借以提高渲染速度。
细节层次(LOD,levels of Details)技术是一种符合人视觉特性的网格简化技术。我们知道,当场景中的物体离观察者很远的时候,它们经过观察、投影变换后在屏幕上往往只是几个像素甚至是一个象素。我们完全没有必要为这样的物体去绘制它的全部细节,可以适当的合并一些三角形而不损失画面的视觉效果。对于一般的应用,我们通常会为同一个物体建立几个不同细节层度的模型。这样的技术应用在地形渲染中,也称之为多分辨率地形(Multi—Resolution Terrain)。下图就是一个多分辨率地形网格:
在开发3D游戏时也有不采用基于LOD的地形网格简化算法的做法。典型的游戏有韩国游戏公司开发的著名3D网游《奇迹》,每个Tile场景地形它用257×257的高度图构成,采用静态载入场景数据的方案同样产生出了美妙的场景。对于这么小的场景,当然没有必要做地形网格的简化,现在的一般显卡足以能够应付。之所以他能成功应用这种方式,是因为在设计场景时限制了地形的高低起伏,使地形趋于简单,缩放标尺取得很大,同时用纹理和光照贴图来弥补地形本质上的单调。另外,如果运用基于外存的动态数据载入算法,理论上也可以不用基于LOD的地形网格简化算法。
随着3D游戏的成熟,玩家需要有更真实的体验,因此地形变得更加复杂,场景变得更加巨大,地形的绘制需要很多数据参与,对系统资源消耗巨大。如果一点也不进行地形网格简化,试想一个1025 X1025的场景就将生成2M个三角形,渲染大的场景时显卡的处理能力很难跟上。在显卡的数据吞吐能力有限的情况下,游戏场景渲染中普遍基于LOD的思想,减小绘制多边形数目。它能在牺牲适量CPU资源的前提下大大减轻图形卡的数据负载,使CPU与GPU之间没有明显的瓶颈,从而达到实时渲染大地形的目的。
基于LOD的地形网格简化算法分为动态LOD和静态LOD算法。动态LOD算法是在每帧渲染之前都经过计算重新确定送入显卡的顶点。所有的顶点数据全部需要参与运算。ROAM算法和基于四叉树的动态LOD算法都属于此类,GeoMipMap算法则是静态LOD算法的代表。
3.3 ROAM算法
1997年,Duchaineau提出了实时优化适应性网格(ROAM,Real-timeOptimalAdaptive Meshes)算法瞳1,它是一种基于规则网格的连续LOD网格构造算法。其基本思想是在对地形进行三维显示时,依据视点的位置和视线的方向等多种因素,对表示地形表面的三角形图元进行一系列基于三角形二叉剖分分裂与合并,最终形成和原始表面近似且无缝无叠的简化连续三角形表面。
ROAM的基础是等腰直角三角形的一个性质。等腰直角三角形可以从直角顶点到斜边引一条垂线,这条垂线把这个三角形分成了两个小的等腰直角三角形,并无限制的递归分下去。而从另一个角度来看,这正好构成了一个二叉树,每个三角形都是把它分开而生成的两个小三角形的父母(parent)。根据这条性质,只要计算出哪些三角形需要被分割开、哪些三角形需要合并成为自己的父母,就可以做到控制LOD(分开就是增加LOD,合并就是减少LOD)。图3.5显示了1~4层三角形二叉树和相应的层次结构。
当把一个三角形分成两个的时候,会在斜边上增加一个顶点,是由斜边的两个端点插值求得。不过高度却不能插值。因为高度是按照相应的地图数据来的(比如heightmap)。所以,就存在一个插值以后的高度和实际高度不匹配的问题(会产生裂缝)。为了解决这个问题,就需要调整Y轴上的值来升高或者降低这个顶点,这个顶点高度的调整距离就称为误差量。
要确定一个三角形是否要被分割,就要看它是否能精确的描述地形的高度数据。如果可以的话,自然就不用分割了,多边形越少越好。如果不能的话,就细化它,也就是分割掉,直到所有的小三角形都能够精确表示地形数据为止。
通过不停的分割,三角形越来越小,每个三角形在高度图上所覆盖的面积也越来越小。那么,总会分割到足够小,使得三角形的面积和高度图上一个点的面积之比为l:l,分到这里就不用再分了。通过检查所有孩子的误差量,我们就找到了一个描述三角形是否需要分割的精确方法。当递归的遍历这棵树以后,就能够找到这棵树里面的最大误差量,就是所谓的largest error metri c。这个最大误差量如果是O那就是完全符合实际高度了,这个值越大,就越不符合。
用每个三角形的误差量和镜头到该三角形的距离比较,用以判断是否一个三角形需要分裂。用以给每个三角形做测试的值是人为定义的,这个值又叫错误容忍度(error metric tolerated)。通过错误容忍度对每个三角形做测试,小于这个容忍度的三角形就被留下(不分裂),大于这个的就被分裂掉,然后再分别对被分裂的三角形的两个儿子做分裂。如果加入了视角依赖(view—dependence)的话,就需要通过镜头到三角形的距离来调整这个容忍度了。镜头越远,容忍度就越大,而镜头越近,容忍度就越小。’
此算法的优点在于:可动态的改变每个网格;可在渲染时控制每个网格的生成与否;可以和纹理坐标很好的结合在一起;可控制地形三角形的最大数目;可以根据坡度的不同自动调整LOD的细节程度,也就是说在地形坡度大的地方LOD细节程度高,在地形坡度小的地方LOD细节程度低;可以根据到观察点的距离自动调整LOD的细节程度,也就是说在离观察点近的地方LOD细节程度高,在离观察点远的地方LOD细节程度低。
3.4基于四叉树的动态LOD算法
3.4.1算法思想
此算法是Lindstrom提出的n劓,他用了一个叫四叉树(Ouad Tree)的结构来描述地形,先把可视范围内的地形分割成四等份矩形子块,依靠计算判定因子检测四个子块,如果检查到某个子块的网格精度达到所要求的绘制精度就不需要往下再分割;否则就把此子块再分割成四等份更小的子块,依次递归分割下去,直到所有子块中的矩形网格都达到渲染精度。
3.4.2此算法涉及的难点
●对T形裂缝的处理
由于不同层次间的采样间隔不同,在可视化过程中会出现缝隙,这种缝隙必须进行专门的处理.如图所示,上方矩形具有较高的分辨率,而下方矩形的分辨率较低,这使得矩形间的连接处出现了末被覆盖的区域(阴影处),从而在地形绘制时就产生了“裂缝”。
每个.LOD层次区域节点均为正方形,在分辨率低的四叉树区域节点上,四个方向均可能出现缝隙,所以必须依次用自身节点的分辨率大小比较四个方向上临近节点的分辨率大小。如果前者小于后者,则必须通过在相邻边上加一条边来实现裂缝的消除。当自身节点分辨率与相邻节点的分辨率相差不止一级,那么还必须递归比较并添加边,直到完全消除裂缝。
●判定是否细分
子块离视点的距离和地形的平坦度共同确定是否需要进一步细分以达到所要求的渲染精度,从而使最终分割后的叶子节点达到最优。离观察者视点越近的地方细节越多,地形越不平坦细节越多。
子块离视点的距离d可以用公式表示如下:
其中(Xl,Y1,Z1)为视点坐标,(XO,YO,ZO)为子块的中心坐标。对一个子块区域平坦度的计算如图3.7所示:
对于图中这个子块的平坦度,先计算出高度值h卜h8,找出其中最大的高度值hMax,最小的高度值bMin,令err=hMax—hMin。err即是此子块的平坦度。
err的值越大就意味着子块越不平坦,网格细节应该越多。
最终,综合考虑距离和平坦度两个因素得出判断是否细化的标准:
如果err*r/d—k>O则继续细化,反之则不。(k为一个可变的控制值,r为子块的i/2边长,err为子块平坦度,d为子块到视点的距离)。
3.4.3算法运行步骤
(1)初始化顶点数组,建立完全四叉树并初始化,使每个节点描述清楚自己对应的区域地形。
(2)实时渲染:首先对每个四叉树节点进行视锥体裁剪,确定进行渲染的四叉树节点,再渲染这些节点。
a.对节点进行视锥体裁剪。
b.确定哪些节点需要渲染:
先计算本节点所管理的地形区域离视点的距离,再计算其平坦度,两者共同确定渲染精度值。
根据渲染精度值和判定标准来确定是否需要进一步细分,对于需要细分的就按四叉树思想递归细分,而不需要细分的就设置它的渲染标志位为真。c.遍历四叉树,渲染标志位为真的节点,并判定本节点区域网格在渲染时是否会出现T型裂缝。判定方法是通过和四周相邻的节点比较细节分辨率值,如果小于相邻节点则做修补裂缝处理。
(3)释放资源。
3.4.4算法相关代码
以下是1025×1025大的高度图用基于四叉树的LOD算法在线框模式下生成的地形网格:
随着图形卡数据吞吐能力的不断提高,每秒钟处理上亿个三角形己不再困难。很多计算,比如几何变换和光栅处理都可以交给GPU去计算。所以在GPU数据吞吐量很大的情况下,如果一个算法在剔除渲染顶点的过程中占用了太多CPU资源,出现GPU等待CPU的情况,那么即使算法在剔除多余顶点方面做得很好,总体绘制效率也不是高效的。上面讨论的两种动态LOD算法(RoAM和基于四叉树的动态LOD)都存在这方面的缺陷。并且,受硬件带宽的限制,频繁地传输海量顶点数据,使得时间集中消耗在数据“迁移"过程中。过多的DP(Draw Primitive)也使得图形卡不能发挥最大功效,造成资源极大浪费。所以要适应现代图形卡的硬件架构,算法必须改进,几何多重映射(GeoMipMap,Geometrical Mipmapping)等算法由此产生。
本设计在GeoMipMap算法的基础上,改进了原算法关于抑制不同细节分辨率模型之间突变的处理方法,使得在提高绘制效率的同时,保证了绘制图形的质量。通过使用查表法的分块网格顶点数据组织方式,使得CPU的工作进一步减轻。此外,本设计还利用了现代图形卡的存储功能来优化地形的绘制。
3.5.1 GeoMipMap算法
●基本思想
GeoMipMap算法是Willem根据纹理多重映射的概念提出的H3,他把整个地形场景在XZ平面上进行分块(block),比如用33×33的block把1025X 1025的地形表示为32×32个block。每个分块可用不同分辨率的网格模型来描述。在同一分块内,网格模型的分辨率相同。采用隔行采样的方式生成不同分辨率的网格。整个地形的模型表示和组织如图3.9所示:
不同的block之间互相拼接时,如果分辨率不同则可能产生裂缝。为了消除裂缝,在较高分辨率的block边界上,忽略一些点作为网格顶点,如图3.10所示:
每个block分辨率是通过屏幕空间误差H3来决定的。在地形数据预处理阶段,取一个屏幕误差阀值£(一般取4个像素),预先计算出当e等于4个像素时,视点到block的距离d和相应的block分辨率,并保存在查找表中。当实时绘制时,根据视点到每个block的距离查找表中的d,查找决定该block的网格分辨率。
●优点和不足
GeoMipMap算法的网格生成方式显然和ROAM算法以及基于四叉树的连续性LOD算法大不一样。以基于四叉树的连续性LOD算法为例,他是通过自顶向下的方式用四叉树递归地将地形分成一个个小地形块,越往下细分,地形块越小,直至不能细分。当视点发生改变时,所有的顶点都必须重新参与细分的递归运算,这种算法能根据实际情况最大程度确定整个地形的网格分辨率,但计算量很大,并且递归层次很多,CPU的负载极大。而对于GeoMipMap算法来说,当
视点改变时,只需要判断可见的每一个block的网格分辨率应该是多少,block内部的顶点并不参与计算。虽然这种做法不能最大化减少进入渲染管道的顶点,减少三角形面片,但是增加的这些渲染顶点对现代图形卡来说是不会影响绘制速度的。相反,由于他减小了实时绘制时模型简化的计算复杂度,速度得到极大提高,所以他是符合现代图形卡硬件架构的地形绘制算法。此外GeoMiaMap相对固定的三角形面片组织方式,也使得进入固定渲染管线的顶点能更好的组织成三角形带,大大减少传入图形卡的顶点个数。
但是,这种算法由于不是连续的LOD算法,也就是说LOD的粒度比较粗放,因此在block的网格分辨率发生改变时,会产生网格形状的突变,使得在地形漫游时,图形过渡不自然。虽然通过屏幕投影误差来选择block的网格分辨率可以使突变的效果有所减轻,但对用户来说仍比较明显。本设计通过线性插值的思想,使层次过渡由突变转为逐步递进,极大减小了这方面的不足。
3.5.2 GeoM i pMap优化算法
·地形数据的总体组织和表示
我们首先读入一个场景的高度图数据(heightmap),保存在一个顶点线性表中,然后把这个场景在XZ平面上划分成均匀大小的多个block。block的大小按需求而定,其边长满足2l+l,如9×9,17×17,33×33等。如果地势总体比较平坦,我们可以选得大一点,如果对地形的细节要求较高我们可以选得小一点。本文以17×17作为block的大小。block通过顶点索引所组成的三角形带描述他负责的一片小的区域。整个场景用一棵完全四叉树把这些blocks组织起来。实时渲染时完全四叉树负责场景的裁剪,决定哪些blocks应该绘制,然后计算可见block的网格分辨率,从而得到整个地形要渲染的三角形面片。其数据组织图如下所示:
用面向对象的方式描述地形对象:
当地形是超大地形绘制时,我们采用多线程机制加上场景缓冲池的方法实现大地形数据的动态调入和管理。每一个场景Tile作为动态加载单元,用一个缓冲池来管理,用单独线程来维护。详细讨论在3.7节。
· block多分辨率网格模型的构造和数据组织
Willem在他的论文中指出当细节层次不同时block的顶点取舍方法,以及为了避免出现T型裂缝,block的边界顶点应该怎么调整。在他的思想基础上,本设计提出一种基于查找表的block三角形带生成方法。
我们把block的中心地带和边界分开对待。在预处理阶段就生成五个顶点索引表。如图3.12所示:
中心地带索引表负责生成block中心的三角形条带,其索引参数就是自身的网格分辨率。边界索引表负责生成与其他block相邻区域的三角形带(防止T型裂缝)。索引参数有三个:自身的网格分辨率,相邻的方向,相邻block的网格分辨率。
网格都使用三角形带(Triangle Strip)的方式生成,有些地方需要生成一些退化三角形,用于三角形带的连接。运用三角形带的方式比三角形扇和纯粹三角形方式更能减少顶点个数,提高绘制效率。
这五个位置的网格所关联的索引表一起就能够描述任何一个block网格所有顶点的相对位置(在block区域内的位置)。在场景渲染初始化时,我们读入block的五个LOD顶点索引表,得到block的不同分辨率网格。在实时渲染的时候,针对一个特定的block,我们可以根据这个block在场景中的起始位置,他的网格分辨率,和他四周block的网格分辨率,直接查表得到这个block完整的三角形带顶点索引,减少了CPU的判断和计算量。内存中只保存一个block网格顶点的相对索引,不是整个场景的所有block的顶点索引都保存,因此不会造成什么内存消耗。
· 用面向对象的方式来描述block
block是本算法很重要的对象,可以描述如下:
· 利用线性插值逐步过渡不同分辨率的网格模型
当block的网格分辨率次发生变化时,其网格模型可能变化较大,由于变化是在瞬间完成的,极易被观察者察觉。但如果我们把这种变化由突变改为渐变,用户就不易察觉,其视觉影响也就可以忽略不计。
我们在预处理阶段已经得到一个合适的查找表,可以查出block的网格分辨率c与block到视点的距离d之间的对应关系。我们假设d=lOOOm时c=n+l,d=2000m时c=n。
如果现在d=1500m,则网格的分辨率正处在n和n+l的过渡阶段。我们取网格顶点为c=n+l时的索引,他比c=rl时多出一些细节顶点,对这些多出的细节顶点,我们对其高度进行线性插值,使其缓慢在分辨率n+l和分辨率rl之间过渡。如图3.13,v3为高分辨率时出现的细节顶点,v4为模型在低分辨率时v3的初始点。随着网格向高分辨率过渡,v4逐步过渡到v3。v’的Y坐标由下面的公式决定:
如3.13图:
采用这种插值的手段后,只增加了很少的计算,视觉效果上却得到了很大的提高。
·利用显存保存地形的顶点表
现代图形卡已经支持把一定大小经常使用的数据直接保存在显存中,所以如果我们把经常使用不频繁变动的数据保存在显存中,可以避免大量数据在渲染时频繁从内存传输到显存。在实验中通过0penGL的VBO(Vertex Buffer Object)方式把顶点线性表数据保存在显卡中,经比较渲染速度大幅提高。
·描述算法的流程
预处理阶段:
(1)载入地形数据,初始化顶点线性表。
(2)初始化所有分辨率的block模型所对应的三角形带顶点索引表,此表保存的是组成三角形带的相对顶点索引。生成描述整个地形场景的block数组,每个block记录自身在场景中的绝对位置。
(3)构造完全四叉树,每个子节点对应管理一片区域(一个或多个block),设置包围球半径。每个叶子节点都对应一个block索引。实时绘制阶段:
(4)遍历完全四叉树,根据空间裁剪算法,得到可见block的索引。
(5)计算这些block的网格分辨率,根据分辨率和网格的三角形带索引表,顶点表,可以得到组成地形网格的所有三角形带顶点的完整信息。
(6)根据前面介绍的线性插值方法,调整相关顶点的高度信息。
(7)送入渲染管道绘制。
(8)回到(4)。
●测试结果
我们使用大小为2049×2049的高程图作为实验数据,以Athlon2500+,DDR IG,ATI 9550,128M显存作为硬件环境对上述算法进行测试。程序用VC+OpenGL在windows平台上完成。其中分块大小为17×17,共分4个细节分辨率,以下是场景绘制的网格形式截图:
从表3.1中的技术参数统计来看,新算法的渲染效率有很大提高,能满足大规模地型的渲染要求。
我们只对地形进行分辨率上的简化是不够的,摄像机在场景中只有一个可见范围,怎么样有效的剔除不需要渲染的地形部分,这就要涉及到地形的空间管理算法,可见性裁剪算法口9|。四叉树,八叉树,Bsp树,背面剔出等很多其他方法都是针对这个目的而提出的。本设计的可见性剔除采用了如下流程:
3.6.1按距离剔除
单靠视锥体剔除已经能剔除大部分面片,但是在他之前有一步距离剔除也是有必要的,因为他的计算很简单,就是通过计算地形block的包围球心与视锥体的距离,距离大于系数k的blocks统统剔除。k的确定一般与天空盒子的大小有关。
3.6.2视锥体剔除
从3D到2D投影过程中,需要一个投影体,只有当物体处于这个投影体中的时候,我们才能看到这个物体,否则物体将被裁剪掉。这个投影体通常被称为视见体(View Frustum)。在进行正交投影的时候,投影体为一个长方体,在进行透视投影的时候,投影体则为一个平头锥体,所以也叫视锥体。
空间中物体与视锥体的关系有三种:在视锥体内,在视锥体外,与视锥体相交。只要我们排除在视锥体外的物体,也就是排除在视锥体外的三角形面片就能大幅提高渲染的效率。
·求视锥平面系数
视锥体有上、下、左、右、近、远,共6个面组成。一个平面的方程可以表示为Ax+By+Cz+D=O。首先,把视锥体变换为长方体状的裁剪空间。如图3.16所示,左图为世界空间中的视锥体。右图为经过变换后的裁剪体。
我们规定朝投影体内部的方向为平面的正方向,判断一个顶点是否在投影体内部时,只要把顶点坐标代入到六个面的方程中,通过检查结果的符号就可以判断点是不是在投影体内部(所有的符号都为正)。世界空间的投影体在经过投影变换后,会成为一个范体。我们很容易得到这个范体的六个面的方程。
我们假设这六个面中某个平面上有一个点(x0,y0,z0,1),在进行投影变换之前的坐标为(x0",y0",z0",1)。这个平面的方程为Ax+By+Cz+D=0。投影变换前,在世界空间中的方程为A"x+B"y+C"z+D=0,则点必须满足:
如果变换矩阵为T,则投影前后的点要满足(xO’,yO’,zO’,1)XT=(xO,yO,zO,1)。通过这三个等式,我们可以得到
再根据投影空间中范体的六个面的方程,我们现在可以很容易的得到世界空间中的投影体的六个面的方程。我们已经有了裁剪体的方程,当我们需要判定一个顶点是否在视锥体中的时候,这六个方程已经足够了。在OpenGL中得到裁剪体六个面的方程系数的伪代码如下:
·用包围盒、包围球做物体的视锥体剔除
对于物体是否在视锥体区域内的判定,我们可以借助包围球或者包围盒隅1。在课题中,选用了包围球,也就是地形的外接球。
视锥体和包围球是否相交的经典算法是检查包围球的球心到视锥体每一个平面的有向距离di,i∈[0,5],设球体半径为R,如果存在一个i∈[0,5]使得di≤一R,那么包围球是在视锥体外,如果存在一个i∈[0,5]使得di≤R,那么包围球和视锥体是相交,否则包围球是在视锥体内。代码如下:
对地形的三角形面片而言,怎么判断哪些面片在视锥体中,我们不能把面片的所有顶点都计算判定一次,更好的算法是把整个场景分成一个个方便管理的区域,以每个区域为最小单位做视锥体剔除。由此空间管理算法出现,他的作用就是在空间上快速排除不需要渲染的面片。
· 用四叉树(Quadtree)管理空间
四叉树结构是每个父节点对应四个子节点的数据结构。我们可以把地形的三维空间近似看作XZ的二维空间,根节点表示整个正方形地形区域,其子节点分别可以表示“左上",“右上’’,“左下"和“右下”四个象限区域,那这四个子区域又可以递归划分下去,如图3.17:
四叉树中的阴影节点就是代表了地形中的阴影区域。四叉树的叶子节点代表了地形的最小可分区域。当要查找某一个区域时只需要遍历这个完全四叉树就可以了。
空间四叉树节点的数据结构可以描述如下:
·用八又树(Octree)管理空间
八叉树是在四叉树的基础上演变而来的。四叉树只可以描述二维空间,八叉树它可以描述三维空间。如图:
八叉树的空间管理一般用在有很多其他物体的场景中,比如建筑,树木等物体参与到游戏中来的时候,可以通过八叉树对这些物体进行统一管理,方便进行碰撞检测、视锥体剔除等。如果只是针对地形的裁剪,用四叉树足够了。
·用空间四叉树节点做视锥剔除
我们从上到下,依次遍历四叉树的节点,判断节点代表的区域与视锥体的位置属于哪一种:a.与视锥体相交, b.在视锥体外,c.在视锥体内。
如果是情况a,递归判断这个节点的四个子节点。
如果是情况b,剔除该节点。
如果是情况C,渲染这个节点代表的地形。
得到可渲染节点的代码如下:
3.6.4地形遮挡剔除,背面剔除
●背面剔除
当我们在三维场景中漫游时,只能看到地形起伏的正面部分,地形的背面部分被正面部分的网格面片遮挡,因此在绘制地形网格时,这部分网格可以不绘制。背面剔除算法的目的就是将这些看不到的背面网格去除掉,实现步骤如下:
(1)计算位于一个给定网格多边形平面上的某两个向量的矢量积,得到这个网格多边形的法向量,这两个向量可以通过多边形顶点的差分来得到。在求解网格平面的法向量时必须保证两个向量的矢量积的方向朝外,否则无法得到正确的法向量值。
(2)计算视点观察方向与法向量之间标量积的符号,由此决定它们之间是否形成大于90。的角。视线与网格平面之间的关系如3.20图所示:
当视线与网格平面法向量之间夹角大于90。时,表示这个多边形位于起伏地形的背面,需要剔除,否则不被剔除。
在实际编程时,地形网格的法向量可以预先计算并存储在内存中,当漫游时,只需要直接计算视线与法向量的夹角就可以判断网格是否要被剔除。如果对每个三角形面片都去判断其是否是背面的话,计算量是很大的,这增加了CPU的负担,虽然能够最小化参与绘制的面片,但是CPU的计算很容易形成效率瓶颈,结果绘制效率有可能反而没有不剔除背面的做法高。因此,这种算法必须针对具体情况,适当选取。如果场景大多是峰峦叠嶂,这时就可以考虑使用此算法。
●遮挡剔除
遮挡剔除大致可分为两类:针对视点的遮挡剔除和针对视点单元区域的遮挡剔除。前者判断两个物体之间相对于一个视点而言的遮挡关系;后者则判断两者间相对于一个连通区域(即所谓视点单元区域)的遮挡关系,由此得到的两个物体之间是否遮挡的断言对该区域中每一视点都成立。
针对视点的遮挡剔除算法大都需要根据视点位置将挑选出的遮挡物在图像空间离散化,并将其离散表示组织成层次结构,剔除时将场景中物体的层次包围盒自顶向下地与遮挡物的层次离散表示作比较,迅速拒绝被遮挡物体。由于对遮挡物采用了离散表示,可以很容易地实现多个遮挡物的融合,但由于离散化往往需要借助于图形加速卡,而从图形加速卡中读取数据相对较慢。另一些针对视点的遮挡剔除方法直接在三维物体空间中判断遮挡关系,但这使得多个遮挡物的融合变得困难。由于针对视点的遮挡剔除不一定需要严格的可见性信息,而只需知道潜在的可视的物体集合(Potentially Visible Set,简称PV$)。由此发展出另一类以PVS计算为核心的算法。PVS的好处就是数据为静态,渲染的时候不需要计算,但它对动态物体的判断不够好。
总的来说,由于现在显卡对三角形面片的吞吐量快速增加,如果为了少量剔除遮挡和背面的面片而增加CPU很多计算,那将是不值得的。所以,目前的背面剔除和遮挡剔除算法的使用只适合于地形交叠起伏厉害的场景。
在前面章节中涉及的场景是有限大的,我们实现了2049 X2049高度图的实时绘制。但是当游戏中虚拟世界的地图再扩大很多时,即使我们能对地形面片做很好的简化,对地形区域做很好的剔除,也不能一次把数据全部读入内存进行渲染。所以,对于整个游戏虚拟世界的表现,实际游戏引擎中往往采取地图数据的静态加载和动态加载两种方案。
我们把单个场景(Tile)看作组成整个游戏世界地图的基本单元,那么整个虚拟世界就是所有场景的集合。在物理上,通常把单个场景(Tile)的数据打包在一起。不管采用什么样的数据加载方式,都是以单个场景数据为基本单位的。在本课题中的单个场景数据主要包括高度图、纹理索引图、光照贴图、纹理贴图等。
3.7.1静态加载方案
这是最简单的办法,也是目前常用的方式。所谓静态加载是指在渲染场景前必须等待读入场景数据,读入后人物只能在这个固定的场景里漫游。在场景一定的位置设置下一个场景的开启点,使得人物一旦到达此区域就触发事件,读入下一个场景的数据,经过数据读取后,角色便切换到了这一场景中。如图3.21所示:
这种方法不要求场景间无缝衔接,通常会把单个场景设计得大一些,避免频繁的场景切换等待。对于一般游戏的渲染细节要求,一个1025 X 1025大小的场景已经能表现很开阔的场景了。
这种方法的优点是逻辑简单,系统开销小。但是由于场景的切换等待,存在不能连续漫游超大无缝场景等缺点,极大影响游戏体验。所以现在很多游戏正试图采用其他方案跳出这种限制。
3.7.2动态加载方案实现无缝连接超大场景的实时绘制
·地图数据的动态加载机制
动态加载是指在场景渲染的同时更新内存中要渲染区域的数据。本文通过维护一个区域数据缓冲池,根据角色所处位置,读入磁盘文件系统中的场景数据,使得缓冲池中始终保存有角色周围相关区域的场景数据(肯定不是整个世界的数据),场景渲染引擎只在缓冲池中挑选要进行渲染的数据。通常单独开一个线程负责维护这个数据缓冲池,主线程负责场景的绘制。整个数据调度过程如下图所示:
渲染主线程对数据加载是透明的,他只负责从缓冲池中挑选数据。这种技术的难点在于根据单位区域的数据量大小、磁盘I/O效率来决定缓冲池的大小。缓冲池太小会引起频繁的I/0读取,影响游戏流畅;缓冲池建得太大,占用内存资源过多,预取的Tile数据也会增加。通过实验得出,如果把每个Tile的高程图大小设为256×256,缓冲池取为25个Tile大小比较合适。如果能有效的利用这种技术,那么角色可以在游戏中自由漫游,理论上游戏场景的大小只受限于磁盘的数据容量。技术的关键在于使CPU、GPU、I/O三者的效率达到一种平衡,在任何一个环节不能出现瓶颈。
·缓冲池的建立与维护
所谓动态加载必须使场景绘制和数据的取得分工协作,异步处理。所以缓冲池必须通过创建单独线程来处理。关于缓冲池具体有以下几个问题需要处理:
(1)缓冲池维护线程的创建
对于缓冲池维护线程的生命周期有两种方式。一种是当绘制主线程发出更新缓冲池指令后得以创建,其生命周期在缓冲池维护工作(数据的读取和删除)后结束。第二种是在整个软件初始化时期得到创建,其生命周期一直持续到整个软件运行结束。通过与主线程共享数据区域中的一个缓冲池维护指令标记来决定是否进行I/O操作。
(2)缓冲池维护线程与主线程的协作机制和通讯
出于效率的考虑,在线程间使用异步机制。主线程只在需要重新调整缓冲池的时候向缓冲维护线程发送消息,他在使用缓冲池资源的时候不需要采用锁机制与维护线程互斥。通过为缓冲池中的每一个Tile建一个状态标记,主线程在查找数据的时候先看其对应的标记,如果标记表明可以使用,才让数据进入渲染引擎。反之,则不把这个Tile调入渲染引擎。通过在主线程发出更新缓冲池指令和维护线程从文件系统读完数据之间预留足够的时间,可以保证在最大程度上让主绘制线程取到想要的数据。这么做最大的好处是效率很高,绘制线程不需要任何等待时间。软件初始化时在内存中开辟的一块专用区域,两个线程的数据都在这里得到共享。他们的消息传递也通过改变在这块区域中的一些状态标记来实现。
在缓冲池中的每个Tile数据通过索引指针被绘制线程方便的使用。
(3)何时更新缓冲池,更新哪些
当角色在场景中移动位置超出某个距离限制时,让主线程通知缓冲池维护线程开始按主线程要求的最邻近Tiles索引表来读取新的Tiles文件,在池中删除需要丢弃的Ti les空间(或许覆盖更好)。关于何时更新,如图3.23所示:
图中的每个小格子代表一个Tile,也就是缓冲池维护线程要加载的最小单位。在本程序中每个Tile为256×256大小。当角色在点a位置时,在他周围相邻的Tiles是以TilelO---Iilel4为边长的正方形。一共25个Tiles,他们就是此时缓冲池中拥有的Tiles。角色向b点移动,假设角色现在的坐标是(X’,Y’),比较的基准位置为a点,其坐标为(X,Y),那么
当X’-X>=Tile.Width
或者Y’-Y>=Ti le.Height 时触发缓冲区更新事件。
同时把基准位置设为b点。通知缓冲池维护线程从池中剔除TilelO-一Tilel8,从文件系统读入Tilel_Tile9。角色走过(X’一X)或者(Y’一Y)这段路程的时间就是预留给池维护线程动态读取数据的时间,在本例中为256个高程图单位,足够了。
值得注意的是,绘制主线程只从缓冲池中挑选最邻近他四周的9个Tile进入渲染引擎。如果在实际应用中我们的硬盘读取时间预留不是很足,读取数据比较频繁的话,可以通过增加进入缓冲区的Tile个数来调节。比如设置进入缓冲区的T儿e为36个。
另外,也可通过设定Tile的优先级来更合理的决定进入和退出缓冲池的Tile。Tile的优先级可以根据角色的运动方向来判定,比如人物向右移动,那右方Tile的优先级显然应该比左方的高。’
3.8本章小结
本章节讨论比较了ROAM、基于四叉树的LOD、GeoMipMap几种基于LOD思想的网格简化算法,提出一种优化的GeoMipMap算法,并讨论了其实现细节。另外,本章介绍了符合室外地形的空间管理算法一一基于四叉树的空间管理。并讨论了在其基础上实现的视锥体裁剪,背面剔除算法和遮挡剔除算法。
此外,针对超大无缝地形的渲染,本文提出通过维护数据缓冲池来实现地图数据动态加载的技术。
第三章讨论了怎样有效绘制地形的三角形面片,关于怎么在面片上表现真实的地表(草地、沙粒),以及怎么表现光照、天空、植物等,这一系列问题涉及的就是场景的真实感渲染技术。
4.1纹理映射技术
4.1.1概念
在游戏设计中,由于客观世界千变万化、错综复杂,要把真实世界的各种细微结构直接用几何模型表示出来,不仅模型难以建立,而且计算量庞大,难以满足实时显示的需要,比如一张曲面可以用许多微小的多边形(或曲面片)表示其表面细节,假定每个微小多边形具有近似相同的表面特征,要显示这样一个曲面,就必须对这些微小多边形(或曲面片)进行分别处理,这将需要大量的存储空间和处理时间,因此在实际应用中,为了获得比较高的显示速度,往往以牺牲图形的真实感为代价。尽管这样,显示一幅比较复杂的图像仍然需要很长时间。于是,人们就想象是否可以用“贴墙纸"的方法将反映物体表面的细节的图案贴到物体表面,从而开辟了一个新的研究领域一纹理映射(Texture Mappirig)。处理过程如下图:
纹理映射的方式主要分为三种:
颜色纹理映射:最简单和最基本的应用是将一幅花纹图案映射到物体表面,采用与表面上点的位置有关的值(如:参数曲面上一点的参数值)来确定纹理坐标并采样包含图案的纹理,采样值用定义该点的颜色。通过此类方法在图案和表面点之间建立的固定联系,不会因视线的改变而改变。当花纹或图案绘上之后,表面仍光滑如故,这种纹理称为颜色纹理,形如在物体表面绘制了一些花纹图案。
凸凹纹理映射:根据粗糙表面的光反射原理,通过一个扰动函数扰动物体表面法向量,使光滑表面得到调制,并在光线下呈现出凸凹不平的形状,这种纹理称为凸凹纹理(或几何纹理)。利用扰动函数可以很好的模拟皮毛、头发、衣服之类的物体。
过程纹理映射:过程纹理映射属于三维纹理,它就是将三维的纹理函数映射到三维物体上。也就是说在物体的内部也会受到纹理的影响。例如,木材和大理石的纹理,需要考虑每一个相邻面的纹理映射,通常每个面分别进行颜色纹理映射时,由于面的边界处纹理不连续,往往难以表现出自然的纹理,这种场合定义三维的纹理函数进行立体纹理映射是非常有效的。用过程纹理模拟物体表面细节,能够在非常复杂的曲面上表现连续的纹理,且纹理效果不受物体表面形状的影响,可以很大程度的解决纹理走样问题。
4.1.2 OpenGL实现纹理映射的步骤
(1)指定纹理
在最简单的情况下,纹理是单个图像。利用称为MipMap的技术,程序员可以指定同一纹理的多级分辨率图像,在进行纹理映射过程中,该技术可以按照景物表面在屏幕上所占区域分大小,自动选择合适分辨率的纹理图像对景物表面进行映射,以避免因纹理映射中的点采样方式所导致的纹理走样。另外,图像的定义可以包括边界值,以防止物体的纹理坐标超出有效区域。边界值允许程序员把多个纹理映射平滑的粘贴在一起,从而增加最大可用纹理的有效尺寸。相关函数:glTexImage2D(),glTexGen()。
(2)纹理如何作用于每个像素点
OpenGL根据片元的颜色和纹理图像数据中的颜色来计算最终的RGBA值。程序员可以选择下面三种功能中的一种:a.可以简单的使用纹理颜色作为最终的颜色,这种方法如同贴花一样,纹理被粘贴在片元的表面上;b.可以用纹理来调整片元的颜色和颜色比例,该技术对光照和纹理的综合效果非常有用;C.用纹理值将一个固定的颜色和片元的颜色混合在一起。相关函数:glTexParameter(···),glTexEnv(···)。
(3)激活纹理颜色
在绘制场景之前需激活纹理映射。相关函数:glEnable(),glDisable()。
(4)利用纹理坐标和几何坐标绘制几何场景
在粘贴纹理之前,必须说明纹理相当于片元是如何排列的。也就是说,必须指定场景中物体的纹理坐标和几何坐标。对于二维纹理图可指定其两个方向上的纹理坐标均为[O.0,1.0],而要粘贴纹理的物体坐标可以是任意的。相关函数:glTexCoord*()。
4.2 用多层纹理混合贴图模拟融合性地表
4.2.1纹理混合贴图
对于小的地形,我们可以考虑在整个地形上贴一张纹理。比如65×65的地形贴上一张128×128的图片。但是当地形很大时,我们的贴图不可能很小。当贴图很小时,地形会被拉伸产生失真;当把贴图选得太大,显卡会承受不了。于是有人想到了,利用纹理贴图坐标(texture coordinate)的设定,我们可以将一张图片一次次的重复贴至地形场景上,也就是所谓图素纹理(tile texture)的方法;不把128x1.28的图片直接映射(mapping)至整个地形范围,而是以每个128x128大小为一个单位,将图文件完整贴上,如此反复进行贴图,就像是铺地板砖一样。这样的方法,的确可以大幅减低贴图失真的情形,可是如果还想进一步实现更真实的地形场景贴图,就会发现这个作法还是有不足之处。一个地形场景通常不会只拥有单独一种样式的地貌。举例来说,可能依照地形高度的不同,在平地上会有草地,高度往上的山坡会有沙地,在山顶或陡峭之处可能会有岩地或雪地等等多种可能的组合。如果想达成这种形式的地形纹理贴图,就不是简单的使用图素纹理(tile texture)可以实现的了。这就需要纹理混合贴图(Texture Blending)技术。
两层纹理混合原理公式:C=a*alpha+b*(1-alpha)。
4.2.2基于索引图的纹理混合贴图
在很多游戏中,地形纹理又分为粗纹理和细节纹理。粗纹理是指表现地表大致图像的纹理,比如一片草地,它的粗纹理就可以是一张绿色的图片。而细节纹理顾名思义是指表现细节的纹理,在例子中,它就可以是一张表现草的细节、沙石细节的图片。一张粗纹理一般映射得很广,甚至映射到整个地形;而细节纹理一般是反复贴图(铺砖)。室外场景一般2到3张细节纹理足以表现出漂亮的结果。当多张细节纹理混合时,怎么体现不同纹理间的平滑过渡呢·可以应用纹理索引图技术。如图4.2
在灰度图表示的纹理混合索引图中每个像素对应地形中的一块区域(不能太大),白色像素表示那块区域贴岩石纹理,黑色像素表示贴草地纹理,灰色像素即是两种细节纹理的混合。比如灰度值是78,则在混合过程中岩石纹理的alpha=78/256×100%。
同理,我们可以用彩色图来表示更多细节纹理的混合索引。令索引贴图的R分量代表沙滩的纹理,G分量代表草地,B分量代表岩石。如果索引贴图上一个像素的值是(0,255,0),即绿色,则这个像素所对应的地形区域的具体纹理就为草地。如果该像素颜色值是(127;127,0),即黄色,则该像素所对应的地形区域的纹理为草地和沙滩的混合,看起来既有草,又有沙。这种方法的优点是简单、灵活,方便美工人员设计地表的纹理。
4.2.3 纹理混合的OpenGL实现
在OpenGL中,纹理混合有三种方式来实现:多通道多遍渲染,单通道多重渲染,用GLSL(GL Shading Language)实现多纹理混合。
·多通道多遍渲染
我们把地形用第一种纹理画出一次,然后再开启阿尔法混合(alphablending)的功能,用第二种纹理再画出地形一次,这样用不同纹理重复多次画出地形。这种做法适应配置低端的显卡,但渲染速度会受很大影响。
·单通道多重渲染
这种方法要用到两个OpenGL扩展指令(OpenGL Extensions),分别是GL_ARB_multitexture与GL_EXT_texture_env_combine。GL_ARB_multitexture可以让我们一次控制二到多个纹理单元(texture unit)的操作。这种技术适合于配置中端的显卡,速度也很快,但是支持的纹理个数有限。其步骤:
(1)地形初始化时,由纹理混合索引图确定每个地形顶点的所有纹理的映射坐标,纹理间的混合alpha值。
(2)初始化多重纹理,载入纹理贴图,设置相关参数。
(3)渲染时,用glMultiTexCoord2fARB设置顶点的纹理映射坐标,关键代码如下:
·用shader实现多纹理混合
所谓shader是基于显卡的着色器。OpenGL和DirectX都先后发布了自己的着色器语言,如OpenGL的GLSL(GL Shader Language)。这是目前比较新的渲染技术,这种方式编写程序简单,速度很快,支持的纹理也很多,但显卡必须是比较高阶的支持着色器语言的。这种方法在下面章节光照贴图中会有相关代码。
要绘制逼真的场景必须做光照处理。没有光照的三维物体模型与二维物体没有任何差别,没有一点立体感。只有具有光照的物体才是真正的三维物体。光照射到物体表面时,可能被物体吸收、反射或透射。光的反射和透视部分进入视觉系统使我们能看见物体。光的颜色是由其波长决定,一束白光含有所有可见波长的光。白光照射物体时,只有所有可见光被等量吸收物体才会呈现灰色;如果被不等量吸收,物体会呈现其它的颜色。光的亮度由光强决定。从物体表面反射出来的光的强度取决于光源的位置、光强、物体材质、物体表面位置、物体表面法线和视点的位置。
4.3.1光照模型介绍
光照射到物体表面时,光线可能被吸收、反射和透射。被物体吸收的部分转化为热,反射、透射的光进入人的视觉系统,使我们能看见物体。为模拟这一现象,我们建立一些数学模型来替代复杂的物理模型,这些模型就称为明暗效应模型或者光照模型。
在真实感图形学中,我们把仅处理光源直接照射物体表面的光照模型称为局部光照模型,而与此相对应的,可以处理在物体之间光照的相互作用的模型称为整体光照模型。
●Phong光照模型
Phong光照模型综合反映了漫反射、镜面反射和环境光对表面的作用1。他属于局部光照模型,在游戏场景的实时渲染中经常用到。其光照强度方程式如下:
式中,M表示对场景有贡献的点光源的总个数;I是光照表面点(x,y)处的光强;Ia是入射环境光的光强;Ini、fi(d)是第i个点光源发出的入射光光强和光源强度衰减因子;ka为景物表面对环境光的漫反射系数;kd为景物表面的漫反射系数;ks为景物表面的镜面反射系数;n称为镜面高光指数,被用来模拟镜面反射光在空间的会聚程度;Li为第i个点光源发射方向单位向量;N是点(x,y)处的表面单位法向量;Hi为将入射光反射到观察者方向的理想镜面的单位法向量Hi=(Li+V)/2,V为观察者视线单位向量。
·光线跟踪算法
光线跟踪或称光迹追踪是计算机图形学的核心算法之一n¨。在算法中,光线从光源被抛射出来,当他们经过物体表面的时候,对他们应用种种符合物理光学定律的变换。最终,光线进入虚拟的摄像机底片中,图片被生成出来。由于该算法是全局光照模型,所以可以模拟生成十分复杂的图片。
光线跟踪算法伪代码:
·辐射度算法
辐射度算法本质是将光看作一种物理辐射,然后计算辐射的传导就能获得加之于每个对象物体上的光照强度,从而获得正确的渲染结果。和其他渲染方法相比,辐射度算法更接近于光的自然传播原理。辐射度算法解决了光在不同面之间反射的计算技术,但不分布镜面光,也不产生聚光(Caustic)。辐射度算法也是一种全局光照模型。
全局光照虽然渲染效果很好,但速度很慢,一般还不能应用在实时渲染中,它一般应用在非实时渲染中,比如光照贴图就可以用全局光照算法来生成。
4.3.2室外场景中地形的光照处理
室外场景由于涉及多边形面片太多,如果实时计算光照,那速度是很慢的。并且,用局部光照模型来计算出的效果也不太理想。由于地形的多边形网格是实时产生的,它会随着视点的移动而变化,因此如果直接使用OpenGL内置的顶点光照,就会得到极度不稳定的光照效果,会看到地形表面因为你的移动而不断跳动。所以目前在游戏引擎中,关于地形的光照大多通过光照贴图来实现。
这样的实现方法是一种静态光照,不是实时的。现在国外最顶级的游戏引擎开始把全局光照或接近全局光照的模型引入场景的实时渲染中,这是今后的一个发展方向。在本文中,地形的光照处理通过光照贴图来实现。
●光照贴图的概念
光照贴图顾名思义它是一个覆盖了场景中所有多边形的贴图n钔。通过给贴图赋值,我们可以得到多边形表面复杂的光照效果。使用好的算法计算出来的光照贴图可以模拟极度逼真的光影效果。它给我们带来的视觉享受远远地超过了OpenGL的局部光照。
●光照贴图和多细节纹理混合模拟地表
首先,我们准备3张地表细节贴图,1张光照贴图,1个纹理索引贴图。把3张细节贴图和光照贴图绑定到相应的纹理通道上。然后使用Vertex Shader为每个顶点计算拉伸后的索引贴图的纹理坐标。下面是用GLSL写的相关Vertex Shader代码:
在Fragment Shader里,对索引贴图进行纹理查找,使用查找得到的颜色值的RGB颜色信息按比例混合3张细节贴图,得到当前像素的颜色。最后还应该把这个颜色和光照贴图中的值相乘,得到最终的结果。相关的Fragment Shader代码如下:
●光照贴图的生成
光照贴图可以用全局光照模型,比如光线跟踪、辐射度算法等来预处理得到。由三个数据来生成:a.地形高度图;b.光源位置;C.光照强度。
4.3.3场景中非地形的阴影算法简介
地形的光照阴影用上面介绍的光照贴图解决了,针对树木、人物的阴影我们必须另外实现。人物的阴影是动态的,有两种常用的实时阴影生成技术。一种是ShadowVolume,还有一种是ShadowMap。
·ShadowVolume算法
ShadowVolume基于的是几何体算法,通过延伸光照轮廓区域进行正反面两次渲染,在屏幕的模板缓冲区内分离出阴影区域,能够得到十分精确的阴影区域,画质较好,而且渲染流程十分简单,缺点是建立光照轮廓延伸体十分费时费资源,另外阴影边缘视觉上比较生硬。
●ShadowMap算法
ShadowMap基于的是Shader技术,通过建立Z深度图、逐像素比较深度得到阴影区域。该算法针对一些固定的建筑物之类的图素形成阴影尤其出色,对于一些运动物体的阴影最多只需渲染两次物体即可形成阴影。缺点是需要显卡对Vertex Shader、Fragment shader的支持,而且内存消耗较大,同时只能生成平行光下的阴影,而且无法做到将阴影投影到所有的物体。
· 用阴影贴图创建简单阴影
可以把物体的阴影通过纹理贴图技术贴在平面上简单模拟阴影。下面描述实现步骤:
(1)渲染地形。
(2)根据光源方向和物体的位置计算物体的阴影在地形上方的位置,并构成阴影平面面片。
(3)对于要创建阴影的每个物体,使用支持alpha混合和透明的纹理映射函数在第(2)步计算的阴影平面上贴上物体的阴影。
4.4.1天空盒子
天空渲染最简单的方法是利用天空盒子(SkyBox)。天空盒子几乎在各种室外游戏中都能被运用,它能完全的描述视点周围360度范围内的视野。天空盒子实际上就是一个游戏三维空间中的立方体。如图4.4所示,构建一个天空盒子需要八个顶点和六张天空纹理图,这六张纹理会被贴在天空盒子的内侧,分别描述从摄像机视点所在位置六个方向看去的天空,上方纹理贴图描述向上看去的天空,后方纹理贴图描述向后看去的天空。对天空盒子的渲染也就是简单的渲染一个空间立方体的六面。最主要的是让天空盒子随着摄像机的移动而移动,保证摄像机一直在盒子的中心。天空盒子的六张纹理必须被精心设计以使它们看起来连接得天衣无缝。
4.4.2球形天空和弧度天空
天空盒子很简单,但是离天空边缘近的时候天空会有非常明显的变形。球形天空和弧度天空把天空曲面分成一个个三角形,显然这种方法渲染效果更精细,但是渲染速度也会有一些影响。所以本课题最终选用了天空盒子来实现。
4.4.3太阳和光晕的生成
渲染太阳很简单,在场景的特定位置放置一个圆形物体就能实现,镜头光晕则需要一些额外的处理。首先我们来看一个光晕的位置,如图4.6所示:
镜头光晕由一系列的光环组成,所有的光环排列在一条直线上。这条直线由发光物体位置和屏幕中心确定。这些光环的形状则有多种方式,通常用一张放射线状的图用来作为发光物体的光芒,另外的则用不同厚度的环状图像。我们组合这些光环的时候需要使用Alpha Blending功能把这些光环叠加起来自然的融合到场景中去。
所有的光环都是作为二维物体绘制到场景中去的。因此,发光物体在屏幕上的位置需要通过自己计算得到,在计算发光物体在屏幕上的位置的同时我们还可以得到这个位置的Z缓冲(Z-Buffer)的信息来判断是否能看到发光物体,当发光物体不可见的时候,光晕自然也不可见。
4.5 植物的渲染,雾的生成
4.5.1树草的构成与渲染
花草和树是室外场景必不可少的点缀,如果对这些物体的细节都用三角形面片来描述,在现在的硬件环境下显然是不现实的。因此出现了很多简化、取巧的方法。
·公告板技术(BillBoard)
公告牌技术,即BillBoard技术口¨,在3D游戏中有着广泛的应用。它的本质就是用预先做好的几幅位图来代替3D物体,极大地节省资源和提高速度。早期的很多游戏中,它的精灵、树木、物品通常是二维图象,也就是BillBoard。由于它始终朝向观察者,你根本看不到它“扁平”的一面,所以给人一种立体的感觉。这种技术最大的优点是速度快。如果用多个三角形面片构成3D精灵,至少要百余个面片,而用billboard技术,只需处理两个多边形。很多3D游戏的爆炸效果,如“极品飞车’’中路旁的树木,都使用了该技术。但这种方法的缺点也是显而易见的,真实感不强。现在对植物的渲染很少单独用到这种技术。
· 用交叉平面+alpha测试渲染花草
公告板技术是用一个平面来模拟复杂物体,交叉平面法的做法是用几个交叉的平面来共同表现一个物体,通过alpha测试选择要映射的纹理像素,由于是几个交叉面,所以玩家从不同角度都能看到一些贴着纹理的平面。这种方法运用得好能显出极佳的效果,关键在于针对不同的植物选择不同的交叉平面,同时贴图要很好的与平面配合。目前大多数游戏的花草等点缀物体都是这样渲染的,本课题花草的绘制也是采用的这种方法。下图4.7所示的是一种可以参考的交叉平面。
这种技术在实现的时候还有几点值得注意的地方:
a.选择合适的AlphaTest值来消除植物的黑边
由于纹理过滤和生成MipMap的缘故,渲染一些小物体时会出现淡黑色的边界,这就需要在实际使用中找到一个比较合适的AlphaTest参考值(一般不要取做0)。这个值跟纹理图、MipMap采样方式等很多因素都有关,可以选择一个经验值。
b.用多分辨率纹理(MipMap)消除纹理的抖动
当同样大小的面片处在远近不同的位置是,如果贴上同一张纹理,由于纹理映射默认的采样算法不能完全模拟物体缩小的贴图效果,在用高分辨率的纹理给远处面片贴图时就会发生纹理抖动,如果这样的面片在场景中很多,则画面闪动相当明显。多分辨率技术能很好解决这个问题。
多分辨率纹理就是对同一个纹理贴图准备几个不同分辨率的版本,用于分别对应不同距离的贴图。在实际的使用上,一般有两种方式。第一种是由底层图形库(比如OpenGL、DirectX)在纹理初始化的时候自动生成不同分辨率的纹理。另一种方式是设计者用图形编辑软件处理好不同的分辨率图片,在程序中由设计者自己控制在什么位置载入什么分辨率的纹理。后一种方法的渲染效果更好,但代价显然更大。
c.实现随风摆动的动态效果
由于我们的每棵草是通过几个交叉面模拟的,所以对每棵草几个面的顶点按一定规律调整位置,就可以模拟被微风吹动的效果。这种顶点位置的计算现在一般放在显卡上通过顶点着色器来完成。对于复杂建模的草丛或者树木,可以用关键帧、骨骼动画技术实现摆动效果。
d.模拟光照效果
从每棵草所在位置的地面上取得地面光照亮度信息作为这棵草的基本亮度,将上面的顶点随机调亮一个幅度,将下面的顶点随机调暗一个幅度,模拟草光照不均匀的效果。
●树的渲染
树一般比花草等大得多,复杂得多,所以要求我们要采用细节表现度更高的方法来渲染它。图形学理论上高细节度的植物渲染算法,比如分形算法等复杂度太高,现在还不能很好的用在游戏中。一般目前游戏中用的较多方法是:对枝干和树叶分别建模。
对枝干完全用比较细致的多边形面片建模,树叶用交叉面建模加上重复贴图来实现。这种方式既避免了完全细致建模造成的面片过多引起的渲染速度问题,又能达到极好的视觉效果,是目前广泛采用的折中方法。此法的难度在于怎么用尽量少的交叉面模拟复杂的树叶细节。下图4.8是一颗树的面片组成:
渲染大面积树林的时候,为了加快速度,还会根据距离选取不同分辨率的面片模型进行渲染。太远的甚至可以用前面草丛的交叉面贴图方法渲染。
此法最大的好处是简单、高效。但当靠近从不同角度观察它时,还是容易看出“片状”的缺陷。针对树的渲染,目前游戏应用领域还广泛使用一种才兴起的中间件SpeedTree。他主要的优点是用极少的速度代价实现了树的超高真实感渲染,细节、光影、树叶的动态摆动等都得到很好的表现。国内完美国际公司的“完美世界"游戏中就使用了这个中间件。
4.5.2 树、草在地形中的分布和管理
树和草从地形分离出来单独渲染。根据树草的位置坐标可以计算出它从属于的地形Block。在整个场景做数据初始化的时候,用表记录每个Block上存在的草和树,并从属于此Block。通过前面章节介绍的方法,我们知道每个Block是通过四叉树来做空间管理和可见性剔除的,所以通过对Blcok做视锥体剔除,我们也间接对不可见的草和树进行了剔除。在实时场景渲染的时候,只要查询出可见Blcok上存在的草和树渲染即可。
另外,因为草一般很小,属于点缀,所以对于在视锥体中距离较远的Blcok上的草也可以不渲染。树可以通过上面介绍的简化方式渲染。通过草的X,Z坐标能对应地形上的一个点,这个点就是绘制草的位置。草Y方向上的位置是根据其在地形上这点的高度和草本身的高度共同确定的。
4.5.3 雾的特效
在3D场景中加入雾化效果能增加场景的真实感、纵深感和距离感。当我们运用裁剪剔除算法时,如果一个物体足够远时,它就会完全被剔除而根本不被渲染,这样提高了渲染效率,但是我们也会因此看不到远处的场景。运用雾的特效可以帮助我们掩饰没有画出的远处场景。本质上讲,雾是根据场景中物体的深度或与视点间的距离,将场景中物体的颜色与选定的雾的颜色进行混合实现的。雾的颜色在渲染过程中所占的比例是与它在场景中的深度或者说距离视点的距离成一定比例关系的,如果物体越来越远,它们的本色和雾的颜色混合得越多,造成物体愈加被场景中悬浮的微粒模糊的假象,从而造就现实中雾越浓的效果。通过将离散的雾效果和有创意的场景设计相结合,应用程序可以添加气氛或使场景中物体的颜色变得柔和。从而使三维场景更加逼真,产生一种雾里看花,水中望月的朦胧美。
OpenGL支持两种模式的雾效果,它底层化了所有的与雾化处理有关的图形学算法,可以通过函数直接控制雾的属性,如颜色、雾的浓度、雾的某些行为属性。雾的行为属性又包括雾的影响范围、雾的颗粒大小、雾的地平线杂波属性、雾的活跃程度等。
4.6 本章小结
本章讨论了用多重纹理模拟融合性地表的技术和光照贴图技术。详细阐述了室外场景下光照的处理,并给出了具体实现方法和伪代码。对植物渲染过程中的公告板技术、交叉面与alpha测试技术及一些细节进行了深入的讨论。此外,对天空的渲染方法也提出了几种方案。
如前面章节所述,一个室外场景渲染器的主要工作包括三部分:1.超大场景数据的读取与管理;2.场景地形网格的组织与简化算法;3.场景真实感渲染技术。基于对这三部分的研究,本设计实现了名为0SRender的室外场景渲染器。本章就0SRender的总体框架做简要说明,并介绍在实现中可采用的基于0penGL的渲染优化手段。
5.1渲染器总工作流程
(1)初始化阶段
载入地形Tiles的高度图数据,初始化Tiles缓冲池,载入纹理贴图、光照贴图,载入树的多边形模型、计算顶点法向量。
(2)实时渲染阶段
包括地形的渲染、天空的渲染、草树雾的渲染。地形的渲染是最主要的。
(3)对象清除,资源回收
清除分配的系统资源。
5.2渲染器的总体设计
本设计基于面向对象的设计理念,整个模块框架如图5.2所示:
以下是比较重要的类的说明:
关于重要算法的描述和伪代码已经在具体章节给出,这里不再赘述。
程序用VC+OpenGL在windows平台上完成。我们使用大小为2049×2049的高程图作为实验数据,以Athlon2500+,DDR 1G,ATI 9550,128M显存作为硬件环境对渲染器进行了测试,在图形渲染效果良好的情况下,帧率达到40-50FPS,能满足游戏引擎中图形渲染的需要。下图是本设计关于室外场景的最终渲染效果:
5.4基于OpenGL的渲染优化手段
5.4.1利用GLSL对GPU进行编程
GLSL(GL Shading Language)是用来在顶点和像素着色器(shader)中编程的语言。使用它我们可以通过写短小的自定义程序,在图形卡的GPU(Graphic Processor Unit图形处理单元)上执行,代替固定渲染管线的一部分,所以也称为可编程管道渲染。现在生产的主流显卡都已经支持着色器编程,所以要实现高性能、高逼真的渲染,使用着色器语言是必不可少的。主流着色器语言有两类:DirectX支持的HLSL(High Level Shading Language),OpenGL支持的GLSL。
GLSL的着色器代码分成两个部分:Vertex Shader(顶点着色器)和Fragment Shader(像素着色器)。
顶点着色器主要的工作是利用视图和投影矩阵对点的位置进行变化,控制纹理坐标的产生和转换,顶点的光照或者象素光照的计算,颜色的计算。可以根据需要选择一些功能进行不同的编写。它可以得到当前OpenGL中的状态,用GLSL内置变量进行传递。比如gl_ProjectionMatrix(投影变换矩)、gl_ModelViewMatrix(视图变换矩阵)、g_position、gl_backcolor、gl_frontcolor、gl_normal等;而这些就是OpenGL应用程序中的顶点位置、颜色、法线等信息。
像素着色器是在对每个像素进行光栅化处理期间执行的程序。它实际上替换了固定功能管线的多纹理化阶段,并赋予我们直接操纵单独的像素和访问每个像素的纹理坐标的能力。这种对像素和纹理坐标的直接访问使我们可以高效达成各种特效,例如:多纹理化、每像素光照、景深、云状物模拟、焰火模拟等。
5.4.2使用显示列表加快渲染速度
OpenGL显示列表(Display List)是由一组预先存储起来的留待以后调用的OpenGL函数语句组成的,当调用显示列表时就依次执行表中所列出的函数语句。OpenGL对这些语句进行了预编译,因此速度比立即模式快很多。显示列表可以用在以下场合:
(1)矩阵操作
大部分矩阵操作需要OpenGL计算逆矩阵。矩阵及其逆矩阵都可以保存在显示列表中。
(2)光栅位图
程序定义的光栅数据不一定是适合硬件处理的理想格式。当编译组织一个显示列表时,OpenGL可以把数据转换成硬件能够接受的数据,这可以有效地提高画位图的速度。
(3)光、材质和光照模型
当用一个比较复杂的光照环境绘制场景时,因为材质计算可能比较慢。若把材质定义放在显示列表中,则每次改换材质时就不必重新计算了,因此能更快地绘制光照场景。
(4)纹理
因为硬件的纹理格式可能与OpenGL格式不一致,若把纹理定义放在显示列表中,则在编译显示列表时就能对格式进行转换,而不是在执行中进行,这样就能大大提高效率。
OpenGL提供glNewList 0与glEndList 0的形式创建显示列表。
5.4.3使用VBO扩展加载顶点常驻显存
VBO(Vertex Buffer Object)扩展是把数据保存在高速的显存中,而不是普通的系统RAM内存。它不仅仅降低了每帧的内存操作,而且减少了数据在显卡和内存之间的传输。这种技术应用在合适的时机将大大提高渲染效率。当然,这个扩展是依赖较新的硬件的,不是所有的图形卡都支持的,所以在使用它前需要检测显卡支不支持VBO扩展。在OpenGL中使用VBO可以通过以下几个步骤:
首先用glGenBuffersARB来产生一个可用VBO的ID,OpenGL通过这个ID关联你的数据。
然后通过glBufferDataARB把内存中的数据的指针和大小传递进去,glBufferDataARB将把你的数据拷贝到显卡内存中,这时我们可以把相应主机内存中的数据删除。
在访问数据的时候,用glBindBufferARB绑定你的数据,这样你就可以通过glDrawArrays等函数来把你的数据绘制出来。
5.5本章小结
本章主要介绍在课题研究过程中实现的一个室外场景渲染器的相关流程和框架,在更高层次上清晰地表达室外场景渲染的各个层面。此外,本章还涉及基于OpenGL的一些渲染优化手段。
6.1 总结与展望
场景的实时及真实感绘制是3D游戏引擎中非常重要的研究课题。它直接影响在游戏中的视觉体验是否真实,渲染效率是否达到当前主流硬件配置要求,其很大程度决定着一款游戏的品质。而随着游戏技术的深入发展和计算机硬件水平的提高,对3D游戏中室外场景渲染的真实性、规模性都提出了更高的要求。
本文在充分理解3D图形学理论的基础上,查阅了多篇重要论文,并研究了开源社区(WWW.sourceforge.com)中一款优秀图形引擎oGRE的相关技术,分析了wowmapview(魔兽世界地图查看器)源代码,最终形成了自己的一些方案并加以实现。现将本文的研究内容和研究成果总结如下:
1.对地形渲染中的网格简化进行了深入研究。充分比较ROAM算法、基于四叉树的LOD算法、GeoMipMap算法的优劣,并提出一种基于GeoMipMap的优化算法。本文也涉及算法的各个实现细节。
2.对游戏场景的数据加载方式进行了探讨。提出了一种基于内存缓冲池的动态数据加载方案从而实现超大(无限)无缝地形的实时绘制目标。
3.对室外场景中涉及的空间管理和面片剔除、裁剪技术进行了讨论。
4.对真实感的渲染技术给出了具体实现方案,其中主要涉及用多纹理混合模拟融合性地表,用光照贴图表现地形的静态光照,用交叉平面模拟花草,树和天空的渲染以及雾的特效。
5.基于前述的算法和方案,用VC+OpenGL实现了室外大场景渲染器DemoOSRender,验证了方案的合理性。
6.2 未来工作展望
3D游戏引擎是一块飞速发展的领域,高效显示芯片的不断出现给游戏引擎提出了更高的要求,很多理论、技术、方案随时出现,比如现在著名的虚幻引擎已经在游戏场景中实现了动态光源动态阴影。加上游戏引擎的编写不光涉及实现技术,它还涉及软件的总体架构与规划,模块的抽象与封装。所以本文对3D场景的渲染技术研究还处于初步阶段,有很多问题需要进一步研究,主要包括:
(1)对真实感渲染技术的进一步研究。在场景中实现动态光源光照效果,研究植物更逼真的渲染方案,比如目前国外著名的植物渲染方案SpeedTree。
(2)对河流的真实感渲染研究以及室内场景的渲染研究。
(3)对技术的封装和抽象,真正把场景渲染模块融入到整个游戏引擎中。