第二部分高级地形编程

聚焦3D地形编程第五章GeomipMapping for the CLOD

译者: 神杀中龙 邵小宁 microsoftxiao@163.com
翻译的烂请见谅
原著 《Focus on 3D Terrain Programming》

Woohoo!你现在将要学习地形编程的核心部分了,它们是由难以置信的复杂算法。实际上,那是谎话。本章我将解释三个算法,因为它们的简单和高效所以选择了它们。And , for once in this book, 下面的列表是本章将要讲述的内容:

n         解释持续层次细节的意思

n         geomipmapping的背后理论

n         实现geomipmapping的方法

为了简单那, 我打破了议程的三个部分。不管这些细节,本章将相当长。然而,不要让本章的长度吓到你。The content will be presented, as always, in a fun and simple manner. 注意,虽然我改变了一点学习风格。第5,6,7将集中在算法的解释上相比前几章将使用伪码表示。在以后几章,我将为你提供一些演示并且给出我的实现,但是实现是简单的,并且you should use them only in conjunction with the tex. With that said, 我们开始吧。

CLOD Terrain 101

在本书你已经听说了术语 Continuous Level of Detail(CLOD) 持续层次细节很久了,但是现在到了我告诉你实际情况的时候了。CLOD算法, in one sentence, 是一个动态多边形网格,通过增加额外的三角形来得到更多细节。这是个简单的陈述,但是读完本节你将了解更多关于CLOD的东西,并且知道本章结束。不要烦恼现在还不理解CLOD。

Why Bother with CLOD Terrain? CLOD地形为什么麻烦?

CLOD算法需要更多的研究,难于编码,平均比brute force实现占用更多的CPU周期。在你脑中,为什么CLOD算法麻烦?它真的简单吗: 为了创建更真实,更多细节,更重要,更快的地形碎片。

More Detail Is Added

Where More Detail Is Needed

CLOD的基本思想是添加更多的细节(更多三角形)是必须的。举例来说,如果我们有一个相当光滑的地形片(看图5.1),我们将想要一个平均更少些的地形(如图5.2)。然而,不是所有更多细节的算法都很糟糕。Geomipmapping可以似的三角形不是很分散但又拥有更多细节,但是Rottger的四叉树算法(在第六章,攀登四叉树)来做。因此,话说CLOD, 总体上,添加了更多细节到区域上,但并不总是这样,但是大部分是这样的,我彻底的把你搞晕了?

Cull Like You’ve Never Culled Before!

另外实际的CLOD基础算法是它们允许比brute force方法拣选更多的多边形。这意味着这些多边形发送给API但却看不见。举例来说,我们通过geomipmapping实现一系列的地形碎片。如果碎片不可见,我们排除潜在的289个被渲染的顶点(一个17x17的顶点碎片)in one fell swoop. 大量的顶点不会被加载到显卡里,而是被CPU裁剪了。使用简单的方法,我们满足GPU和CPU两者,让你的主板同时高兴。

Not Everything Is Happy

In the Land of CLOD Terrain

在CLOD地形大陆不是任何事物都是愉快的

虽尽管使用CLOD地形算法有一些缺点,Oddly enough, 世界帮助我们写下了这一段在 Game Developer Magazine里。

主要确定是包括每帧更新多边形网格。

当大部分这些算法还不是很流行时(geomipmapping, Rottger’s quadtree algorithm, and ROAM)被设计。这是因为该算法想要将更多的工作量放置在CPU里而且发送了一点必要的信息到了GPU。从此一直,然而,这些东西有了一丁点的变化。现在我们需要把焦点放在GPU比CPU更多些。

Wrapping Up Your Introduction to CLOD Terrain 封装你的初级CLOD地形

明显的,如果geomipmapping, 四叉树,ROAM算法过时了,你将不会再读到它们了。

这意味着某人,某地(heck, 甚至我)哪出了一些优化的,在今天的地形渲染也是最重要的优化。随即,我将解除前面胡说的关于geomipmapping的东西。

Geomipmapping Theory for the Semi-CLOD Impaired

Geomipmapping, 是Willerm H. de Boer开发,一个友好的GPU CLOD算法。它也是转变到CLOD大陆最完美的简单算法。那么我们继续前进,你也许想要查阅实际geomipmapping白皮书,我放在 CD-ROM里希望给你提供方便。(它的名字叫 /geomipmapping.pdf算法白皮书。)

Simply the Basics 简单的基础

如果你熟悉mipmapping的纹理化概念,那么geomipmapping似乎是很浅显的对你来说。概念同样,除了处理纹理被取代,我们处理地形片上的顶点。推动geomipmapping概念的是你有一个地形片集合。为了解释它,我有大小为5的顶点作为一片(5x5的片)。5x5的片将有一系列的等级细节,第0级是最详细的,这样,第2级是较详细的。

看图5.3如果你需要可视化解释每个各种等级的话。在图中,黑色顶点没有被发送到渲染API, 但是白色被发送到了。

