XNA下的自然场景实时渲染

课程完成,把试验报告放上来留个纪念。
实时动态自然景物模拟实验报告
1、            选题背景
自然景物模拟是计算机图形学中一个充满活力和挑战的研究课题。它研究的对象都是模拟出大家日常生活中见到大自然场景,如茂盛的植被、绵延的山丘、广阔的天空、荡漾的水波等等。然而如何模拟出如此复杂的大自然,进而把它动态的实时的绘制的出来缺失一个巨大的挑战。可是经典的计算机图形学的方法在这一领域并不适用。首先自然界中的许多现象是难以用几何模型有效的表示,例如山体等地形和植被的构造;而且许多自然现象的模拟总是与一些复杂的物理模型相关联,其运动无法用一个的简单模型表示,例如云的模拟和水波的模拟;再有自然界中的存在的物体数目极为巨大,其渲染也是一个很复杂的过程,传统的渲染算法和光照模型也难以适用。
其实,完全真实的仿真各种大规模的自然现象对于虚拟现实等自然景物模拟的主要应用来说是完全不必要,也是不可能的。基本上我们需要的只是一个能够生成这些现在的近似图像的算法,真实性并不重要,只要最终的图像看起来像就可以了。为了模拟自然界中的各种现象,几十年来许多研究者采用各种方法研究出了许多特殊的方法。而近年图形处理器(GPU)性能的飞速提升和通用图形处理编程(GPGPU)的发展完善极大的提高了个人计算机对三维图形算法的处理速度。本程序就主要利用了GPU的处理能力,生成并实时显示地形、水面、天空、草地和树木。
2、            开发与测试环境
本程序使用Visual Studio C# express开发,三维显示采用Microsoft XNA Game Studio express函数库,运行时需要支持Shader Model 3.0的显卡,.Net2.0运行库和XNA framework运行库。目前在测试系统(AMD Sempron 2500+,1G内存,Geforce 6800GS)上本程序在默认设置上能在800×600的分辩率下以10帧左右的速度运行。(主要是草地和数目比较耗资源,如果减少其数目可以很大的提高帧率。)
3、            地形生成和显示部分
l         地形的数据结构
本程序中地形是根据高度图生成的。高度图是一个记录高度信息的数组,它的长宽是我们所需要地形大小的长度加以1所得出的值,它的单元是一个16位无符号整数,足以满足地形细度的要求。
细节分级地形的最基本单元就是地形方块(Patch),它是我们实现地形的最基本骨架。地形方块将拥有不同细度层次的子模型,子模型根据与之相对应的LOD层次的低细度缩放标尺来生成。最高层次的子模型的低细度缩放标尺为1,也就是不简化。我们的地形模块定义的低细度缩放标尺的规范是:0~2LOD层次分别对应低细度缩放标尺为。
地形模型生成中比较常用的地形网格编织方式是三角形扇,其优点是方便生成,原理简单,但是其覆盖面积小,容易造成绘制指令调用过多的情况。为了避免这些缺点,我们采用了三角形条带的编织方式。其优点是可以编织出覆盖范围大的网格模型,而且比三角形序列要节省资源(它使用到的顶点索引数目少)。我们的地形模块所采用的编织顺序为:左右循环。在每一行的末尾需要重复一个顶点作为换行之用。为了解决细节层次切换时高度突变的问题,我们采用了这种用于可编程管线的编织方式,如下图所示。
                                                        图:可编程管线模式
可编程管线下,不需要包含贴图坐标,因为这可以通过着色器程序即时运算出来。本程序中仍然保留一层贴图坐标的数据,但它并不是用来保存贴图坐标,而是用来保存不同LOD级别下顶点的高度信息,所以除去0层高度信息保存在位置数据里,其余两个高度信息将被保存在这一层贴图数据中,而0层的LOD下所有相应顶点的位置保存在顶点缓冲中。这样可以在着色器中进行LOD中的顶点高度混合操作。这样就可以很方便的解决不同LOD层次间的裂缝问题。
l         地形生成算法
本程序采用了采用菱形-方形网算法。
首先为要生成三维地形分配数组来存储各点的高程,将四个角点的高度值初始化,然后进行以下两步的递归细分:
4、      步骤:取四个点的正方形,在正方形中点生成一个随机值,中点为两对角线交点。中点高度值是四个角点高度值的平均再加上一个随机位移量计算得到的。这样就得到了一个棱形网格。设四个角为a、b、c、d,则中点e的高度值则由式子diamond
5、      步骤:取每个四点形成的棱形,在棱形的中心生成一个随机位移值。平均角点高程(或属性)值再加上与 diamond 步相同的随机量,计算出每条边中点值。这样又得到一个正方形网格。square
这个步骤在代码的注释中也有详细说明。其中random项为每步所加的偏移量,是一个具有高斯分布的随机变量。但是一方面由于C#函数库没有提供高斯函数,另一方面这里为了计算的速度,我们使用了普通的均匀随机变量以代替。事实从效果上看真实度的差别并不大。最后,为了保证最终得到地形的效果,程序中还手工对地形进行了一些处理,把地形的中心降低,四角升高,以保证在最终显示时不会看到地形的边缘影响效果。
l         可见性检测
本程序中地形块的可见性检测包括距离检测和视锥检测两个方面。距离检测,就是检测摄像机与方块中心的距离,以确定此地形方块是否处于有效视觉范围之内。视锥检测是将每个地形方块的包围盒与当前的视锥体进行相交检测。
经过这两个步骤的地形块剔除,使得需要显示的面片数量大大减少,提高了显示的效率。同时,由于草地和树木也依附于地形块,此步骤也可以直接判断草地和树木的可见性。
l         层次细节显示
本程序中采用了3层的层次细节来实现地形的层次细节简化,地形的显示采用了GeoMipMap的方法。
对每个地形块所显示的细节层次的选择。这里使用的是距离选择,即根据每个地形方块的中心位置与摄像机之间的距离差别来决定地形方块当前的细节层次。对于不同细节层次之间的过渡,在可编程管线上采用了渐进式细节层次过渡,它可以有效防止地形撕裂和层次切换突变的出现。本程序的做法是在顶点信息中保存不同细节层次下该顶点的高度信息,在顶点着色器中根据顶点与视点距离来插值计算该顶点当前应该处于的位置,使得不同LOD层次的地形块在相同位置的顶点有相同的高度值。(此算法的主要问题是在移动视点时同一位置的高度会有一定变换,影响视觉效果。)地形的渲染采用了8个细节纹理逐次混合得到(目前没有计算地形的法向,也没有计算地形的光照。),各纹理的坐标都根据此点的世界坐标计算生成,同时根据象素点到视点的距离计算一个表示雾的混合项。地形渲染的网格如下:
最终生成的地形效果如下(没有显示草地和树木):
6、            水面渲染
水面渲染的网格仍然采用绘制地形的网格。这样可以节省系统资源,同时也很能够利用地形网格本身的层次细节能力,而且可以在着色器上方便的计算出水面的深度,但是这样使得水面的网格的粒度比较大,对于波浪的位移表现的不精确细致。
水面的网格在着色器中统一设定到固定的高度,然后在计算两个正弦波合成其在高度方向上的位移。(本来准备使用顶点纹理访问(Vertex Texture Fetch)来从多个高度图中获得顶点的高度位移,但似乎准备的高度图有点问题,效果不明显,因此只有使用简单的正弦波混合。)而水面的法向在象素着色器上通过对两张凹凸纹理(Bump map)的混合来确定。这样就可以在粒度较大的网格上很好的表现水面的细节。(缺点是难以与通过正弦波混合成的高度配合。)取得此点的法向后,再根据此点到视点的方向就可以算出此处的水面高光项。同时也可以计算出此点的视线反射方向,以此方向访问环境纹理贴图,就可以得到此点的反射项。而再根据此点的深度以法向方向扰动当前点的世界坐标值,再以此计算地形的颜色值,作为此点的反射项。最终跟据此点的深度值混合反射项、折射项,再加上高光项就得到最终的渲染颜色。
 