如果查阅了Willern de Boer的geomipmapping的白皮书,你也许注意到图5.3和白皮书里展示的一点不同。原因是我做的改变了一点为了更清晰,但是现在,just know that I did it for a reason.

到了我们讨论更多geomipmapping的时候了。我前面已经给介绍了些基础,但是现在到了你知道所有东西的时候了….

好,几乎所有东西。我也许会保留些信息。

[译者注: Mip Mapping (Mip贴图) 这项纹理贴图的技术,是依据不同精度的要求,而使用不同版本的材质图样进行贴图。例如:当物体移近使用者时,程序会在物体表面贴上较精细、清晰度较高的材质图案,于是让物体呈现出更高层、更加真实的效果;而当物体远离使用者时,程序就会贴上较单纯、清晰度较低的材质图样,进而提升图形处理的整体效率。LOD(细节水平)是协调纹理像素和实际像素之间关系的一个标准。一般用于中、低档显卡中。

注意 Mip-Mapping和三线性过滤有区别。]

[

译者注 MipMapping 和三线性区别

Mip-Mapping

  我第一次看到Mip-mapping技术是在游戏QUAKE里,而现在这种技术早已是随处可见了。这种技术是由Williams在1983年发明的,“Mip”这个名称起源于“multum in parvo”,大概就是在一小块地方有很多东西的意思。

  具体说来,Mip-Mapping的思想就是构建一套纹理,总共需要大约1.3倍的内存。其中,每块子纹理是通过对父纹理过滤而得到,它的长和宽都是其父纹理的1/2,其面积为父纹理的1/4。接下来,在应用的时候,你根据距离选取最合适的一块来进行映射,实践证明,这种技术虽然简单,但对提高纹理映射的质量确实非常有效。

  通过Mip-Mapping,可以为较小的多边形映射上面积较小的纹理,这对减少纹理的扰动大有好处。举个例子,你有一块256x256大小的纹理,当它开始向远离观察者的方向开始移动时,你会看到它开始闪烁和颤动。这种现象的出现是因为我们把一大块纹理映射到一个很小的区域而引起的。你可能在上一帧时,画的是纹理中(50,20)处的像素,到了下一帧,却画的是纹理中(60,30)处的像素。如果这两个像素相差很大,你就会观察到前面所说的现象了。总的来说,这种剧烈的纹理座标的变化,会损害图像的品质,并且影响CACHE的效率,而Mip-Mapping无疑是解决这个问题的好办法。

Tri-linear Interpolation

  在介绍了双线性插值和Mip-Mapping以后,该来讲讲三线性插值了。其实三线性插值也很简单,它就是前两种技术的结合。它在对Mip-Mapping的每块纹理做双线性插值的同时,还要对Mip-Mapping中相邻的两块纹理按距离再做一次插值。既算出较大的一块纹理上的某点双线性插值像素值和较小的一块纹理上的某点双线性插值像素值,再按目标同两块纹理的距离做一次类似的插值。

  使用三线性插值,可以消除Mip-Mapping里纹理切换(既上一帧时用的是某个大小的一块纹理,而下一帧时又换了一块的情况)时的突然变化,从而可以提供很平畅的高质图像输出。

  同前两种技术相比,三线性插值的运算量非常大,目前只能依靠硬件来实现。

原文链接: http://www.chinagcn.com/blog/?43/viewspace-702

]

正如我前面说的,geomipmapping与纹理的MipMapping类似除了我们使用陆地片来替代纹理片。我们需要怎么做呢,从3D空间用户的出发点(摄影机眼睛的位置), 使所有的片的大部分细节都围绕者摄影机因为这些片是用户看得见的。

At a certain distance way, 我们将片切换到低的细节上。而更远的距离,我将切换到甚至更低的细节。图5.4是可视化解释。

如图所示,这些片是摄影机位置处的细节层次(LOD)为0,以为这这些片是最高等级的细节。这些片一旦拉远,细节将调整到1,是第二高的细节。如果距离摄影机更远的话,片将降低到等级2,是图中最低的细节。

Triangle Arrangement Made Easy

前面,你也许注意到5.3排列出的三角形不同于我提供给你的geomipmapping paper中的排列。这种情况你不需要立刻访问白皮书,看图5.5看,论文的建议三角形这么排列。

这个排列也许像更好的想法,and it is for the most part. (警告: I’m going to get slightly sidetracked right here.)三角形带如果你计划使用顶点缓冲渲染片的话,这是我给你的建议。然而,因为使用顶点缓冲区进行渲染辉高度依赖API,我选择使用直接渲染模式因为它更容易转换到其他API语法。使用顶点缓冲区渲染对地形实现来说更高速因为它的函数当你发送每个顶点,纹理坐标,颜色等到API时是单独的。另外,大部分图形卡更喜欢每个顶点信息被发送到顶点缓冲的形式。最后,我推荐你使用顶点缓冲区渲染地形。你将获得极大的速度,完成这个是相当有价值的。如果你喜欢看一个Direct3D顶点缓冲区实现geomipmapping-esque的例子,可以看“Simplified Terrain Using Interlocking Tiles”在 Game Programming Gems, 卷2里。

总之,该回到前面的话题了。如图5.3是我们将要渲染好的排列好的片。这个排列对我们来说是非常有益的:它允许我们更容易的跳过被渲染的顶点当我们需要时,which is quite often. 接着下一个话题。

Hacks and Cracks, But Mostly Just Cracks   砍,爆裂,砍,更多爆裂

常常当你处理CLOD地形算法时,你必须处理裂缝的问题。裂缝出现,在geomipmapping出现这种情况,当高细节挨着低细节时(看图5.6)。

正如你看到的图,片左边比右边细节高。我们的问题位于点A和B。问题是当A点比B细节高时,这意味着左边的精确度高于A点,但是右边的片刚刚是从那个高度平均过来的位于高度之下。这个破裂的东西也许不像个大问题,但是图5.7所示,展示了我的geomipmapping 实现没有裂缝吗?

这不是一个平滑的地形,是吧?这是所有洞穴的景色。我们来解决它!

Crack-Proofing Your Geomipmapping Engine 你的Geomipmapping引擎裂缝实验

裂缝穿透你的geomipmapping地形是比听着来的容易。你添加些有意的东西(that would be me)解释这个概念给你,整个处理过程是容易的… 好,好像是容易的。

我有两个可能的解决裂缝问题的方法。一个方法是在低细节地方添加顶点片以至于这个片区域将达到和高细节区同样的细节等级。这个解决方法是丑陋的,可是,它意味着我们必须对片进行重排列(添加其他的三角形扇)。

另外的方法是从更多细节片内删掉顶点。这个方法更有效一些。看图5.8看删掉顶点多么容易解决裂缝。

Where Art Thou Crack? 你在哪里裂缝了?

你知道怎么导致裂缝和怎样解决它们。真正的问题是: 你如何确定它们?基本上,当你当前渲染片,你需要测试片周围(看图5.9)看是否有低细节。如果它们是,你知道你需要忽略一些顶点了。

测试每个片不困难。你将需要实现一系列的简单的if-else语句。(Pseudo-code如下).

If LeftPath.LOD is less than CurrentPath.LOD

RenderLeftVertex = true;

Else

RenderLeftVertex = false;

If RightPath.LOD is less than CurrentPath.LOD

RenderRightVertex = true

Else

RenderRightVertex = false;

If UpperPath.LOD is less than CurrentPath.LOD

RenderUpperVertex = true;

Else

RenderUpperVertex = false;

If LowerPath.LOD is less than CurrentPath.LOD

RenderLowerVertex = true;

Else

RenderLowerVertex = false;

看多么简单啊?之后测试,渲染你的三角形扇,你跳过粗糙片的方向顶点。例如,右边片是低级等级的,那么你的当前片是高等级细节(行列翻倍被渲染), 那么你仅仅想要跳过这些顶点到远处的片(看图5.10)。

警告:

注意你省略的仅仅是必须的顶点。否则,你将结束整个片而不意味着省略。例如,图5.10, 片由多行多列组成的三角形扇。你不想省略右边的顶点;你仅仅想省略右边的顶点在最右列。

这是你简单的geomipmapping理论!现在到我们实现我们学过所有东西的时候了。

Implementing Geomipmapping for the Very Slightly CLOD Impaired 实现Geomipmapping CLOD

你了解了geomiapping背后的基础,但是现在我们需要实现它。这将使你的大脑负担太重。做完它将是最难的部分,照常,我们将每次做一步。来点咖啡,锁好你的们,然后来点好听的音乐!

Path It Up 拼凑

由于geomipmapping是由一系列碎片组成的,大概可以从一个好的想法出发,创建一个patch数据结构。这个结构不需要包含太多信息,而且我们至少需要包含,最好。实际上,这将是从本书里你见过的最小的结构。不要太习惯漂亮的大小!

所有的patch结构真的只需要两个变量。一个变量将记录当前细节等级,另一个变量存储从patch到摄影机的位置的距离。保持头脑正常!整个patch数据结构。在这里,代码:

struct SGEOMM_PATTH

{

float m_fDistance;

int m_iLOD;

};

它也许是微笑结构,但是记住: 大东西将由小东西组成。虽然小,我们将经常使用它,所以确认你已经花了几小时记住它的成员。

Creating the Basic Geomipmapping Implementation 创建基本的Geomimapping实现

Yeah, 两个成员的数据结构不要哭哭啼啼的。现在我们将开始geomipmapping引擎的重负荷工作——geomimapping类。开始,我们需要获得我们的patch信息指针,我们将动态的分配一些指针在我们的demo内。继续,我们需要计算出patch大小(顶点数)和地形的边需要多少个patchs。