7、            天空的渲染
天空体的网格模型采用了一个半球面和一个圆柱面合成。为了得到比较平滑的效果,在高度维度上把它压缩到1/4,整体贴上一个天空的纹理。云层通过不同的纹理偏移量从两个纹理贴图中混合产生,并渲染到天空体的半球上。太阳采用一张贴图渲染再用Alpha混合绘制到天空上。通过不断的更新云层的纹理偏移量就可以实现云层漂移的效果。
8、            草地渲染
为了得到更好的效果,本程序使用了简单的草模型+硬件Instancing技术在一个渲染批次内绘制大量的草。所谓Instancing,就是把提供给顶点渲染器的顶点数据分为模型数据和属性数据两个部分。在模型数据中提高草模型的局部坐标值、法向等直接从模型文件中提供的信息。在属性部分中提供每一棵草的世界坐标系坐标、方向、大小、摆动起点等信息。
在模拟草的摇摆时,对每个顶点分别做一定的位移(直接跟据其局部坐标系的高度值乘上一个正弦函数值再乘上位移方向来计算)。下图是本程序实际渲染出的草地效果:
9、            树木生成
这次大作业主要采用 95 年的文章 Creation and Rendering of Realistic Trees[1] 中提出的 3 维树木生成、绘制办法。
这篇文章中提出了一个生成树木的模型,这个模型是基于树木的整体几何结构。这个模型能够生成不同种类的树木以及相关的植被。这个模型还能够处理随机参数以保证一种特定的树木也有许多不同的结构变化。
文章中的模型生成树木主要基于两种元素,树干和叶子。树干包括树木的主躯干和分支。树干单元模型主要是用一个窄的类似圆锥形的管子表示。而每一层上的树干都被分解成为一系列的类似圆柱的片断,通过随机生成这一系列片断的 z 坐标轴方向,可以使得树干变成简单的 S 形。在模型种,树干的分裂可以通过随机的参数确定分支个数以及角度。同一层上的分支,可以采用复制的手段生成。仅仅通过复制的手段生成的分支,从分支的类型上说,显然不是很丰富。因此在生成下一层次的分支时,主要是通过利用上一层分支参数计算出本层分支的参数,并在一定范围内进行随机。通过这样的方法即保证了分支在某些特征上的延续,又加入了随机的多样性。同时,文章的模型中还提出了分支的范围控制,除了主躯干以外的分支的范围都有其父分支的范围的一个函数定义。
模型中叶子的生成。在模型中,分支的层数通常为 3 4 层。在分支递归的最后一层递归时采用叶子替代分支显示。叶子的参数利用分支中的部分参数,主要是角度信息。叶子的密度类似于分支也是由父分支的一些参数决定。关于叶子的形状是根据预先定义好的形状生成,具体参数根据树木类型不同而设定。
文章提出的模型还提出剪枝用于保证生成的树木能够包裹在一个特定包围中,以使得生成的树木与真实树木模型相似。在生成每个新的分支时,要求分支调整自己的长度以便落在包围中。文章提出的模型可以通过修改参数来修改包围的大小和形状。
在真实场景中,树木不是静止不动的,会因为风的作用产生摆动,文章中也通过引入分支片段的曲率根据时间变化来达到模拟树摆动的效果。通过随机产生的摆动参数,在动画的每帧内重新计算分支片段的位置。
在文章的模型中为了考虑树木向上生长的趋势,引入了垂直方向上的吸引力来修正分支片段上的曲率。
最后,因为实际上,树木的叶子的面朝向有一定要求,一般是向阳和向光的,因此要引入参数重新修正叶子的朝向。
程序中采用的树的参数来源于《 Creation and Rendering of Realistic Trees 》论文附录中的几种树的参数以及 sourceforge arbaro project 上提供的一些树的具体参数,程序实现了主要的 8 种树的生成。
树生成设计到的类具体如下:
Tree 类:一颗树的类,可以通过 Grow 方法生成整棵树。
TStem 类:一颗树上的一个枝干的类,可以通过 TStem substems 访问孩子节点。
TParameters 类:专门保存一颗树的具体参数。
TSectionFrame 类:专门保存 Stem 上每个 Section 的旋转矩阵和原点。
l        Tree 类具体实现:
GetRandomValue 方法:获得一个指定上界的随机浮点数,其中随机数的种子由 Tree 类的 mSeed 决定。
Grow 方法:由 Tree 中的主干生出子节点的树,以及树叶。
CreateMesh 方法:获得 Tree 中所有的枝干节点以及树叶节点,并且获得枝干和树叶上的三角形。
l        TStem 类具体实现方法:
CreateSection :生成每个 Stem 上某个小 Section 圆柱的顶点,根据传入的 pSectionFrame 决定 Section 的位置, fSectionRadius 决定圆柱的半径。
CreateStructure :生成指定 Stem 的子节点的结构,如果子节点为叶子,则根据指定参数生成叶子的数目;如果子节点为 stem ,则生成 stem 的子节点的结构,再递规调用子节点的 CreateStructure 方法。
CalculateSectionRadius :根据实现指定的参数计算每个 stem 上每个小 section 圆柱的半径,每个 section 的半径是由 section stem z 方向的位置决定的。
CalculateVerticalAttraction :计算垂直引力
CreateLeaf :构造叶子顶点,根据 LeafShape 参数生成叶子的四边形结构。
Grow :生成当前 Stem 上的所有的 section 圆柱,根据当前层上的 CurveRes 参数修改每个 Section pSectionFrame 参数以达到在一个 Stem 上的 Section 有弯曲效果。生成完以后根据 Stem 的子节点是叶子还是枝干调用 GrowLeaves GrowSubStems
GrowSubStems :生成当前 Stem 的子枝干的每个 SubStem 的朝向和位置,以及初始半径等参数,然后调用 Grow 生成这些子 Stem
GrowLeaves :生成当前 Stem 上的叶子的位置和朝向,再调用 CreateLeaf 生成叶子。
AddLeavesVertices :将生成的叶子顶点组合成一个 ArrayList
AddMeshVertices :将生成的枝干的定点组合成一个 ArrayList
AddMeshFace :生成枝干的面片的顶点序
AddLeavesFace :生成叶子的面片的顶点序。
以下是本程序生成的各种类型的树木的图像:
 
 
此算法生成的树木在本程序中也使用Instance的方法来同时显示多棵同样的树木。但由于生成的树木面片树木很多,对程序的性能有很大的影响,因此无法同时渲染太多的树木。
10、      实验结果
   
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值