警告

geomipmmaping实现是基于2的N次方+1个像素块高度图。这意味着你不能使用midpoint displacement不规则高度图产生高度图。所有你的高度图只能使用fault formation产生。

Patch的大小由用户来指定,所以我们可以让它指定patch大小当它初始化类时。(我趋向于严格的大约17x17顶点提供一个漂亮的细节和速度。本章将解释为什么采取这么大。)

Geomipmapping Initialization Geomipmapping初始化

为了初始化geomipmapping系统,我们需要用户指定他们的patch大小。(我将恢复我的建议17x17的顶点。)After we have that, 我们初始化系统。

首先我们许哟啊计算地形每个边的片。我们描绘出高度图和划分出独立的patch, 如图5.11。

P描绘一系列的每个边, h描绘出高度图的大小, s描绘出单独patch的大小。使用一个等式,看图5.12我们将在之后插入一个等式。

之后我们计算每个边的patch,我们需要为每个片分配正方形的缓冲区。(这个值我们仅仅完成这个计算。)

m_pPatches = new SGEOMM_PATH[SQUARE(m_iNumPathcesPerSide)];

继续,虽然它不是初始化必要的部分,我想计算出这个片可以达到的最大细节等级。注意最大等级是最小细节等级,最大细节等级是0。如果等级增加,细节减少。这的计算:

iDivisor = m_iPatchSize – 1;

while(iDivisor > 2)

{

iDivisor = iDivisor >> 1;

iLOD++;

}

所有我们做的许哟啊多少个循环获得iDivisor下至2。当iDivisor到达2时,我们不会继续减,并且我们计算一些我们处理的细节。例如17x17片大小,我们的最大细节为3,意味着我们有四个不同的细节等级(0, 1, 2, 3)从任何单个片选择。初始化吧!现在我们将移动到可怕的下一节。

Geomipmapping Shutdown 清理Geomipmapping

关闭geomipmapping系统是简单的。我们需要释放我们为patch缓冲分配的所有内存和让类成员变量都休息。

Geomipmapping Maintenance Geomipmapping维护

不像我们前三章的地形, CLOD地形算法需要在每帧更新(这也是为什么叫做Continuous Level of Detail的原因)。大部分基于CLOD的算法需要在更新阶段进行一些维护工作,但是geomipmapping不那么做。这个工作我们必须在更新函数彻底到最小限度时处理;它简单由我们的patch来计算得到。

为了实现我们的geomipmapping更新函数,我们需要为每个patch进行更新; 因此,我们需要制作一个双循环:

for(z = 0; z<m_iNumPatchesPerSide; z++)

{

for(x = 0; x<m_iNumPatchesPerSide; x++)

{

}

}

首先的东西我们需要在循环内计算从摄影机位置到当前片中心的距离。这个计算将是你在高校熟悉的数学,他们操练一个距离公式在你的大脑里。这种情况你可以像我一样所有课在睡觉,这里重复。

Dist = sqrt((x2-x1)^2 + (y2-y1)^2 + (z2-z1)^2); 3D距离公式

带着这个灯市,看图5.13变量将被插入到等式中。

这是距离计算的代码;

m_pPatches[iPatch].m_fDistance = sqrtf(

SQUARE( ( fX-camera.m_vecEyePos[0] ) )+

SQUARE( ( fY –camera.m_vecEyePos[1]) )+

SQUARE( ( fZ –camera.m_vecEyePos[2] ) ) );

之后我们从摄影机开始计算距离,我们可以计算出该片的细节等级。代码,我计算出硬编码的距离。(我的代码仅仅有一点是这样的;你也许想撇去这样的代码)。对于你的引擎,虽然,你想要更多严格的方式计算出细节等级。例如,在geomipmapping白皮书内, Willem de Boer描述了屏幕像素决定算法以至于当片改变登记,too mch popping won’t be present.

Popping是当多边形改变到不同的细节去出现的。这个改变也许或不会很明显。例如,从1改变到0没有引起popping因为1 等级片仍然是端正的细节(是17x17,至少) 。然而,从3级改变到2导致一点popping因为你从8个三角形到了32。虽然那些同样比率的三角形被在第一个片添加了,在3到2级是明显的。CLOD算法一个主要的目标是减少甚至完全消除popping。我将在稍后部分讲述它。

总之,这本书要实现我的geomipmapping,我简单硬编码远离了LOD。(我想排除和你的经验差距,更容易理解。是的,我知道我是个和蔼的家伙。) 这是改变的LOD代码小片:

if(m_pPatches[iPath].m_fDistance < 500)

m_pPatches[iPath].m_iLOD = 0;

else if(m_pPathes[iPatch].m_fDistance<1000 )

m_pPatches[iPatch].m_iLOD = 1;

else if(m_pPatches[iPatch].m_fDistance<2500)

m_pPatches[iPatch].m_iLOD = 2;

else if(m_pPatches[iPatch].m_fDistance>=2500)

m_pPatches[iPatch].m_iLOD = 3;

这些距离有效的速度整合和细节。如果demo有点在你的视频卡迟缓,你也许需要改变下这些距离。之后的章节我们将优化下速度,所以不要失望!那么更新所有geomipmapping片吧… 现在,至少。现在到了地形实现最有趣的部分了:渲染它!

Geomipmapping Rendering 渲染Geomipmapping

这可能是本章你遇到的最难的部分了,但也没那么坏。它偶尔变得复杂些,但是我带领你搞定它。

Splitting Things Up a Bit 劈开东西

最容易的方式是我们把要渲染的东西分离开以使得代码不是太膨胀。这也许增加了函数调用,但是我们做的只能些,它不是很坏。

我描绘它, geomipmapping类将是高级渲染函数,另外有一些低级的, in succession. 例如,最高级的渲染函数是Render。比Render更低的是RenderPath和RenderFan。 RenderVertex将是最低级的。使用这些函数,我们增加了一点函数调用开销,但是我们减少我们代码的丑陋。这种交易是值得的。如果你已经抓住设计的低级部分,如图5.14。作为实现我们的渲染系统,让我们从低和是高级的部分开始呢?

The RenderVertex Function

这个系统的顶点渲染函数不是特定的,但是公平起见,它是个小函数。我们将经常调用它,是个完美的inline函数。 RenderVertex设置顶点的颜色,并从光照图和分别乘以光颜色的RGB值以着色。然后RenderVertex发送纹理坐标到渲染API(为细节映射, 如果需要,颜色纹理) 。之后,一简单的需要发送缩放后的顶点坐标到渲染API。 That’s it!

Render->RenderPath->RenderFan->RenderVertex

The RenderFan Function

每个geomipmapping patch是在一些三角形扇处停止,不管它们是1 patch或者256 patch。使用这个函数在RenderPatch内整理代码,在下一节进行讨论。

所有渲染patch的函数都渲染单个的三角形扇。因此,函数需要允许扇形中心作为一个扇形的边传送到函数并渲染它。RenderFan也需要包含临近的patch信息。好,排序。临近的信息是独立的扇形。如果patch需要省略应得的顶点到海岸的patch在它的正确的一边,但是当扇形在patch的中间时,那么临近的结构显示出所有为真。(仅扇形在patch需要的右边时在大约顶点省略。) 如果扇形被渲染,然而,顶点需要被省略。例如,如果扇形在右边的一边,那么扇形将在LOD滑坡的一边,那么当前扇形需要省略右边的顶点。

The RenderPat Function

Patch-rendering函数是整个渲染系统是重要的因为不渲染patch, 你不会看见地形。大部分裂缝都发生在这个函数,使用休息发生在RenderFan函数。

记得我展示给你的伪代码了么? Where Art Thou Crack? 好,这里我们将实现它。我们需要填充临近的结构,这是一个简单那的数据结构四个布尔值标志提供给临近的patch(左,右,上和下patch)是高等级细节多于当前的。如果临近的标记为真,那么我们可以正常渲染在patch边上的因为我们不需要特殊的预防裂缝。如果标记为false, 那么我们需要指定特殊的量。

注意:

当我们谈论关于“高级细节”时候,我们以为这” 低级细节.”这因为我们的等级系统从高(低值)到更高,而0是最多细节的。千万不要搞乱了当我们说高/低细节时。

之后裂缝预防步骤,我们需要计算出起始渲染三角形是怎样的。它也许稍微变得复杂了些。最难的部分是计算三角扇的中心的距离。之后再完成, we’re good as gold!

我们怎样计算出每个扇形间的距离呢?好,我要着手做了,虽然它看起来是不固定的,以独立的patch大小开始并且试着计算它并在每个扇形中心来划分。我计算出每个片大小的约数,然后while循环计算出被多个总的patch划分。例如,如果patch是0等级,我们用patch自己去划分,我们渲染它直到产生一个1的长度。(我们做但不想缩放顶点知道我们执行了RenderVertex函数。)如果patch等级为1, 每个扇形距离将是2单位, 等级为2将为8个单位等。之前的计算代码像这样:

fDivisor = (float)m_iPatchSize;

fSize   = (float)m_iPatchSize;

iLOD   = m_pPatches[iPath].m_iLOD;

// find out how many fan divisions we are going to have

while (iLOD >= 0)

iDivisor = fDivisor/2.0f;

// the size between the center of each triangle fan

fSize /= fDivisor;

尽管计算结果不完全是正确的;当我们使用它们,他们产生图5.15的地形。

这些计算做错了? 好, 我们犯了一个简单的错误。 我们想要分割变量, fDivisor, 作2的N次方。记得当patch大小和divisor相等时, 在扇形等级0和1直接有1个单位? 好的,从中心到中心, 我们需要至少两个独立的单位(看图5.16。)。你看到如图5.16成对的扇形彼此交叉(产生了相当难看的结果) with a one-unit interval and how the two fans on bottom fit together perfectly with the two-unit interval? 好的, 我们需要改变前一个扇形中心间隔计算所以在patch间的最小间隔为两个单位。How de we do this, 你问?这相当简单。我们刚刚设置了初始除数变量为patch大小减1, 总是2的平方的约数, 因此我们已经解决了前面的所有问题。检查新代码。(fDivisor变成iDivisor以致于我们加速了一点计算。)

fSize = (float)m_iPathSize;

iDivisor = m_iPathSize – 1;

iLOD = m_pPatches[iPatch].m_iLOD;

// find out how many fan divisions we are going to have

while(iLOD >= 0)

iDivisor = iDivisor >>1;

// the size between the center of each triangle fan

fSize /= iDivisor;

完成之后, 渲染了patch的三角形扇形就变得微不足道了。你仅仅需要确定你开始渲染fSize的一半因为第一个扇形的中心。我们需要检查每个边是否省略了顶点。这意味着我们需要使用patch相临结构的信息,that we filled out earlier并应用它到每个片的被渲染的边,如下演示:

// if this fan is in the left row, we might need to

// adjust its rendering to prevent cracks

if( x==fHalfSize)

fanNeighbor.m_bLeft = patchNeighbor.m_bLeft;

else

fanNeighbor.m_bLeft = true;

// if this fan is in the bottom row, we might need to

// adjust its rendering to prevent cracks

if(z == fHalfSize)

fanNeighbor.m_bDown = patchNeighbor.m_bDown;

else

fanNeighbor.m_bDown = true;

// is this fan is in the right row, we might need to

// adjust its rendering to prevent cracks

if(x>=(m_iPatchSize – fHalfSize))

fanNeighbor.m_bRight = patchNeighbor.m_bRight;

else

fanNeightbor.m_bRight = true;

// if this fan is in the top row, we might need to

// adjust its rendering to prevent cracks

if(z>=(m_iPatchSize-fHalfSize))

fanNeighbor.m_bUp = patchNeighbor.m_bUp;

else

fanNeightbor.m_bUp = true;

// render the triangle fan

RenderFan((PX*m_iPatchSize)+x, (PZ*m_iPatchSize)+z, fSize, fanNeighbor, bMultiTex, bDetail);

填充分离的扇形到相邻结构, 我们必须重新计算patch的相临结构。扇形结构被发送到渲染扇形的函数里,习惯于找出无论任何顶点需要从渲染时被省略掉。那是因为所有的低级渲染函数。现在我们需要简要的讨论高级渲染函数,因为用户将要使用。 我们可以释放demo5_1到世界中了!

The Render Function 渲染函数

好,我们刚刚完成了我们的简单geomipmapping实现。你激动吗?我知道我很激动!

在高级渲染函数里,我们需要循环遍历所有的patchs并调用RenderPatch函数,但是我们将有三个不同的渲染patch循环。还记得我们的brute force实现么?我们仅仅需要成为循环体的一体如果拥护开起多纹理的话;然而,如果用户没有开启多纹理,我们也许需要制作纹理贴图通道还有细节贴图通道。两个不同的渲染通道不是好东西,可是,所以一个选项是如果用户不支持多纹理就不进行细节映射。如果你已经阅读了从开始到现在的章你将很熟悉这个概念。唯一的东西是你必须循环遍历所有的patch和使用RenderPatch函数来渲染它们。

That’s it! 我们完成了接纳的geomipmapping实现。看demo5_1(位于CD盘的Code"Chatper5"demo5_1)并且看图5_17的截图。左边展示了纹理/细节贴图右边是线框情形下的图。注意在线框模式下离近距离你可以看到更多细节比远处。这真是一个漂亮的CLOD算法!

Problems Exist to Be Fixed 存在的问题被解决

Yeah, yeah, 我知道。我仅仅完成了geomipmapping的一部分问题。例如,除非你有非常高级的卡,你可能经历了可怕的前一个demo的帧速率。(我在GeForce 4 TI4600,在我写本书时时常上最好的卡,并且稳定在45-50帧每秒) 这个demo也会遭受当地形改变的他们低级细节时的裂缝问题。我们将解决所有上述章节的问题,所以不要担心!

Adding a Bit of Juice to the Engine   给引擎来点果汁

首先,我认为我们将加速我们的实现。加速是容易的。使用一些简单的视锥裁剪,因为我们的地形被分割成了多个patch, 我们最好只裁剪视锥外的patch,这意味着不需要多少测试。

Cull Like You’ve Never Culled Before… Again

我们将完成基本的视锥裁减。我添加了一些视锥计算函数到CCAMERA类基于 Mark Morley的文章 “Frustum Culling in OpenGL, “ 我认为最好的视锥裁剪的文章在互联网上。(你可以从这里找到: http://www.markmorley.com/opengl/frustrumculling.html.) 是的,我承认,我的数学知识不太性(注意遍及这本书的数学变复杂!), 但是那可能是个好东西——除非你的数学瘾君子, in which case, 我大概除了不喜欢邮件外。

总之,这里是我们将要使用的基础。我将再次使用视锥对patch进行裁剪(如图5.18你需要一个可视化的更新)以排除不必要的对CPU/GPU的周期的浪费。(如果观察者不能看到它,将不会被渲染/更新。)

我们需要再次利用视锥来对地形片进行测试。To do this, 我们制作了一个Axis-Aligned Bouding Box(AABB)包围住patch.(实际上, we make more along the lines of a cube.)那么我们想再测试视锥呢。为了计算patch的尺寸,我们在中心并缩放了变量的大小。(看图5.19).

因为我们仅仅处理patch中心,你需要一般的大小作为函数参数(Cube 和视锥相交)并计算出正方体的角度。我们也获得了更精确的盒子,但是在我的裁剪实现下,其余的空间缓存是必要的以为了视锥不能看见地形而矛盾(像patch轻微的可见 but ends up getting culled anyway).

现在你知道更多关于拣选和它的实际是如何用于地形的了吧,检测demo5_2(在CD上Code"Charpter5"demo5_2)并且作为你获得更快的证据。例如,在图5.20上,用910个patch制成, 我们仅仅369。而demo,我稳定工作在(80-120帧), 并且高度图使用了2倍大的在demo5_1上。还不算太破!

Pop Today, Gone Tomorrow 今天破裂,明天死掉

下个问题是解决之前的问题。我们的目标四简化或者——甚至更好——略过裂缝。

有一系列的方式关于这一点,我将描述它们背后的两种原理。而实际实现你自己来做。

Morph It! And Morph It Good! 它的变体,变体更好。

首先的解决方案叫geomorphing,几何变形。虽然名字听起来像一个巨大的网格,它绝对没有庞大的机器人。Gomorphing实际过程是是逐渐的变形多边形的顶点(像我们的geomipmapping patch一样)改变他们的等级细节。我个人认为这些mesh将更有趣,但是实际意味着几何变形更有用。

为什么是有用的,你会问?这相当简单,实际上。你看,当一个patch低级时,大约高度为这个区域(图5.21)。并且,在图中,patch接近黑点的标记出来的值。

你看在低的patch上有多少个黑点?好,接近低细节patch有多高的值。这个近似值接近于实际的值,但是不会等于实际值。因此,当从等级3细分为等级2patch(or vice-versa), 裂缝发生;那是因为那些值被实际的值替换了(或者真实的值被接近的值替换了。)

Geomorphing解决这个问题计算出从实际值到远处接近的值并从超过大于255的范围内进行插值。(Or vice versa…again.好的,从这外面,我将假设我们细分了patch. 如果你合并了patch, 就推翻了所有的计算。)为了计算一串你需要移动每步的单位,就用这个等式:

Geo = (to-from) / numSteps

在等式内, “to”是你要用的实际值, “from”是要用的近似值,而”numSteps”是你想要记性geomorphing替换的步长,我建议设置为255。整个过程使用图5.23解释。

好,that’s it. Geomorphing是简单的概念,并且你可以用不同的方式实现。(我不想限制你的想象力,这就是为什么我没有实现的原因)。Go have some func with it, 但是要确认你记得并读了下节,帮助你减少裂缝甚至更多。

Should Lights Make a Popping Noise? 光制作裂缝燥声?

答案是不,尽管这不重要。重要的是减少我们地形实现里的裂缝。现在,可是多半裂缝户出现等改变LOD等级使用geomorphing, 另外问题是,我们使用了goraud光照对每顶点!虽然听起来不是太大的处理。使用LOD切换,光照被应用到了多或少的细节patch上,那么将发生。幸运的是解决这个问题是微不足道的,并且你可以用不同的方式处理。

一种方式解决这问题是结合地形光照图使用地形的纹理图。这允许你执行光照和纹理的渲染通道。不幸的是,这意味着你不得不制作你的光照图和纹理图用同样的分辨率并优化结果。因此,如果你动态改变光照,你也动态改变了纹理映射,代价相当昂贵。另外解决的方式是光照图纹理通道分离。这视为光照图作为纹理,忽略每顶点光照,允许你动态更新而不必烦恼纹理图。不幸的是,这意味着用户需要至少3层多纹理单位来接近效率。而且,甚至用户有两个纹理单位,执行2次渲染通道不是个好思想。多通道渲染不擅长处理小模型,但是巨大多边形集数据,像地形Mesh, 它擅长。

这个解决方案你伴随你完成。如果你不需要做动态斜坡光照或光照图的话就接近完成了,而第二个方法如果你知道你的用户图形卡支持3层或更多纹理单位的话就加上。请小心的使用额外的纹理单位。

Summary 摘要

好,真是够长的一章,但是你达到目标了。祝贺你!本章你全面的学习了CLOD地形,但是你也了解了geomipmapping CLOD地形算法。你学会了怎样加速geomipampping和怎样减少LOD产生的裂缝。所有这些都将应用到下一章。你要支撑住: 四叉树结构是基于CLOD算法的。

References 参考

1 de Boer, Willem H. “Fast Terrain Rendering Using Geometrical Mipmapping. “ October 2000. http://www.flipcode.com/tutorials/geomipmaps.pdf

转载于:https://www.cnblogs.com/microsoftxiao/archive/2008/07/09/1238558.html

聚焦3D地形编程第五章GeomipMapping for the CLOD相关推荐

  1. 3D地形编程——之GeoMipMap基础

    HoneyCat游戏编程学习笔记---之3D地形编程 原著<Focus on 3D Terrain Programming> 翻译:邱涛 风轻炫舞 HoneyCat Email:qiuta ...

  2. 3D地形编程——之GeoMipMap基础(1)

    HoneyCat游戏编程学习笔记---之3D地形编程 原著<Focus on 3D Terrain Programming> 翻译:邱涛 风轻炫舞 HoneyCat Email:qiuta ...

  3. 使用ThreeJs从零开始构建3D智能仓库——第五章(添加货架、货物与侧边栏)

    使用ThreeJs从零开始构建3D智能仓库--第五章 写在前面 创建货架对象 创建货架.货位类 根据配置添加货架 添加货物 添加信息侧边栏 结束语 写在前面 本章我们来讲解下如何添加货架货物和显示各类 ...

  4. Windows核心编程 第五章 作业(上)

    第5章 作 业 通常,必须将一组进程当作单个实体来处理.例如,当让 Microsoft Developer Studio为你创建一个应用程序项目时,它会生成 C l . e x e,C l . e x ...

  5. 第四章 基本TCP套接字编程 第五章 TCP客户/服务器程序实例

    TCP客户与服务器进程之间发生的重大事件时间表 TCP服务器 socket() --- bind() --- listen() --- accept() --- read() --- write -- ...

  6. 3D游戏编程 作业五 枪打恶鬼(打飞碟)

    前言 这次的作业个人感觉挺好玩的,我给做成了射击游戏.同时我也在其中学到了unity的一些有趣应用, 下面先把大的框架给讲一下,然后再着重讲一下我个人的收获吧. Asset文件:github 参考博客 ...

  7. Windows核心编程 第五章 作业(下)

    5.4 查询作业统计信息 前面已经介绍了如何使用 Q u e r y I n f o r m a t i o n J o b O b j e c t函数来获取对作业的当前限制信息.也可以使用它来获取关 ...

  8. python程序操作的核心_python核心编程-第五章-个人笔记

    1.用del删除对对象的引用 >>> a = 123 >>>a123 >>> dela>>>a Traceback (most ...

  9. (深入.Net平台和C#编程)第五章.体检套餐管理项目.20170408

    -----------------------------------------------------体检项目类------------------------------------------ ...

最新文章

  1. JSP导入XML不成功的一个原因
  2. [转载] Python 递归函数
  3. nginx之lua_shared_dict命令
  4. 感知机学习算法的直观解释
  5. Aardio格式化代码工具
  6. 2019测试指南-web应用程序安全测试(二)指纹Web应用程序
  7. PHP检查日期格式是否符合
  8. python论坛签到_python简单实现网站打卡签到
  9. 百度地图jsApi,地图拖动,中心定位图标不动,准确获取拖动过后中心定位图标所在位置
  10. JS格式化JSON,JSON着色
  11. 前端 css 自动生成,关于前端:利用Zeplin从设计图自动生成CSS提高前端样式开发效率...
  12. 【Scratch】青少年蓝桥杯_每日一题_6.25_加密
  13. 人机对话这件事为什么难?| 清华x-lab人工智能研习社
  14. ssh登录windows
  15. 解决了新版微信个人收款二维码在H5网页里不能长按识别支付
  16. 乐优商城(三十)——授权中心
  17. R: RStudio的中文读取、保存与显示
  18. 百度云盘archlinux manjaro直接安装
  19. 腾讯技术分享:微服务接口设计原则
  20. 宽带不能上传发文件_此时此刻(2019年),移动宽带如何了?

热门文章

  1. 分布式消息通信ActiveMQ原理-持久化策略-笔记
  2. Oracle Hint 之 Parallel
  3. PHP/TP5 接口设计中异常处理
  4. [日推荐]『知乐邀请函』好用的H5制作工具
  5. 位运算及在java中的应用整理
  6. php中this的含义
  7. 时光穿越_电脑时间显示为2011年6月1日
  8. GridView导出Excel研究
  9. mysql实现row_number()和row_number() over(partition by)
  10. 基础语法(IDE:PyCharm)