﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-xosen-文章分类-3D图形算法</title><link>http://www.cppblog.com/xosen/category/10101.html</link><description /><language>zh-cn</language><lastBuildDate>Fri, 03 Apr 2009 19:40:30 GMT</lastBuildDate><pubDate>Fri, 03 Apr 2009 19:40:30 GMT</pubDate><ttl>60</ttl><item><title>3D引擎架构</title><link>http://www.cppblog.com/xosen/articles/78922.html</link><dc:creator>xosen</dc:creator><author>xosen</author><pubDate>Fri, 03 Apr 2009 18:50:00 GMT</pubDate><guid>http://www.cppblog.com/xosen/articles/78922.html</guid><wfw:comment>http://www.cppblog.com/xosen/comments/78922.html</wfw:comment><comments>http://www.cppblog.com/xosen/articles/78922.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/xosen/comments/commentRss/78922.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/xosen/services/trackbacks/78922.html</trackback:ping><description><![CDATA[<p>Introduction （简介）</p>
<p>让咱们谈谈你如何撰写一份提供优雅性能的3D引擎。你的引擎需要提供的包括：曲面(curved surfaces)、动态光线(dynamic lighting)、体雾(volumetric fog)、镜面(mirrors)、入口(portals)、天空体(skyboxes)、节点阴影(vertex shaders)、粒子系统(particle systems)、静态网格模型(static mesh models)、网格模型动画(animated mesh models)。假如你已经知道如何以上所述的所有功能顺利工作，你也许便能将那些东东一起置入到一个引擎当中。</p>
<p>等等！在你开始撰写代码前你必须先构思一下如何去架构你的引擎。多数来讲，你一定是迫切地渴望去制作一个游戏，但如果你立即投入便开始为你的引擎撰写代码后，你一定会觉得非常难受，开发后期你可能会为置入新的特效与控制而不得不多次重写大量的局部代码，甚至以失败而放弃告终。花一点时间好好地为你引擎深谋远虑一番，这将会为你节省大量时间，也少一点头痛。你一定不会急切地去架构一个巨型的工程；或许你也会在引擎未完成时而干脆放弃它，然后去干的别的什么事儿。好了，当你掌握学习你所需知识的方式之前，也许你还不能完成那些事儿。将设计真正地完成确实是件美事，为之你会感觉更好，你将为之而耀眼！</p>
<p>让我们分析一下具备完整功能的3D游戏引擎的需要哪些基本部件。首先，这为具有相应3D经验但且还需一些指引的开发者提供了一些信息。这是一些并不难且能快速掌握但是你必须应用的内容条目。为将你的工作更好地进行下去，这里将对关于&#8220;把多大的工作量&#8221;与&#8220;多少部分&#8221;置入一个游戏引擎给出一个总概。我把这些成分称为 系统（System）、控制台（Console）、支持（Support），渲染/引擎 内核（Renderer/Engine Core）、游戏介质层（Game Interface）、以及工具/数据（Tools/Data）。</p>
<p><br>Tools/Data （工具/数据）</p>
<p>在开发过程中，你总是需要一些数据，但不幸的是这并不象写文本文件或是定义一个立方体那么简单。至少，你得需要3d模型编辑器，关卡编辑器，以及图形程序。你可以通过购买，也可以在网上找一些免费的程序满足你的开发要求。不幸的是你可能还需要一些更多的工具可你却根本无法获得（还不存在呢），这时你只得自己动手去写。最终你很可能要自行设计编写一个关卡编辑器，因为你更本不可能获得你所需。你可能也会编写一些代码来为大量的文件打个包，整天面对应付成百上千个文件倒是非常痛苦的。你还必须写一些转换器或是插件将3d模型编辑器的模型格式转换成你自己的格式。你也需要一些加工游戏数据的工具，譬如可见度估算或是光线贴图。</p>
<p>一个基本的准则是，你可能要为设计工具而置入比游戏本身等量甚至更多的代码。开始你总能找到现成的格式和工具，但是经过一段时间以后你就能认识到你需要你的引擎有很大的特性，然后你就会放弃以前的撰写方式。</p>
<p>也许目前非常流行利用的第3方工具辅助开发，所以你必须时刻注意你的设计。因为一旦当你将你的引擎发布为opensouce或是允许修改，那也许在某天中会有某些人来应用你的开发成果，他们将其扩展或者做某些修改。</p>
<p>或许你也应该花大量时间去设计美术，关卡，音效，音乐和实体模型，这就和你设计撰写游戏，工具以及引擎一样。</p>
<p><br>System （系统）</p>
<p>系统(system)是引擎与机器本身做通信交互的部件。一个优秀的引擎在待平台移植时，它的系统则是唯一需要做主要更改（扩加代码）的地方。我们把一个系统分为若干个子系统，其中包括：图形（Graphics）、输入（Input）、声音（Sound）、记时器（Timer）、配置（Configuration）。主系统负责初始化、更新、以及关闭所有的子系统。</p>
<p>图形子系统（Graphics Sub-System）在游戏里表现得非常直观，如果想在屏幕上画点什么的话，它（图形子系统）便干这事儿。大多数来讲，图形子系统都是利用OpenGL、Direct3D, Glide或是软件渲染（software rendering）实现。如果能更理想一些，你甚至可以把这些API都给支持了，然后抽象出一个&#8220;图形层&#8221;并将它置与实现API之上，这将给了客户开发人员或是玩家更多的选择，以获取最好的兼容性、最佳的表现效果。</p>
<p>输入子系统（Input Sub-System）需要把各种不同输入装置（键盘、鼠标、游戏板[Gamepad]，游戏手柄[Joystick]）的输入触发做统一的控制接收处理。（透明处理） 比方说，在游戏中，系统要检测玩家的位置是否在向前移动，与其直接地分别检测每一种输入装置，不如通过向输入子系统发送请求以获取输入信息，而输入子系统才在幕后真正地干活（分别检测每一种输入装置），这一切对于客户开发人员都是透明的。用户与玩家可以非常自由地切换输入装置，通过不同的输入装置来获取统一的行为将变的很容易。</p>
<p>声音子系统（sound system）负责载入、播放声音。该子系统功能非常简洁明了，但当前很多游戏都支持3D声音，实现起来会稍许复杂一些。</p>
<p>3D游戏引擎中很多出色的表现都是基于&#8220;时间系统&#8221;(time)的。因此你需要一段时间来为时间子系统（Timer sub-system）好好构思一番。即使它非常的简单，（游戏里）任何东西都是通过时间触发来做移动变化，但一份合理的设计将会让你避免为实现而一遍又一遍地撰写大量雷同的控制代码&#8230;&#8230;</p>
<p>配置系统（Configuration）位于所有子系统的顶端。它负责读取配置记录文件，命令行参数，或是实现修改设置(setup)。在系统初始化以及运行期间，所有子系统都将一直与它保持通讯。切换图象解析度（resolution），色深（color depth），定义按钮（key bindings），声音支持选项（sound support options），甚至包括载入游戏，该系统将这些实现显得格外的简单与方便。把你引擎设计得更为可设置化一些，这将为调试与测试带来更大的方便；玩家与用户也能很方便地选择他（她）们喜欢的运行方式。</p>
<p><br>Console （控制台）</p>
<p>哈！我知道所有人都乐意去更风做一个象Quake那样的控制台（console）系统。但这的确是一个非常好的想法。通过命令行变量与函数，你就能够在运行时改变你的游戏或是引擎的设置，而不需要重启。开发期间输出调试信息它将显得非常的有效。很多时间你都需要测试一系列变量的值，将这些值输出到控制台上要比运行一个debugger速度显然要快得多。你的引擎在运行期间，一旦发现了一个错误，你不必立即退出程序；通过控制台，你可以做些非常轻便的控制，并将这个错误信息打印出来。假如你不希望你的最终用户看见或是使用该控制台，你可以非常方便地将其disable，我想没人能看得见它。</p>
<p><br>Support （支持）</p>
<p>支持系统（Support）在你引擎中任何地方都将被使用到。该系统包含了你引擎中所有的数学成分（点，面，矩阵等），（内）存储管理器，文件载入器，数据容器（假如你不愿自己写，也可以使用STL）。该模块任务显得非常基础与底层，或许你会将它复用到更多别的相关项目中去。</p>
<p><br>Renderer/Engine Core （渲染/引擎 内核）</p>
<p>哈~是呀，所有的人都热爱3D图象渲染！因为这边有着非常多的不同种类的3D世界渲染方式，可要为各类拥有不同工作方式的3D图形管道做出一个概要描述也是几乎不可能的。</p>
<p>不管你的渲染器如何工作，最重要的是将你的渲染器组件制作得基化（based）与干净（clean）。<br>首先可以确定的是你将拥有不同的模块来完成不同的任务，我将渲染器拆分为以下几个部份：可见裁减（Visibility）、碰撞检测与反馈（Collision Detection and Response）、摄像器（Camera）、静态几何体（Static Geometry）、动态几何体（Dynamic Geometry）、粒子系统（Particle Systems）、布告板（Billboarding）、网格（Meshes）、天空体（Skybox）、光线（Lighting）、雾（Fogging）、节点阴影（Vertex Shading）和输出（Output）。</p>
<p>其中每一个部分都得需要一个接口来方便地实现改变设置（settings）、位置（position）、方向（orientation）、以及其他可能与系统相关的属性配置。</p>
<p>即将显露出来的一个主要缺陷便是&#8220;特性臃肿&#8221;，这将取决于设计期间你想实现什么样的特性。但如不把新特色置入引擎的话，你就会发觉一切都将变的很困难，解决问题的方式也显得特别逊色。</p>
<p>还有一件有意义的事便是让所有的三角形[triangles]（或是面[faces]）最终在渲染管道里经过同一点。（并非每次的每个三角形，这里讨论的是三角形列表[triangle lists]、扇形[fans]、带形[strips]、等） 多花一些工作让所有物体的格式都能经过相同的光线、雾、以及阴影代码，这样就能非常便利地仅通过切换材质与纹理id就使任何多边形具有不同的渲染效果。</p>
<p>这不会伤及到被大量被渲染绘出的点，但是一旦你不当心，它可能会导致大量的冗余代码。</p>
<p>你也许最终便能发现，实现所有这些你所需的极酷效果可能只占了所有的15%左右的代码量甚至更少。这是当然的，因为大多数游戏引擎并不只是图形表现。</p>
<p><br>Game Interface （游戏介质）</p>
<p>一个3D（游戏）引擎很重要的部分便是------它是一个游戏引擎。但这并不是一个游戏。一个真正的游戏所需的一些组件永远不要将它包含到游戏引擎里。引擎与游戏制作之间的控制介质能使代码设计变得更清晰，应用起来也会更舒服。这虽是一些额外的代码，但它能使游戏引擎具有非常好重用性，通过设计架够游戏逻辑（game logic）的脚本语言（scripting language）也能使开发变的更方便，也可以将游戏代码置入库中。如果你想在引擎本身中嵌入你的游戏逻辑系统设计的话，大量的问题与大量修改一定会让你打消复用这个引擎的念头。</p>
<p>因此，此时你很可能在思考这个问题：联系引擎与游戏的介质层到底提供了什么。答案就是控制（control）。几乎引擎的每一个部分都有动态的属性，而该引擎/游戏介质层（engine/game layer）提供了一个接口去修改这些动态属性。它们包括了摄像器（camera）、模型属性（model properties）、光线（lights）、粒子系统物理（particle system physics）、声效播放（playing sounds）、音乐播放（playing music）、输入操作（handling input）、切换等级（changing levels）、碰撞检测以及反馈（collision detection and response）、以及2D图形界面的顶端显示、标题画面等相关的东西。基本上来讲如果你想让你的游戏能优雅的实现这些元素，在引擎中置入这个介质层（interface）是必不可少的。</p>
<p><br>The Game （游戏）</p>
<p>在这里，我无法告诉你如何去写你的游戏。这该轮到你发挥啦。如果你已经为你那令人赞异的引擎设计出了一套出色的介质层的话，我想在设计撰写游戏过程中一定会轻松许多。</p>
<p>3D游戏引擎设计是一项巨大的软件工程。一个人独立完成设计并撰写也并非不可能，但这不只是熬一两个晚上便能搞定的，你很可能会出写出几兆的源代码量。如果你没有持久的信念与激情，你很可能无法完成它。</p>
<p>当然，别指望你的第一次尝试就能写出完整的引擎，挑一个比较小的项目所需的小规模引擎去实现。按你的方式去努力工作，你就能到达成功。</p>
<p>*******************************&nbsp;</p>
&nbsp;
<img src ="http://www.cppblog.com/xosen/aggbug/78922.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/xosen/" target="_blank">xosen</a> 2009-04-04 02:50 <a href="http://www.cppblog.com/xosen/articles/78922.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Shaderey――非真实渲染 </title><link>http://www.cppblog.com/xosen/articles/78921.html</link><dc:creator>xosen</dc:creator><author>xosen</author><pubDate>Fri, 03 Apr 2009 18:48:00 GMT</pubDate><guid>http://www.cppblog.com/xosen/articles/78921.html</guid><wfw:comment>http://www.cppblog.com/xosen/comments/78921.html</wfw:comment><comments>http://www.cppblog.com/xosen/articles/78921.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/xosen/comments/commentRss/78921.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/xosen/services/trackbacks/78921.html</trackback:ping><description><![CDATA[<p>本文版权归原作者所有，仅供个人学习使用，请勿转载，勿用于任何商业用途。<br>由于本人水平有限，难免出错，不清楚的地方请大家以原著为准。欢迎大家和我多多交流。<br>作者：Aras&nbsp;Pranckevicius<br>翻译：clayman</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;本文描述了以非真实渲染(none-photorealistic&nbsp;rendering)风格，对户外场景进行着色的技术。在2003年秋天的Beyond3D/ATI&nbsp;shader&nbsp;compititon中，Shaderey程序最先使用了这些技术来进行渲染。在Shaderey的户外场景中，包含了地形，云，树木，房屋，天空顶，以及湖水，如图所示：<br><img onmouseover="if(this.title) {this.style.cursor='hand';}" onclick="if(this.title) {window.open('http://farm1.static.flickr.com/131/354342324_fabfd6abce.jpg?v=0');}" alt="" src="http://farm1.static.flickr.com/131/354342324_fabfd6abce.jpg?v=0" onload="if(this.width>screen.width-333) {this.width=screen.width-333;this.title='open new window';}" border=0><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;确切的说，这里使用的NPR技术都是在图片空间（image&nbsp;space）进行的操作，它依赖于场景中两张重要的图片：一张包含了颜色信息，一张包含法线和深度信息。处理过程分为两部分：<br>渲染：&nbsp;把场景渲染到颜色和法线/深度目标中。<br>后期处理：&nbsp;在图片空间进行一系列过滤操作，获得最终的非真实效果。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;后期处理包括：HSV空间下的颜色扭曲，屏幕空间中简单&#8220;阴影线（hatching）&#8221;的渲染，以及在法线/深度不连续处的轮廓线绘制。我们将在后面详细讨论这些过滤操作。首先，先来看看Shaderey的场景渲染方式。</p>
<p>场景渲染</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;场景中所有的树木和房屋都经过了可视体裁剪（frustum－culled）。地形是一张512&nbsp;x&nbsp;512的高度图，但分为若干尺寸固定的（32&nbsp;x&nbsp;32）小块（chunk）。所有通过视见体裁剪的地形小块都没有进行任何形式的LOD。整个场景使用了一张1024&nbsp;x&nbsp;1024的阴影帖图。房屋和树木都将产生阴影，并且投影到地面上。场景中的树木和木屋投射阴影，而地形接收这些影子。我们使用pick-nearest采样器，对阴影贴图进行四次有偏移的采样，然后再shader中对这些值进行均值采样，以提高影子边界上的质量。阴影贴图并不需要覆盖整个地形的大小，在我们的实现中，它将随观察者的位置移动，以保证观察者前方总是有正确的阴影。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;为了模拟湖面的简单反射效果，可以把摄像机反转到水面之下，把场景渲染为一张较小的平面反射贴图。我们把这张阴影贴图投影到水面上，另外使用两张卷动的EMBM风格的凹凸贴图来模拟波纹。为了减少几何数据，渲染到反射贴图中的地形将使用较低的LOD层次。对所有物体来说，大气光照散射效果都是在顶点级别计算的。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;除了把颜色渲染到后备缓冲之外，还需要把场景中物体的法线和深度渲染到一张和屏幕大小相同的A8R8B8G8纹理中。世界坐标下的法线信息保存在RGB通道中，深度值的导数保存在alpha通道中。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;下面是在vertex&nbsp;shader中，使用HLSL正确计算法线和深度值倒数的代码：</p>
<p><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//output&nbsp;normal&nbsp;in&nbsp;RGB,&nbsp;sort-of-depth&nbsp;in&nbsp;A,&nbsp;p&nbsp;&#8211;&nbsp;final&nbsp;(&nbsp;clip&nbsp;space)&nbsp;position,&nbsp;&nbsp;n—world&nbsp;space&nbsp;normal</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;static&nbsp;inline&nbsp;float4&nbsp;gNormalZ(&nbsp;float4&nbsp;p,&nbsp;float3&nbsp;n)</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float4&nbsp;o;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;o.xyz&nbsp;=&nbsp;n&nbsp;*&nbsp;0.5&nbsp;+&nbsp;0.5;&nbsp;&nbsp;//&nbsp;in&nbsp;to&nbsp;0&#8230;.1&nbsp;range<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;o.w&nbsp;=&nbsp;100.0&nbsp;/&nbsp;(&nbsp;p.w&nbsp;+&nbsp;100&nbsp;);&nbsp;&nbsp;//&nbsp;kind-of-depth</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;如果支持DirectX&nbsp;9中的Multiple&nbsp;Render&nbsp;Target（MRT），可以在渲染场景颜色的同时，渲染法线和深度。如果不支持MRT，则需要分两次渲染（译注：从demo来看，使用MRT将会严重影响渲染质量，应该是由于MRT不支持多重采样造成的）。当把地形渲染到法线/深度纹理中时，需要使用&lt;&lt;&nbsp;Non-Photorealistic&nbsp;Rendering&nbsp;with&nbsp;Pixel&nbsp;and&nbsp;Vertex&nbsp;Shader&gt;&gt;中所描述的方法，在pixel&nbsp;shader中对阴影贴图进行采样，对阴影中的像素来说，需要对插值之后深度值取反（译注:在Non-Photo原文中是对法线值取反）。这样做的原因在后面描述后期处理的部分会讲解。</p>
<p>图片后期处理</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;目前已经把场景渲染为颜色和法线/深度图片了，接下来就可以对这些图片进行一系列处理了，包括把颜色转到HSV颜色空间下进行风格化处理，绘制边缘轮廓线，实现阴影线。</p>
<p>颜色失真</p>
<p>图片处理的第一步是进行颜色失真，获得风格化的样式。<br>1．降低采样率，把图片缩为一张521x512的纹理。<br>2．把颜色从RGB空间转换到HSV空间，并且量化（quantize）颜色值。颜色空间的转换将通过对一张体积材质的查找来实现。把原像素的RGB值作为立方纹理坐标。立方纹理中的像素为HSV颜色空间。这里我们将使用一张32x32x32的纹理，并且不进行任何过滤，所以颜色转换的同时将会量化颜色值。<br>3．使用2D偏移纹理，对同一纹理中当前像素的两个偏移位置进行采样。用来访问偏移纹理的纹理坐标由程序控制，它们将和观察者的位置有关（观察点的yaw值将在水平方向影响偏移，pitch值在垂直方向影响）。这些额外的采样颜色也必须转换到HSV空间。<br>4．替换图片中的颜色。目前我们有2个额外的偏移采样。首先，我们检察两个偏移值之间差分的差值，如果小于某个限制，就什么也不做。如果它们之间的差别足够大，则输出S和V通道的均值，保留中心原像素的H值。这个方法能高效的在颜色区域边缘替换原像素的饱和度。<br>5．再使用一张立方纹理把颜色转换回HSV空间。<br>&nbsp;&nbsp;&nbsp;&nbsp;第2~5步的pixel&nbsp;shader代码如下，需要pixel&nbsp;shader&nbsp;2.0的支持。</p>
<p>struct&nbsp;PS_INPUT<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float2&nbsp;uv[2]&nbsp;:&nbsp;TEXCOORD0;&nbsp;//base&nbsp;uv,displace&nbsp;uv<br>};</p>
<p>&nbsp;</p>
<p>float4&nbsp;psMain(&nbsp;PS_INTPUT&nbsp;i)&nbsp;:&nbsp;COLOR<br>{</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//sample&nbsp;rgb,convert&nbsp;into&nbsp;hsv<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;half&nbsp;base&nbsp;=&nbsp;tex2D(&nbsp;smpBase,&nbsp;i.uv[0]&nbsp;).rgb;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;base&nbsp;=&nbsp;tex3D(&nbsp;smpRGB2HSV,&nbsp;base&nbsp;).rgb;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//get&nbsp;2&nbsp;displaced&nbsp;sample&nbsp;locations<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;half2&nbsp;bleedB&nbsp;=&nbsp;tex2D&nbsp;(&nbsp;smpBleedB,&nbsp;i.uv[1]&nbsp;).rg&nbsp;*&nbsp;2&nbsp;-1;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;half2&nbsp;bleedC&nbsp;=&nbsp;tex2D&nbsp;(&nbsp;smpBleedC,&nbsp;i.uv[1]&nbsp;).rg&nbsp;*&nbsp;2&nbsp;-1;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float2&nbsp;uvB&nbsp;=&nbsp;i.uv[0]&nbsp;+&nbsp;bleedB&nbsp;*&nbsp;(8.0/512);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float2&nbsp;uvC&nbsp;=&nbsp;i.uv[0]&nbsp;+&nbsp;bleedC&nbsp;*&nbsp;(-7.0/512);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//sample&nbsp;base&nbsp;at&nbsp;displaced&nbsp;locations&nbsp;,convert&nbsp;to&nbsp;hsv<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;half3&nbsp;baseB&nbsp;=&nbsp;tex2D(&nbsp;smpBase,uvB).rgb;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;baseB&nbsp;=&nbsp;tex3D(&nbsp;smpRGB2HSV,baseB);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;half3&nbsp;baseC&nbsp;=&nbsp;tex2D(&nbsp;smpBase,&nbsp;uvC).rgb;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;baseC&nbsp;=&nbsp;tex3D(&nbsp;smpRGB2HSV,baseC);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;half3&nbsp;bleed&nbsp;=&nbsp;baseB&nbsp;*&nbsp;0.5&nbsp;+&nbsp;baseC&nbsp;*&nbsp;0.5;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//final&nbsp;color&nbsp;is&nbsp;base&nbsp;if&nbsp;differences&nbsp;in&nbsp;hsv&nbsp;values&nbsp;are&nbsp;smller&nbsp;than&nbsp;tresholds<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//else&nbsp;average&nbsp;of&nbsp;displace&nbsp;values<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;half3&nbsp;diff&nbsp;=&nbsp;abs(base&nbsp;-&nbsp;baseC)&nbsp;-&nbsp;half(&nbsp;1/8.0,1/3.0,1/3.0)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;half3&nbsp;final&nbsp;=&nbsp;all(&nbsp;diff&nbsp;&lt;&nbsp;float3&nbsp;(&nbsp;0,0,0)&nbsp;?&nbsp;base&nbsp;:&nbsp;bleed;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//leave&nbsp;original&nbsp;hue&nbsp;channel<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;final.r&nbsp;=&nbsp;base.r;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//convert&nbsp;back&nbsp;to&nbsp;rgb<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;tex3D&nbsp;(&nbsp;smpHSV2RGB),final);</p>
<p>}</p>
<p>边缘检测和轮廓线<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;为了获得NPR风格的样式，必须在图片上渲染出深色的轮廓线和阴影线，表现出场景的着色效果。在Shaderey中，我们将同时绘制边缘轮廓线和阴影线。这里需要使用之前计算的法线/深度图来计算边缘，用光线和法线的点积来计算那些区域需要绘制阴影线。阴影线是一张简单的纹理。在这一步处理中，边缘和轮廓线都是白色。最终合成时，进行反色处理，轮廓线变为纯黑色，轮廓线颜色根据场景的着色进行衰减。<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;以下是绘制轮廓线和阴影线的pixel&nbsp;shader代码：</p>
<p>half4&nbsp;psMain&nbsp;(&nbsp;float2&nbsp;uv[3]:TEXCOORD):&nbsp;COLOR<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//sample&nbsp;center&nbsp;and&nbsp;2&nbsp;neightbours<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;half4&nbsp;cbase&nbsp;=&nbsp;tex2D(&nbsp;smpBase,&nbsp;i.uv[0]);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;half4&nbsp;cb1&nbsp;=&nbsp;tex2D(smpBase,&nbsp;i.uv[1]);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;half4&nbsp;cb3&nbsp;=&nbsp;tex2D(smpBase,&nbsp;i.uv[2]);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//normal&nbsp;into&nbsp;-1..1&nbsp;range<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;half3&nbsp;nbase&nbsp;=&nbsp;cbase.xyz&nbsp;*&nbsp;2&nbsp;-1;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;half3&nbsp;nb1&nbsp;=&nbsp;cb1.xyz&nbsp;*&nbsp;2&nbsp;-&nbsp;1;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;half3&nbsp;nb3&nbsp;=&nbsp;cb3.xyz&nbsp;*&nbsp;2&nbsp;-&nbsp;1;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//edges&nbsp;from&nbsp;normals<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;half2&nbsp;ndiff;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ndiff.x&nbsp;=&nbsp;dot(&nbsp;nbase,nb1);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ndiff.y&nbsp;=&nbsp;dot(&nbsp;nbase,nb3);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ndiff&nbsp;-=&nbsp;0.6;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;ndiff&nbsp;=&nbsp;ndiff&nbsp;&gt;&nbsp;half2(0,0)&nbsp;?&nbsp;half2(0,0):half2(1,1);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;half&nbsp;ndiff1&nbsp;=&nbsp;ndiff.x&nbsp;+&nbsp;ndiff.y;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//edges&nbsp;from&nbsp;z<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;float2&nbsp;zdiff;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;zdiff.x&nbsp;=&nbsp;cbase.a&nbsp;-&nbsp;cd1.a;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;zdiff.y&nbsp;=&nbsp;cbase.a&nbsp;-&nbsp;cb3.a;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;adiff&nbsp;=&nbsp;abs(zdiff)&nbsp;-&nbsp;0.02;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;zdiff&nbsp;=&nbsp;zdiff&nbsp;&gt;&nbsp;half2(0,0)&nbsp;?&nbsp;half2(1,1)&nbsp;:&nbsp;half2(0,0);</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//sampler&nbsp;hatch<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;half4&nbsp;chatch&nbsp;=&nbsp;tex2D(&nbsp;smpHatch,&nbsp;i.uv[0]);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//dot&nbsp;normal&nbsp;with&nbsp;light<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;half&nbsp;dotNL&nbsp;=&nbsp;dot(&nbsp;nbase,&nbsp;vLightDir);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//hatch&nbsp;blend&nbsp;factor<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;half&nbsp;factor&nbsp;=&nbsp;saturate(&nbsp;(1.0&nbsp;-&nbsp;0.9&nbsp;-&nbsp;dotNL)&nbsp;*&nbsp;2);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;chatch&nbsp;*=&nbsp;factor;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;return&nbsp;chatch&nbsp;+&nbsp;ndiff1&nbsp;+&nbsp;dot(zdiff,half2(1,1));<br>}</p>
<p>最终合成<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;在处理完了两张图片之后，把失真之后的颜色与反转之后的边缘/轮廓线进行调制，合成出最终图像。</p>
<p>&nbsp;点击<a href="http://www.nesnausk.org/nearaz/files/shaderey.zip" target=_blank><font color=#336699>这里</font></a>下载完成程序和代码。<br><img onmouseover="if(this.title) {this.style.cursor='hand';}" onclick="if(this.title) {window.open('http://farm1.static.flickr.com/63/354342325_bd69628c3d.jpg?v=0');}" alt="" src="http://farm1.static.flickr.com/63/354342325_bd69628c3d.jpg?v=0" onload="if(this.width>screen.width-333) {this.width=screen.width-333;this.title='open new window';}" border=0><br><img onmouseover="if(this.title) {this.style.cursor='hand';}" onclick="if(this.title) {window.open('http://farm1.static.flickr.com/126/354342326_063a0fa88e.jpg?v=0');}" alt="" src="http://farm1.static.flickr.com/126/354342326_063a0fa88e.jpg?v=0" onload="if(this.width>screen.width-333) {this.width=screen.width-333;this.title='open new window';}" border=0>&nbsp;<br><br></p>
&nbsp;
<img src ="http://www.cppblog.com/xosen/aggbug/78921.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/xosen/" target="_blank">xosen</a> 2009-04-04 02:48 <a href="http://www.cppblog.com/xosen/articles/78921.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>四叉树和八叉树的剔出选择</title><link>http://www.cppblog.com/xosen/articles/78920.html</link><dc:creator>xosen</dc:creator><author>xosen</author><pubDate>Fri, 03 Apr 2009 18:46:00 GMT</pubDate><guid>http://www.cppblog.com/xosen/articles/78920.html</guid><wfw:comment>http://www.cppblog.com/xosen/comments/78920.html</wfw:comment><comments>http://www.cppblog.com/xosen/articles/78920.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/xosen/comments/commentRss/78920.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/xosen/services/trackbacks/78920.html</trackback:ping><description><![CDATA[[翻译]四叉树和八叉树的剔出选择 <br>&nbsp;
<p><br>翻译：宋晓宇&nbsp;&nbsp;&nbsp; write by Henri Haki</p>
<p>介绍：<br>　　传统计算机图形应用--特别是的应用的需要一个实时，交互的方法来现实--通过处理一个发送到显卡的数据的最有效的图形数据子集的方法来决定图形数据的显示，而不是传送全部的数据，四叉树，八叉树，Bsp树，背面剔出，pvs集合很多其他方法都是针对这个目的而提出的。</p>
<p>　　流行的计算机图形卡近些年在处理能力和处理方法上程指数增长，当前的状态揭示出很多时候应该更好的和快速的找到一个好的数据集把它们送到显卡里，而不是把精力放在努力的找到一个最好的数据集。这样的数据集是一个近似的最好的数据集并且能经常发现它都有十分有效的算法，因此手头上的任务因此就变成了回顾已经存在的技术和算法并且尝试找到最快的选择，这对于找到最好的解决问题的方法并不是什么负担。</p>
<p>一、四叉树：<br>　　四叉树作为数据处理表达技术的一个好方法已经有很多年了，特别是地形渲染引擎都能利用他很有效的作为剔出机制，剩余的这个章节将会给出一个小数量的说明四叉树并且传统的，高层的方法使用四叉树，在描述一个快速的方法之前。</p>
<p>四叉树数据结构：</p>
<p>　　四叉树被描述通过对应每个父节点传递四个子节点，在一个地形渲染上下文里，根节点将会表达为这个围绕地形的正方形区域集，自节点表示为&#8220;左上&#8221;，&#8220;右上&#8221;，&#8220;左下&#8221;和&#8220;右下&#8221;象限，这些象限由根节点组成并且每一个都是由四个字节点递归的定义下面的描述将会说明这些概念一个四元数的数据结构并不负责,下面的伪码演示了这个四叉树的节点：</p>
<p>TPosition = record<br>&nbsp; x,y : float;<br>end;</p>
<p>PQuad = pointer to TQuadNode;<br>TQuad = record<br>&nbsp; p1,p2,p3,p4 : TPosition;&nbsp;&nbsp;&nbsp; // corners<br>&nbsp; c1,c2,c3,c4 : PQuadNode;&nbsp;&nbsp;&nbsp; // children<br>end;</p>
<p>一个简单的递归初始化一个深度为max_depth的四叉树如下：</p>
<p>function InitQuad(x,y,w : float; lev : int) : PQuad;<br>var<br>&nbsp; tmp : PQuad;<br>begin<br>&nbsp; inc(lev);<br>&nbsp; if lev &gt; max_depth then return(nil);<br>&nbsp; new(tmp);<br>&nbsp; ...initialize tmp node with corner data<br>&nbsp; w := w / 2;<br>&nbsp; tmp^.c1 := InitQuad(x - w, y - w, w, lev);<br>&nbsp; tmp^.c2 := InitQuad(x - w, y + w, w, lev);<br>&nbsp; tmp^.c3 := InitQuad(x + w, y + w, w, lev);<br>&nbsp; tmp^.c4 := InitQuad(x + w, y - w, w, lev);</p>
<p>&nbsp; return(tmp);<br>end;</p>
<p>基于四叉树的背面剔除</p>
<p>　　在很多应用里主要的四叉树函数是提供一个有效的的视截体数据剔除,下面的基于视点的高层视截体剔除已经够了：</p>
<p>procedure CullQuad(q : PQuad);<br>begin<br>&nbsp; case Clip_Quad_against_View(q) of<br>&nbsp;&nbsp;&nbsp; inside&nbsp; : DrawQuad(q);<br>&nbsp;&nbsp;&nbsp; outside : ; // ignore quad<br>&nbsp;&nbsp;&nbsp; else begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ...CullQuad children of q<br>&nbsp;&nbsp;&nbsp; end;<br>&nbsp; end;<br>end;</p>
<p>Clip_Quad_against_View 是cullquad的关键函数，并且当然运行的函数和方法来解决这个问题是否是一个四元（或者多边形）交叉于这个视见体，通过交叉视见体金字塔作为平面集合检测平面作为一套光线，然后连续测试几何体的光线怎么和可视体平面相交的，光线相交测试的方程在很多３ｄ几何的书上都可以找到，这里不做重复。</p>
<p>二、选择基于四叉树的视点剔除：</p>
<p>　　上面描述的方法一般情况下会得出正确的结果，但是他没有给这个简单的静态的四叉树提供任何东西，好多的优化可以应用到四叉树的剔除过程，下面的两个阶段产生了一个很快的和很有效的基于四元数的剔除：</p>
<p>　　*基于点的剔除：一个完全的剔除计算，他通过记录相关的四个角的可视点。</p>
<p>　　很多这样的情况可以被考虑，例如：如果一个单独的点在视见体内发现，那么这个块就在视见体内，如果所有的点都在视见体的一面，那么这个块就不在视见体内．一个数量的可能性就存在，并且需要通过一个完全的计算，但是上面给出的两种情况得出了一个充足的启发性来接受，丢弃或者将来重新定义一个潜在的块。</p>
<p>　　*Ｍｏｍｏｉｚａｔｉｏｎ：是一种储存计算结果的技术并且还需要相同的计算时查找储存的结果。</p>
<p>　　四元数作为一个静态结构，这种特殊角的点经常都是相同的，另外，很多块的角是四个块共享的，并且在循环传递四元数的角一遍又一遍的，通过决定一个角的相关位置关联这个可视体和方便的储存这个四元数的的计算结果。</p>
<p>　　下面的伪代码声明了这个算法，对于一个四元数横跨的区域是(0,0)到(256,256);</p>
<p>(globals:) <br>Memoized : array[0..256,0..256] of byte;<br>posx,posy : float;&nbsp; // origin of FOW<br>Lx,Ly,Lz : float;&nbsp;&nbsp; // normal of left FOV plane<br>Rx,Ry,Rz : float;&nbsp;&nbsp; // normal of right FOV plane</p>
<p>function CheckPos(x, y : int) : int;<br>// checks position x,y against FOV planes, memoize<br>// Results: bit 1 (inside), bit 2 (left), bit 3 (right)<br>var<br>&nbsp; res : int;<br>begin<br>&nbsp; res := 0;<br>&nbsp; if (x-posx)*Lx + (y-posy)*Ly &gt; 0 then res := 2;<br>&nbsp; if (x-posx)*Rx + (y-posy)*Ry &gt; 0 then res := res or 4;<br>&nbsp; if res = 0 then res := 1;<br>&nbsp; Memoized[x,y] := res;<br>&nbsp; return res;<br>end;</p>
<p>function TestQuad(x, y, w : int) : int;<br>// quad-midpoint: (x,y)<br>// quad-width: 2w<br>// test quad against FOV<br>// Results: 0 (out), 1 (partially in), 2 (completely in), -1 (unknown)<br>var<br>&nbsp; m1,m2,m3,m4 : int;<br>&nbsp; tmp : int;<br>begin<br>&nbsp; m1 := Memoized[x - w, y + w];<br>&nbsp; if m1 = 0 then CheckPos(x - w, y + w);<br>&nbsp; m2 := Memoized[x + w, y + w];<br>&nbsp; if m2 = 0 then CheckPos(x + w, y + w);<br>&nbsp; m3 := Memoized[x + w, y - w];<br>&nbsp; if m3 = 0 then CheckPos(x + w, y - w);<br>&nbsp; m4 := Memoized[x - w, y - w];<br>&nbsp; if m4 = 0 then CheckPos(x - w, y - w);</p>
<p>&nbsp; tmp := m1 and m2 and m3 and m4;<br>&nbsp; if tmp = 1 then<br>&nbsp;&nbsp;&nbsp; return 2;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // quad completely inside FOV</p>
<p>&nbsp; if m1 or m2 or m3 or m4 = 1 then<br>&nbsp;&nbsp;&nbsp; return 1;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // quad partially inside FOV</p>
<p>&nbsp; if tmp =&gt; 0 then <br>&nbsp;&nbsp;&nbsp; return 0;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // quad completely outside FOV</p>
<p>&nbsp; return -1;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // else quad state undetermined<br>end;</p>
<p>上面的函数应该被清除并且及早的需要整数的的四元块程序</p>
<p>procedure CullQuadtree;<br>begin<br>&nbsp; Clear_Memoized_Array_to_Zero;<br>&nbsp; CullQuad(Quadtree_Root);<br>end;</p>
<p>procedure CullQuad(q : PQuad);<br>begin<br>&nbsp; case Test(quadmidpoints and half-width) of<br>&nbsp;&nbsp;&nbsp; 2 : ...handle complete quad<br>&nbsp;&nbsp;&nbsp; 1 : ...handle partial quad<br>&nbsp;&nbsp;&nbsp; 0 : ;&nbsp; // ignore quad<br>&nbsp;&nbsp;&nbsp; else begin<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ...CullQuad children of q<br>&nbsp;&nbsp;&nbsp; end;<br>&nbsp; end;<br>end;</p>
<p>三、另外需要考虑的：</p>
<p>很多在代码里和一般的算法里都需要被考虑的是：</p>
<p>　　*四叉树算法的版本里表现出的只剔除左/右，不是近裁剪面，不是远裁减面或者上下都没有考虑，另外只有平面视角。因此这个算法只覆盖了四叉树的的高度剔除和视见面沿着这个四叉树的面。</p>
<p>　　我们扩展这些代码沿着3d的四叉树，增加如：应用四叉树的移出算法-任何可视的位置和方向将会正确的进行的东西。</p>
<p>　　*另外很多附加的点，视见体需要考虑执行的比如：如果两个点在视见体的前面并且对这FOV的一个面，这个块部分在这个视见体里，对于很多的算法，这样的关卡满足这个结果。</p>
<p>　　*主要关心这个算法的需要是在记忆需求里，尽管一般的沉浸记忆对于每个可能的块某些点需要一个附加的字节。因此如果正方形的区域有n个间隔，每个面都都需要n个字节来储存，通过典型的只有一个碎片的内存被一个给定的遍历访问，这一部分就被访问了很多次。</p>
<p>　　这篇文章总结了算法的表达，我发现这是一个有用积极的算法，如果你查询了相关的算法并且有感触可以写信给我咨询。</p>
<p>英文原文出处：<a href="http://www.gamedev.net/reference/articles/article1485.asp"><font color=#336699>http://www.gamedev.net/reference/articles/article1485.asp</font></a></p>
<p>　　第一次翻译文章贴出，错误很多，尚未修改，希望大家谅解和支持。原来一直喜欢看别人翻译后的文章，今天早上实在找不到汉语的来看，索性翻译一篇好了，心想也报了12月份的英语6级考试从没复习过，就当仔细复习下英语好了。也改一改看英文文档不仔细的毛病。谁知道遇到了太多的困能。唉！看来以后要体谅一下那些翻译专业英文的人了。而且本来想要发表到技术文档区的，可实在怕丢人就算了，发这里大家先给意见再说了。谢谢各位了。&nbsp;</p>
<img src ="http://www.cppblog.com/xosen/aggbug/78920.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/xosen/" target="_blank">xosen</a> 2009-04-04 02:46 <a href="http://www.cppblog.com/xosen/articles/78920.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>空间直线段和三角形相交算法</title><link>http://www.cppblog.com/xosen/articles/78918.html</link><dc:creator>xosen</dc:creator><author>xosen</author><pubDate>Fri, 03 Apr 2009 18:43:00 GMT</pubDate><guid>http://www.cppblog.com/xosen/articles/78918.html</guid><wfw:comment>http://www.cppblog.com/xosen/comments/78918.html</wfw:comment><comments>http://www.cppblog.com/xosen/articles/78918.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/xosen/comments/commentRss/78918.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/xosen/services/trackbacks/78918.html</trackback:ping><description><![CDATA[三维空间当中，直线和三角形的相交算法是计算机三维图形学当中，碰撞检测和选择操作的最基本的算法<br>DirectX SDK当中PICK例子，提供了原始代码，对于这段代码有不同的理解<br>这里是用仿射坐标系分解的方式解释它<br><br>基本知识<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 空间平面方程， N*P＋D＝0； 或者N*P=D， <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 这里*是向量的点乘，N是平面的法向量，P是平面上的任意一点<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 而D表示的是原点到平面的距离，或者平面到原点的距离，注意这个距离是矢量<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;点到空间平面的距离<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 对于N*P=D的情形，D就是从原点到平面的距离<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;N*P0－D就是点到平面的距离了<br>&nbsp;&nbsp;&nbsp; <br>要解决线段和空间三角形相交的问题，分两步解决<br>第一步<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; 计算线段和空间三角形所在的平面的交点<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 计算线段的两个端点到平面的距离，如果符号相同，表示位于同测，必然不相交<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 根据距离可以差值得到0距离时候的点，这个点就是交点了<br>第二步，交点是否在空间三角形的内部<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 这个时候，可以用仿射坐标系分解的方法来计算<br>首先介绍什么是仿射坐标系<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 我们比较熟悉的是笛卡儿的正交坐标系，两个坐标轴是相互垂直的<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 而仿射坐标系当中，两个坐标轴是不垂直的，只要不是平行的就可以组成仿射坐标系<br>&nbsp;&nbsp;&nbsp;&nbsp; 显然，笛卡儿的正交坐标系是仿射坐标系的一个特例了<br>&nbsp;&nbsp;&nbsp;&nbsp; 无论是正交还是仿射坐标系，都有下列事实存在：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 假定两个坐标轴的向量是X和Y<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 空间任何的一个点P都可以表示成为 P ＝ O ＋ x*X + y*Y<br>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; O是原点，x和y就是在这个坐标系下的坐标了<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;空间任何一个点，按照这个坐标系来说，x和y的数值都是唯一的<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 对于空间上的一个点，过它做平行坐标轴的平行线，和坐标轴相交，这个交点到原点的距离就是系数<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 对于正交坐标系来说，平行线和坐标轴围成的是一个矩形<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 对于仿射坐标系来手，平行线和坐标轴未成的是一个平行四边形<br>&nbsp;如果我们把三角形的一个顶点看作是仿射坐标系的原点，而从这个顶点发出去的两个边作为仿射坐标系的两个坐标轴的单位向量，那么平面内的任何一个点在这个仿射坐标系下的坐标数值就可以区分出来点和这个三角形之间的关系了。假定坐标是u和v，那么&nbsp;u&gt;0，v&gt;0表示，点在第一象限<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; u+v&lt;=1&nbsp;表示点在三角形的内部<br>&nbsp;&nbsp;如果&nbsp;&nbsp;0&lt;= u &lt;= 1 并且&nbsp;&nbsp; 0&lt;=v &lt;=1则表示点在有两条边组成的平行四边形的内部<br>根据找个原理，我们就可以判定，点是否在三角形或者平行四边形的内部了<br><br>具体计算方式<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 三角形的三个顶点分别是P1,P2,P3, 同平面的点Po<br>&nbsp;&nbsp;&nbsp;&nbsp; 两个边向量就是 U = P2-P1&nbsp; V=P3-P1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp; 此坐标系内的点Po' = Po - P1<br>解这个方程就可以获得到仿射坐标系下的坐标了：<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Po' = u*U + v*V<br>因为点是三维的，只要u和v两个位置数， 所以取出三维坐标当中的两个坐标分量计算就可以了。<br>其实我们只要找平面法向量三个坐标分量当中，绝对值小的那两个坐标分量来计算就可以了<br>实际上这就是解一个二元一次的方程组而已<br>具体的算法无需给出<br>&nbsp;
<img src ="http://www.cppblog.com/xosen/aggbug/78918.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/xosen/" target="_blank">xosen</a> 2009-04-04 02:43 <a href="http://www.cppblog.com/xosen/articles/78918.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>建立自己的3D静态模型文件</title><link>http://www.cppblog.com/xosen/articles/78917.html</link><dc:creator>xosen</dc:creator><author>xosen</author><pubDate>Fri, 03 Apr 2009 18:41:00 GMT</pubDate><guid>http://www.cppblog.com/xosen/articles/78917.html</guid><wfw:comment>http://www.cppblog.com/xosen/comments/78917.html</wfw:comment><comments>http://www.cppblog.com/xosen/articles/78917.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/xosen/comments/commentRss/78917.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/xosen/services/trackbacks/78917.html</trackback:ping><description><![CDATA[跟着我一步一步来吧（我的思路和过程）。<br>我有一个能画出微软例子中tiger.x的工程，其中创建Mesh的代码片断如下（代码中有一些变量在.h文档中定义的）：<br>&nbsp; HRESULT CMyMesh::Create( LPDIRECT3DDEVICE9 pDevice, string MeshFile )<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp; if( pDevice == NULL ) <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return E_FAIL;
<p>&nbsp;&nbsp;&nbsp; LPD3DXBUFFER pD3DXMtrlBuffer;<br>&nbsp;&nbsp;&nbsp; if( FAILED( D3DXLoadMeshFromX( MeshFile.c_str(), D3DXMESH_SYSTEMMEM, pDevice, NULL, &amp;pD3DXMtrlBuffer, NULL, &amp;NumOfMaterials, &amp;Mesh ) ) )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return E_FAIL;<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; D3DXMATERIAL* d3dxMaterials = (D3DXMATERIAL*)pD3DXMtrlBuffer-&gt;GetBufferPointer();<br>&nbsp;&nbsp;&nbsp; MeshMaterials = new D3DMATERIAL9[NumOfMaterials];<br>&nbsp;&nbsp;&nbsp; MeshTextures&nbsp; = new LPDIRECT3DTEXTURE9[NumOfMaterials];</p>
<p>&nbsp;&nbsp;&nbsp; for( DWORD i = 0; i &lt; NumOfMaterials_; i ++ )<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MeshMaterials[i] = d3dxMaterials[i].MatD3D;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MeshMaterials[i].Ambient = MeshMaterials[i].Diffuse;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MeshTextures[i] = NULL;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if( d3dxMaterials[i].pTextureFilename != NULL &amp;&amp; lstrlen(d3dxMaterials[i].pTextureFilename) &gt; 0 )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if( FAILED( D3DXCreateTextureFromFile( pDevice, d3dxMaterials[i].pTextureFilename, &amp;MeshTextures[i] ) ) )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return E_FAIL;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; pD3DXMtrlBuffer-&gt;Release();<br>&nbsp;&nbsp;&nbsp; return S_OK;<br>&nbsp; }</p>
<p>画这个Mesh的代码如下：<br>&nbsp; void CMyMesh::Render( LPDIRECT3DDEVICE9 pDevice )<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp; for( DWORD i = 0; i &lt; NumOfMaterials; i ++ )<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pDevice-&gt;SetMaterial( &amp;MeshMaterials[i] );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pDevice-&gt;SetTexture( 0, MeshTextures[i] );</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Mesh-&gt;DrawSubset( i );<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp; }<br>&nbsp; <br>我们先想办法替换DrawSubset函数，代码如下：<br>&nbsp; void CMyMesh::Render( LPDIRECT3DDEVICE9 pDevice )<br>&nbsp; {<br>&nbsp;&nbsp;LPDIRECT3DVERTEXBUFFER9 pVertexBuffer;<br>&nbsp;&nbsp;LPDIRECT3DINDEXBUFFER9 pIndexBuffer;<br>&nbsp;&nbsp;DWORD dwNumBytesPerVertex;<br>&nbsp;&nbsp;DWORD dwFVF;<br>&nbsp;&nbsp;DWORD dwNumVertex;<br>&nbsp;&nbsp;DWORD dwFaces;<br>&nbsp;&nbsp;Mesh-&gt;GetVertexBuffer(&amp;pVertexBuffer);<br>&nbsp;&nbsp;Mesh-&gt;GetIndexBuffer(&amp;pIndexBuffer);<br>&nbsp;&nbsp;dwNumBytesPerVertex = Mesh-&gt;GetNumBytesPerVertex();<br>&nbsp;&nbsp;dwFVF = Mesh-&gt;GetFVF();<br>&nbsp;&nbsp;dwNumVertex = Mesh-&gt;GetNumVertices();<br>&nbsp;&nbsp;dwFaces = Mesh-&gt;GetNumFaces();</p>
<p>&nbsp;&nbsp;pDevice-&gt;SetStreamSource(0, pVertexBuffer, 0, dwNumBytesPerVertex);<br>&nbsp;&nbsp;pDevice-&gt;SetFVF(dwFVF);<br>&nbsp;&nbsp;pDevice-&gt;SetTexture(0, MeshTextures[0]);<br>&nbsp;&nbsp;pDevice-&gt;SetMaterial(&amp;MeshMaterials[0]);<br>&nbsp;&nbsp;pDevice-&gt;SetIndices(pIndexBuffer);<br>&nbsp;&nbsp;pDevice-&gt;DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, dwNumVertex, 0, dwFaces);<br>&nbsp;}<br>&nbsp;<br>虽然MeshTextures和MeshMaterials处有点问题，但是我们知道了画tiger.x的时候都需要什么东西：<br>VB,IB,NumBytesPerVertex,FVF,NumVertex,Faces,Texture,Material<br>那么我们将这些东西保存到文件中，不就和.x文件目的相同了么， 并且可以绕过D3DXMesh了。那么这些东西怎么得到呢：我修改了Create部分的代码：</p>
<p>&nbsp; HRESULT CMyMesh::Create( LPDIRECT3DDEVICE9 pDevice, string MeshFile )<br>&nbsp; {<br>&nbsp;&nbsp;&nbsp; if( pDevice == NULL ) <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return E_FAIL;</p>
<p>&nbsp;&nbsp;&nbsp; LPD3DXBUFFER pD3DXMtrlBuffer;<br>&nbsp;&nbsp;&nbsp; if( FAILED( D3DXLoadMeshFromX( MeshFile.c_str(), D3DXMESH_SYSTEMMEM, pDevice, NULL, &amp;pD3DXMtrlBuffer, NULL, &amp;NumOfMaterials, &amp;Mesh ) ) )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return E_FAIL;<br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; D3DXMATERIAL* d3dxMaterials = (D3DXMATERIAL*)pD3DXMtrlBuffer-&gt;GetBufferPointer();<br>&nbsp;&nbsp;&nbsp; MeshMaterials = new D3DMATERIAL9[NumOfMaterials];<br>&nbsp;&nbsp;&nbsp; MeshTextures&nbsp; = new LPDIRECT3DTEXTURE9[NumOfMaterials];</p>
<p>&nbsp;&nbsp;&nbsp; for( DWORD i = 0; i &lt; NumOfMaterials_; i ++ )<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MeshMaterials_[i] = d3dxMaterials[i].MatD3D;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MeshMaterials_[i].Ambient = MeshMaterials_[i].Diffuse;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MeshTextures_[i] = NULL;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if( d3dxMaterials[i].pTextureFilename != NULL &amp;&amp; lstrlen(d3dxMaterials[i].pTextureFilename) &gt; 0 )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp; FILE* fp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; int nLen = 0;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; char* pBuf;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if(fp=fopen( d3dxMaterials[i].pTextureFilename, "rb" )) {<br>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; fseek(fp,0,SEEK_END);<br>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; nLen = ftell(fp);<br>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; fseek(fp,0,SEEK_SET);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pBuf = new char[nLen];<br>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; fread(pBuf,1,nLen,fp);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fclose(fp);<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; HRESULT hr = D3DXCreateTextureFromFileInMemory(D3DDevice,pBuf,nLen,&amp;MeshTextures[i]);<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; delete [] pBuf;</p>
<p>&nbsp;&nbsp;&nbsp; //HRESULT hr = D3DXCreateTextureFromFile( D3DDevice_, d3dxMaterials[i].pTextureFilename, &amp;MeshTextures_[i] );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if( FAILED( hr ) )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return E_FAIL;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; }<br></p>
<p>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; //下面是增加的内容<br>&nbsp;&nbsp;&nbsp; FILE* fp;<br>&nbsp;&nbsp;&nbsp; if( ( fp = fopen("tiger.o","w") ) != NULL ) {<br>&nbsp;&nbsp;&nbsp;LPDIRECT3DVERTEXBUFFER9 pVertexBuffer;<br>&nbsp;&nbsp;&nbsp;LPDIRECT3DINDEXBUFFER9 pIndexBuffer;<br>&nbsp;&nbsp;&nbsp;D3DVERTEXBUFFER_DESC desc;<br>&nbsp;&nbsp;&nbsp;D3DINDEXBUFFER_DESC idesc;<br>&nbsp;<br>&nbsp;&nbsp;&nbsp;Mesh-&gt;GetVertexBuffer(&amp;pVertexBuffer);<br>&nbsp;&nbsp;&nbsp;Mesh-&gt;GetIndexBuffer(&amp;pIndexBuffer);<br>&nbsp;&nbsp;&nbsp;pVertexBuffer-&gt;GetDesc( &amp;desc );<br>&nbsp;&nbsp;&nbsp;pIndexBuffer-&gt;GetDesc( &amp;idesc );<br>&nbsp;<br>&nbsp;&nbsp;&nbsp;VOID* pVBData = NULL;<br>&nbsp;&nbsp;&nbsp;VOID* pIBData = NULL;<br>&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;pVertexBuffer-&gt;Lock( 0, 0, (VOID**)&amp;pVBData, 0 );<br>&nbsp;&nbsp;&nbsp;pIndexBuffer-&gt;Lock( 0, 0, (VOID**)&amp;pIBData, 0 );<br>&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;DWORD dwFVF = Mesh-&gt;GetFVF();<br>&nbsp;&nbsp;&nbsp;DWORD dwNumBytesPerVertex = Mesh-&gt;GetNumBytesPerVertex();<br>&nbsp;&nbsp;&nbsp;DWORD dwNumFaces = Mesh-&gt;GetNumFaces();<br>&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;//怎么样？该有的都有了吧，要怎么保存就看你的了<br>&nbsp;&nbsp;&nbsp;//这段代码显然不完善，你需要自己去完善它<br>&nbsp;&nbsp;&nbsp;//有人说了你光考虑了Mesh，还有多AnimationSet等等东西呢？<br>&nbsp;&nbsp;&nbsp;//这里本来就只是建立静态模型，当然没有AnimationSet啦。动画的部分我后面在写吧。<br>&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;pVertexBuffer-&gt;UnLock();<br>&nbsp;&nbsp;&nbsp;pIndexBuffer-&gt;UnLock();<br>&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;fclose(fp);<br>&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; //增加结束</p>
<p>&nbsp;&nbsp;&nbsp; pD3DXMtrlBuffer-&gt;Release();<br>&nbsp;&nbsp;&nbsp; return S_OK;<br>&nbsp; }<br>&nbsp;</p>
<img src ="http://www.cppblog.com/xosen/aggbug/78917.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/xosen/" target="_blank">xosen</a> 2009-04-04 02:41 <a href="http://www.cppblog.com/xosen/articles/78917.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>ROAM实时动态LOD地形渲染</title><link>http://www.cppblog.com/xosen/articles/78916.html</link><dc:creator>xosen</dc:creator><author>xosen</author><pubDate>Fri, 03 Apr 2009 18:40:00 GMT</pubDate><guid>http://www.cppblog.com/xosen/articles/78916.html</guid><wfw:comment>http://www.cppblog.com/xosen/comments/78916.html</wfw:comment><comments>http://www.cppblog.com/xosen/articles/78916.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/xosen/comments/commentRss/78916.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/xosen/services/trackbacks/78916.html</trackback:ping><description><![CDATA[1 引言<br>　　<br>　 　如果没有接触过涉及到细节等级（LOD）的地形生成法则，恐怕你就不能在地形可视化的世界里任意挥舞你的指挥棒了。细节等级是一种使用了一系列启发式的 方法来决定地形的哪一部分需要看起来有更多的细节的技术。在这里，对于地形渲染的许多技术挑战之一是如何存储一个地形的特征。高度图是事实上的标准解决方 案，简单的说他们就是保存地形每点高度的二维数组。<br>　　<br>　　2 LOD地形法则概论<br>　　<br>　　一个LOD地形法则的优秀概 述可以被三篇论文来描述，作者分别为[1微软的Hoppe][2 Lindstrom][3 Duchaineau]。在第一位作者的论文中描绘了一个基于Progressive Meshes的法则，这是一个与增加三角形到任意网格来达到你需要的细节相关的新的和绝妙的技术。这篇论文是一篇精彩的读物但有点复杂，同时这项技术需要 大量的内存。第二篇论文的作者是Lindstrom，他描述了一个叫四叉树（Quad Tree）的结构用于描绘地形碎片（PATCH），一个四叉树递归的把一个地形分割成一个一个小块（tessellates）并建立一个近似的高度图。四 叉树非常简单但很有效。第三篇论文的作者是Duchaineau，他描述了一个基于二元三角树结构的法则ROAM（实时优化自适应网格）。这里每一个小片 （PATCH）都是一个单独的正二等边三角形，从它的顶点到对面斜边的中点分割三角形为两个新的正等边三角形，分割是递归进行的可以被子三角形重复直到达 到希望的细节等级。由于ROAM法则的简单和可扩展性吸引了我的目光。不幸的是这片论文非常短，仅仅只有少量的伪代码。但无论如何，他可以在连续的范围实 现从最基本的平面到最高级的优化。而且ROAM分割成小方块非常快速，而且可以动态更新高度图。<br>　　<br>　　3 ROAM执行初步<br>　　<br>　　代码用Visual C++ 6.0来写的，使用OPENGL来渲染。<br>　　<br>　　ROAM资源说明<br>　　<br>　　让我使用一个概述来介绍这个法则，然后讨论单独的小块是如何相互影响的：<br>　　<br>　　1高度图文件被载入内存并和一个Landscape类的实例相联系，多个Landscape物体连接起来产生无限的地形。<br>　　<br>　　2一个新的Landscape物体把载入的高度图的一部分包裹到新的Patch类物体中，这一步的目的是：<br>　　<br>　　（1）使用基于树的结构来控制随着深度而呈指数增长的内存，这样可以保持他们的深度在一个很小的有限的范围。<br>　　<br>　　（2）动态更新高度图需要在变更场景时有一个完整的变更树从算操作。过大的Patch类物体在实时重新计算时非常慢。<br>　　<br>　 　3每一个Patch类物体被调用来建立一个MESH的近似值（分割成小块）。Patch类物体使用了一个叫二元三角树的结构来存储即将显示在屏幕上的三 角的坐标。这些三角形顶点坐标被非常合理的存储，ROAM使用36字节以上的内存来存储每一个三角形。高效的坐标计算也是渲染的一部分（见下）。<br>　　<br>　　4在分割完高度图后，引擎已经建立了二元三角树。树的叶节点保存了需要进入图形渲染流水线的三角形。<br>　　<br>　　高度图文件格式<br>　　<br>　　高度图使用一个RAW的数据格式来保存，这个格式包含了8位的高度信息。通常高度图必须从头至尾保存在内存中，在高级标题中我将讨论如何扩展法则来呈现大的数据集。<br>　　<br>　　二元三角树Binary Triangle Trees<br>　　<br>　 　ROAM使用了二元三角树来保持三角坐标而不是存储一个巨大的三角形坐标数组来描绘地形。这个结构可以看作是一个测量员把地形切断为一个一个小三角块的 结果。这些三角块逻辑上看就象一组相连的邻居一样（左右邻居）。同样的当一个三角块把土地当作遗产时，他需要平等的分给两个儿子。<br>　　<br>　 　用这样进行扩展，这个三角块就是二元三角树的根节点，其他三角块也是他们各自树的根节点。Landscape类如同一个局域的土地注册表，保存所有三角 块的索引，同时也保存他们之间的层次关系。由于大量子三角块的产生，分割土地也成为一个沉重的负担，但是大量的细节可以被需要更好模拟的区域的种群 'population'来简单的处理。看图一：<br>　　
<center>　<img alt="" src="http://www.stcore.com/images/game/h000/h13/img20060803100411037.gif"></center><br>　　
<center>图一 二元三角树结构等级0-3</center><br>　　<br>　　二元三角树被TriTreeNode结构保存，同时他还保存ROAM需要的五个最基本的数据，参考图二。<br>　　<br>　　struct TriTreeNode {<br>　　TriTreeNode *LeftChild;<br>　　// Our Left child<br>　　TriTreeNode *RightChild;<br>　　// Our Right child<br>　　TriTreeNode *BaseNeighbor;<br>　　// Adjacent node, below us<br>　　TriTreeNode *LeftNeighbor;<br>　　// Adjacent node, to our left<br>　　TriTreeNode *RightNeighbor;<br>　　// Adjacent node, to our right<br>　　};<br>　　<br>　　
<center>　<img alt="" src="http://www.stcore.com/images/game/h000/h13/img20060803100413133.gif"></center><br>　　
<center>图二 基本的二元三角树的子和邻节点</center><br>　　<br>　 　当对高度图建立一个网格模拟值时，我们需要向二元三角树中添加子节点直到达到我们需要的细节。这一步完成后重新遍历整个树，此时把子节点中保存的三角形 数据渲染到屏幕上。这就是一个最基本的引擎了但需要重新设置每一帧，这种递归的方法最大的优点是我们不需要保存每一个顶点的数据，可以释放大量的内存给其 他物体。实际上，TriTreeNode结构需要多次的建立和销毁，但这种方法是非常高效的，同时我们或许需要建立几万个这样的结构，因此我们需要一个指 针指向我们需要的内存，TriTreeNode结构是通过一个静态内存池来分配的，而不是动态分配，他也给了我们一个快速的重新设置状态的方法。<br>　　
<center>　<img alt="" src="http://www.stcore.com/images/game/h000/h13/img200608031004152176.gif"></center><br>　　
<center>图三 典型的地形PATCH，从左至右依次是网格模式，光照模式，纹理模式</center><br>　　<br>　　4　Landscape类的详解<br>　　<br>　　Landscape类对地形的细节渲染进行了高级的封装，通过一些简单的函数调用我们可以在屏幕缓冲中进行从简单的点的显示到复杂的地形渲染工作。这里是Landscape类的定义。<br>　　<br>　　class Landscape {<br>　　public:<br>　　void Init(unsigned char *hMap);<br>　　// Initialize the whole process<br>　　void Reset();<br>　　// Reset for a new frame<br>　　void Tessellate();<br>　　// Create mesh approximation<br>　　void Render();<br>　　// Render current mesh static<br>　　TriTreeNode *AllocateTri();<br>　　// Allocate a new node for the mesh<br>　　protected:<br>　　static int m_NextTriNode;<br>　　// Index to the next free TriTreeNode<br>　　static TriTreeNode m_TriPool[];<br>　　// Pool of nodes for tessellation<br>　　Patch m_aPatches[][];<br>　　// Array of patches to be rendered<br>　　unsigned char *m_HeightMap;<br>　　// Pointer to Height Field data<br>　　};<br>　　<br>　 　Landscape类管理了一个大的正三角块，同时可以和其他Landscape物体一起工作。在初始化过程中，高度图被分割成大量的可管理的小块，同 时把他和一个新的Patch物体联系起来。Patch类及其它的方法我们将在下面花费更多的时间讲解。注意这些函数的简单性，Landscape物体本身 是设计用于一个简单的渲染流水线的，尤其是在可以免费使用Z缓冲的今天。<br>　　<br>　　5 Patch类详解<br>　　<br>　　Patch类是这个引擎的灵魂，他可以分为两部分，一半是递归部分，另一半是基本函数部分，下面就是这个类的数据成员和基本函数描述：<br>　　<br>　　class Patch {<br>　　public:<br>　　void Init( int heightX, int heightY, int worldX, int worldY, unsigned char *hMap);<br>　　// Initialize the patch<br>　　void Reset();<br>　　// Reset for next frame<br>　　void Tessellate();<br>　　// Create mesh<br>　　void Render();<br>　　// Render mesh void<br>　　ComputeVariance();<br>　　// Update for Height Map changes<br>　　...<br>　　<br>　　<br>　　protected:<br>　　unsigned char *m_HeightMap;<br>　　// Adjusted pointer into Height Field<br>　　int m_WorldX, m_WorldY;<br>　　// World coordinate offset for patch<br>　　<br>　　unsigned char m_VarianceLeft[];<br>　　// Left variance tree<br>　　unsigned char m_VarianceRight[];<br>　　// Right variance tree<br>　　<br>　　unsigned char *m_CurrentVariance;<br>　　// Pointer to current tree in use<br>　　unsigned char m_VarianceDirty;<br>　　// Does variance tree need updating?<br>　　<br>　　TriTreeNode m_BaseLeft;<br>　　// Root node for left triangle tree<br>　　TriTreeNode m_BaseRight;<br>　　// Root node for right triangle tree<br>　　...<br>　　<br>　　在上面的代码中，下面要解释的基本函数被每一个PATCH物体所调用，PATCH类的方法名类似于调用他们的Landscape类的方法，这些方法或许太单纯化这里需要详细的解释一下：<br>　　<br>　　Init()函数需要高度图和世界坐标的偏移值，他们用来对地形进行缩放，指向高度图的指针已经经过调整，指向了这个PATCH物体所需要数据的第一个字节。<br>　　<br>　 　Reset()函数释放所有无用的TriTreeNodes结构，接着重新连接两个二元三角树成为一个PATCH，现在这些还没有被提及，但是每一个 PATCH物体都有两个单独的二元三角树构成一个正方形（ROAM论文中称为'Diamond'）。如果不明白的话再看一下图二，详细的内容下一节再讨 论。<br>　　<br>　　Tessellate()函数简单的传递适当的高级三角形参数（每一个PATCH物体的两个根节点）给一个递归版本的函数，函数Render()和ComputeVariance()也是这样。<br>　　<br>　　6 ROAM精华<br>　　<br>　　讲了这么多我们只是讨&nbsp;&nbsp;
<img src ="http://www.cppblog.com/xosen/aggbug/78916.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/xosen/" target="_blank">xosen</a> 2009-04-04 02:40 <a href="http://www.cppblog.com/xosen/articles/78916.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>基于高度图的三维地形生成算法入门篇 —— 均匀网格地形生成算法</title><link>http://www.cppblog.com/xosen/articles/78915.html</link><dc:creator>xosen</dc:creator><author>xosen</author><pubDate>Fri, 03 Apr 2009 18:39:00 GMT</pubDate><guid>http://www.cppblog.com/xosen/articles/78915.html</guid><wfw:comment>http://www.cppblog.com/xosen/comments/78915.html</wfw:comment><comments>http://www.cppblog.com/xosen/articles/78915.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/xosen/comments/commentRss/78915.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/xosen/services/trackbacks/78915.html</trackback:ping><description><![CDATA[<p>&nbsp;</p>
<p>赵 刚</p>
<p><strong>引言<br></strong>&nbsp;&nbsp;&nbsp; 在三维游戏等建立的虚拟世界中要求虚拟场景具有很高的逼真度，其中的三维地形逼真度是关键之一。然而三维地形的生成和绘制需要巨大的计算量，实景地形的生成还需要地形数据库的支持，在运算能力非常有限的PC机中实时生成逼真的实景三维地形一直是业界的一个难题。三维地形的生成方法经过了多年的探索，现已形成一系列优秀的算法，本文介绍的算法是一种入门的算法，学习该算法可为系统学习三维地形生成算法打下基础，该算法复杂度较低，运算快速，生成的地形可以满足小规模地形可视化的要求。</p>
<p><strong>一、算法实现过程</strong><br>&nbsp;&nbsp;&nbsp; Direct3D立即模式中，三维绘图使用DrawPrimitive方法，DrawPrimitive方法将三维模型分解为基本的点，线，和三角形面三种，本三维地形生成法中将地形全部分解成三角形，使用DrawPrimitive方法绘制一系列三角形，从而绘制出整个地形。DrawPrimitive 绘制三角形的方法有三种，第一种是绘制离散的三角形，每个三角形分别指定三个顶点，这种方法适合绘制零散的三角形，对于绘制成片的三角形效率较低。第二种是绘制三角形序列，第一个三角形指定三个顶点，其余的三角形只需要指定一个顶点，另外两个取前一个三角形的最后两个顶点，这样绘制的三角形全部连接在一块儿，适合于绘制成片的三角形。第三种是绘制三角形组成的扇形，以第一个顶点作为所有三角形个公共顶点，为成一个扇形，该方法只适合绘制扇形类的面。因此本地形生成方法采用第二种方法，但将地形中所有的三角形的顶点坐标按首尾相接方式排列起来以适合于第二种绘制三角形方法很难，幸运的是Direct3D立即模式提供了顶点索引的绘图方法，只要将待绘制顶点的编号传递给DrawPrimitive的姊妹函数DrawIndexedPrimitive，就可以完成和DrawPrimitive一样的功能。顶点在内存中采用数组的方式存储，因此顶点编号是顺序编号。因此只要提供一个使三角形的顶点按首尾相接的索引序列，就可以完成高效的绘图。</p>
<p>本三维地形生成方法生成地形的过程如下：<br>第一步：初始化一个正方形网络（如图1），网格数为64&#215;64（规模太小，生成的地形不逼真，规模太大，PC机难以处理，因此经过多次试验，采用64&#215;64格最为合适）每个正方形网格用两个三角形表示。网格的边长由可视区内的地面大小决定，比如网格边长为50米，可见的地面大小为50&#215;64＝3200米。（网格的边长小，生成的地面就小，网格的边长大，生成的地面就大，但地面的逼真度降低，试验表明采用50米比较合适）。</p>
<p><a class=imagelink title=tu href="http://www.renao.cn/blog/wp-content/uploads/2006/04/zhaog-1.gif"><img id=image155 height=58 alt=tu src="http://www.renao.cn/blog/wp-content/uploads/2006/04/zhaog-1.thumbnail.gif"></a>图1 正方形网格示意图（4&#215;3）</p>
<p>顶点包含的信息有：<br>1.&nbsp;位置坐标 x、y、z（x表示左右方向，y表示垂直方向，z表示纵深方向）<br>2.&nbsp;法线坐标 nx、ny、nz（表示该点周围地面陡峭情况）<br>3.&nbsp;颜色 diffuse（表示该点对光线的反射性质）<br>4.&nbsp;纹理坐标 tu、tv（tu表示纹理横坐标，tv表示纹理纵坐标）<br>定义一个结构存储这些信息：<br>struct VERTEX<br>{<br>&nbsp;D3DVALUE x,y,z;<br>&nbsp;D3DVALUE nx,ny,nz;<br>&nbsp;D3DCOLOR diffuse;<br>&nbsp;D3DVALUE tu,tv;<br>};<br>在64&#215;64的网格中，顶点的数量为（64＋1）&#215;（64＋1）＝4225，因此可声明一个长度为4225的数组存储顶点信息：VERTEX m_Vertex[4225];<br>顶点的编号规则为从左到右，从上到下顺序编号，这样第一行第一列的顶点为0号，第一行第二列的顶点为1号，第二行第一列的顶点为65号，依次类推&#8230;&#8230;，因此64&#215;64的正方形网的顶点可初始化如下：<br>for(j=0;j&lt;65;j++)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp; for(i=0;i&lt;65;i++)<br>&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_Vertex[j*65+i].x=-m_Length*0.5f+m_Block*i;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_Vertex[j*65+I].y=0.0f;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_Vertex[j*65+i].z= m_Length*0.5f-m_Block*j;<br>&nbsp;&nbsp;&nbsp;&nbsp; m_Vertex[j*65+i].nx=0.0f;<br>&nbsp;&nbsp;&nbsp;&nbsp; m_Vertex[j*65+i].ny=1.0f;<br>&nbsp;&nbsp;&nbsp;&nbsp; m_Vertex[j*65+i].nz=0.0f;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_Vertex[j*65+I].diffuse=0xffffffff;<br>&nbsp;&nbsp;&nbsp;&nbsp; }<br>}<br>其中 m_Length 表示可见地面长度（3200米）m_Block 表示网格长度（50米）<br>&nbsp;&nbsp;&nbsp; 按这种方式初始化完的正方形网是一张平面（坐标y均为0），它表示的地面将是一片平地，不含高程数据，高程数据的加入将在稍后介绍。<br>为了使用DrawPrimitive第二种绘制三角形的方式绘图，必须制作一顶点索引序列，使顶点按三角形首尾相接的顺序排列。该顶点索引序列可如下赋值：<br>WORD m_Index[64*64*6];<br>for(j=0;j&lt;64;j++)<br>{<br>&nbsp;for(i=0;i&lt;64;i++)<br>&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp; m_Index[j*64*6+i*6+0]=(WORD)(j*65+i);<br>&nbsp;&nbsp;&nbsp;&nbsp; m_Index[j*64*6+i*6+1]=(WORD)(j*65+i+65);<br>&nbsp;&nbsp;m_Index[j*64*6+i*6+2]=(WORD)(j*65+i+65+1);<br>&nbsp;&nbsp;m_Index[j*64*6+i*6+3]=(WORD)(j*65+i);<br>&nbsp;&nbsp;m_Index[j*64*6+i*6+4]=(WORD)(j*65+i+1);<br>&nbsp;&nbsp;m_Index[j*64*6+i*6+5]=(WORD)(j*65+i+65+1);<br>&nbsp;}<br>}<br>地形的高程数据存贮在一个512&#215;512像素的256色BMP文件里，该图形文件按实际地形高程测绘，使用图形中的红色分量表示地面的海拔高度，红色分量从 0～255共有256级，这样地面的高度也只有256级，这样的精度对于三维地形仿真已经足够。本方法中，为进一步提高地形的真实度，还在地形中融入了水面的效果，只要将高程图的红色分量指定为0，将生成水面而非地面，要指定为陆地，红色分量范围为1～255。高程图中的绿色分量用来指定是否有树林，绿色分量越大，表示树林越密，同时这片土地将呈现为草地效果，但绿色分量不可滥用，因为绘制树木很费时间，树木过多将降低程序地实时性，一般将可见地树木数量限制在500棵以内。典型地高程图 如图2。</p>
<p><a class=imagelink title=fig2 href="http://www.renao.cn/blog/wp-content/uploads/2006/04/zhaog-2.jpg"><img id=image156 height=96 alt=fig2 src="http://www.renao.cn/blog/wp-content/uploads/2006/04/zhaog-2.thumbnail.jpg"></a>图2 典型高程图</p>
<p>&nbsp;&nbsp;&nbsp; 图中为了直观，将红色成分为零的区域绘制成蓝色，可以明显的看到水域部分构成了一条河流和两个湖泊。<br>&nbsp;&nbsp;&nbsp; 高程图的分辨率为512像素&#215;512像素，地形网格的每一个顶点对应一个像素，而顶点间隔为50米，这样这幅高程图表示的地形范围为边长512&#215;50＝ 25600米（25.6公里）的正方形区域（655.36平方公里）这对一般的三维场景已经够用，如果仍不够用，可增大高程图的分辨率，或采用多幅高程图拼接，但会消耗更多的内存。<br>要使地面逼真，地面还应该贴上一层纹理图，纹理图可以是典型地面的照片，或用图像处理软件制作而成。为了正确贴上纹理图，应该该顶点指定纹理坐标，因显示存储器是很有限的，所以纹理图不可能很大，相对于巨大的地面来说，纹理图显得非常不足，因此纹理图要重复使用，就好像贴地砖一样，贴在地面上，这样带来的问题是地面上的纹理呈现周期性，就好像真的是地砖铺的一样，而且纹理图的拼接处会出现难看的裂纹，要减少这些现象，首先纹理图不能太小（本方法中采用1024&#215;1024像素的位图）其次，纹理图的边缘要做特殊处理，使纹理拼接的时候不出现裂缝（这种图叫做可拼接图，广泛用于网页的底纹）本方法中纹理图的边缘不用做特殊处理，也不会出现裂缝，因为本法在贴纹理的时候让拼接处的两个纹理图成镜像关系，因此边缘的图形一致，不会错位，但缺点是可以见到很多对称的花纹，用小纹理拼接地表的方法有待改进，典型的地面纹理图如图3。&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</p>
<p><a class=imagelink title=fig3 href="http://www.renao.cn/blog/wp-content/uploads/2006/04/zhaog-3.jpg"><img id=image157 height=96 alt=fig3 src="http://www.renao.cn/blog/wp-content/uploads/2006/04/zhaog-3.thumbnail.jpg"></a>图3 土质地面纹理图</p>
<p>为了兼顾纹理的细腻度和低周期性，本法让每个网格拥有的纹理大小为64&#215;64像素，这样一张1024&#215;1024像素的纹理图可覆盖16&#215;16个网格（也就是800米&#215;800米的面积）纹理坐标的算法如下：<br>long p,q,a,b;<br>float x,y;<br>x= m_CenterPos[0]*m_ReciBlock-32;<br>&nbsp;y=-m_CenterPos[2]*m_ReciBlock-32;<br>for(j=0;j&lt;65;j++)<br>{<br>&nbsp;for(i=0;i&lt;65;i++)<br>&nbsp;{<br>p=(((DWORD)x+150+m_TexWidth/2+i)*64)%m_TexWidth;<br>q=(((DWORD)y+150+m_TexHeight/2+j)*64)%m_TexHeight;<br>a=(((DWORD)x+150+m_TexWidth/2+i)*64)/m_TexWidth;<br>b=(((DWORD)y+150+m_TexHeight/2+j)*64)/m_TexHeight;<br>&nbsp;if(a%2) m_Vertex[j*65+i].tu=(float)p/m_TexWidth;<br>&nbsp;else m_Vertex[j*65+i].tu=(float)(m_TexWidth-p)/m_TexWidth;<br>&nbsp;if(b%2)&nbsp;m_Vertex[j*65+i].tv=(float)q/m_TexHeight;<br>&nbsp;else m_Vertex[j*65+i].tv=(float)(m_TexHeight-q)/m_TexHeight;<br>}<br>&nbsp;&nbsp;&nbsp; }<br>其中 m_CenterPos[0]，m_CenterPos[2]是可见地面中心的水平坐标<br>&nbsp;&nbsp;&nbsp;&nbsp; m_TexWidth，m_TexHeight 是纹理的宽度和高度<br>&nbsp;&nbsp;&nbsp;&nbsp; m_ReciBlock 是网格边长的倒数（1/50）<br>变量 m_CenterPos[0]，m_CenterPos[2]用于确定绘制地面的中心坐标，在该坐标周围的指定距离内的地面将被绘制，当三维场景中的观察点移动时，地面中心坐标也要做相应的移动，否则观察点移动一定位置后，将会看到地面的边界，甚至看不到地面。一般来说，三维场景中可见区域是一个锥形，观察点位于锥顶，锥底垂直于观察方向，并向观察方向延伸。对于这样可视区，使地面的中心坐标和观察点的水平坐标一致是不可取的，因为在观察点后面有和观察点前面同样大小的一块地面被绘制了，但是却看不到，白白浪费时间，因此应将地面的中心坐标置于观察点正前方一定的距离处，对于边长为3200米的地面，这个距离取1200米是比较合适的。因此程序要负责把观测点正前方，距离观察点1200米的那个点的坐标算出来，用这个坐标给m_CenterPos[0]， m_CenterPos[2]赋值，如图4。这样仍有一半左右的地面位于可视区之外，采用其他方法避免非可视区内地面的绘制。<br><a class=imagelink title=fig4 href="http://www.renao.cn/blog/wp-content/uploads/2006/04/zhaog-4.gif"><img id=image158 height=96 alt=fig4 src="http://www.renao.cn/blog/wp-content/uploads/2006/04/zhaog-4.thumbnail.gif"></a>图4 地面中心在可视区内的选择</p>
<p>到现在为止地面还是一张平面，还没有将高程数据写到顶点坐标里。下面将介绍如何将高程数据写入顶点坐标里。高程数据顺序存贮在512&#215;512像素的图像文件里。我们认为图像的中心为坐标原点，往上和往右是坐标增长的方向，这样可以算得，图像的左上端点像素（即第一个像素）存储着坐标为（－256&#215; 50，256&#215;50）即（－12800，12800）地面的高程数据，第二个像素存贮着坐标为（－12750，12800）地面的高程数据一次类推，因此可以根据地面的水平坐标去高程图像中寻址，获取高程数据，高程数据加入网格中后如图5所示。</p>
<p><a class=imagelink title=fig5 href="http://www.renao.cn/blog/wp-content/uploads/2006/04/zhaog-5.gif"><img id=image159 height=93 alt=fig5 src="http://www.renao.cn/blog/wp-content/uploads/2006/04/zhaog-5.thumbnail.gif"></a>图5 高程数据加入地形网格中</p>
<p>为了提高绘制速度，本法对地形网格中每一个网格都进行一次可见性判断，如果网格位于可视区内，就绘制该网格，否则不绘制。本地形中共有64&#215;64＝ 4096个网格，要进行4096次判断，但判断可见性的函数本身运行较慢，过多的判断反而适得其反，因此，本法中对网格可见性的判断以四个为一组，共判断 1024次，四个中只要有一个位于可视区内就认为四个都可见，否则不可见。这样虽然绘制效率有所降低，但却大大节省了可见性判断时间。试验证明以四个一组的分法总体运行速度最高。<br>本法又声明了另外一个顶点索引数组 WORD m_IndexTemp[4225]，对于可见性判断成功的顶点，与其相关的顶点索引将存入m_IndexTemp，否则不存。这样 m_IndexTemp里只含有在可视区内的顶点索引，使DrawPrimitive绘制最少的三角形，最大程度提高速度。<br>对于高程图中红色分量为零的区域，通过以下方法将地表描述为水面：<br>1．&nbsp;地表的颜色为浅蓝色，半透明。<br>2．&nbsp;地表的纹理坐标周期性移动，使水面具有流动感。<br>处理如下：<br>for(j=0;j&lt;65;j++)<br>{<br>for(i=0;i&lt;65;i++)<br>{&#8230;&#8230;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(altitude==0) //水域地带<br>{&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp; m_Vertex[j*65+i].diffuse=0x600030ff; //浅蓝色，半透明<br>&nbsp;&nbsp;&nbsp;&nbsp; if(Count/16%2) //处理水的流动感<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p=(((DWORD)x+150+m_TexWidth/2+i)*64+Count%16)%m_TexWidth;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp; q=(((DWORD)y+150+m_TexHeight/2+j)*64+Count%16)%m_TexHeight;<br>&nbsp;&nbsp;&nbsp;&nbsp; a=(((DWORD)x+150+m_TexWidth/2+i)*64+Count%16)/m_TexWidth;<br>&nbsp;&nbsp;&nbsp;&nbsp; b=(((DWORD)y+150+m_TexHeight/2+j)*64+Count%16)/m_TexHeight;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp; else<br>{<br>&nbsp;&nbsp;p=(((DWORD)x+150+m_TexWidth/2+i)*64+16-Count%16)%m_TexWidth;&nbsp;<br>&nbsp;&nbsp;&nbsp;q=(((DWORD)y+150+m_TexHeight/2+j)*64+16-Count%16)%m_TexHeight;<br>&nbsp;&nbsp;&nbsp;a=(((DWORD)x+150+m_TexWidth/2+i)*64+16-Count%16)/m_TexWidth;<br>&nbsp;&nbsp;&nbsp;b=(((DWORD)y+150+m_TexHeight/2+j)*64+16-Count%16)/m_TexHeight;<br>&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;&nbsp;&nbsp;&nbsp; if(a%2) m_Vertex[j*65+i].tu=(float)p/m_TexWidth;<br>&nbsp;&nbsp;&nbsp;&nbsp; else m_Vertex[j*65+i].tu=(float)(m_TexWidth-p)/m_TexWidth;<br>&nbsp;I&nbsp;&nbsp;&nbsp; f(b%2)&nbsp;m_Vertex[j*65+i].tv=(float)q/m_TexHeight;<br>&nbsp;&nbsp;&nbsp;&nbsp; else m_Vertex[j*65+i].tv=(float)(m_TexHeight-q)/m_TexHeight;<br>}<br>}<br>其中 Count是计数器，每0.1秒数值增加1。</p>
<p>对于高程图中绿色不为零的区域为树林，通过以下方法实现<br>1．&nbsp;地面的颜色为暗绿色，表示草地<br>2．&nbsp;随机产生树木坐标，在该树木坐标上用公共板技术显示树木。<br>实施如下：<br>if(m_ShowTree) //建立树木参数<br>{<br>&nbsp;long r,s;<br>&nbsp;if(green&gt;0&amp;&amp;m_TreeNum &nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; r=abs((long)((m_Vertex[j*m_Row+i].tu<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; +m_Vertex[j*m_Row+i].tv*10)*10000)%1000);<br>&nbsp;&nbsp;for(s=0;s &nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;vect.x=m_Vertex[j*m_Row+i].x+m_RandPos[s+r][0];<br>&nbsp;&nbsp;&nbsp;vect.z=m_Vertex[j*m_Row+i].z+m_RandPos[s+r][2];<br>&nbsp;&nbsp;&nbsp;vect.y=m_Vertex[j*m_Row+i].y;<br>&nbsp;&nbsp;&nbsp;D3DMath_VectorMatrixMultiply(vect,vect,m_Matrix);<br>&nbsp;&nbsp;&nbsp;vect.y=GetHeight(vect.x*0.01f,vect.z*0.01f,FALSE)*100.0f;<br>&nbsp;&nbsp;&nbsp;m_TreePos[m_TreeNum][0]=vect.x;<br>&nbsp;&nbsp;&nbsp;m_TreePos[m_TreeNum][1]=vect.y;<br>&nbsp;&nbsp;&nbsp;m_TreePos[m_TreeNum][2]=vect.z;<br>&nbsp;&nbsp;&nbsp;m_TreePos[m_TreeNum][3]=(float)((long)(green+s+r)%5);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_TreeNum++;<br>&nbsp;&nbsp;}<br>&nbsp;}<br>}</p>
<p>其中m_ShowTree 是逻辑量，为真时才绘制树木（可以不绘制树木以提高速度）<br>m_TreeMax 用来控制树木数量, 当m_TreeNum等于m_TreeMax时不再增加树木。<br>GetHight（）是一个函数，用来计算地面高度。<br>最后绘制地形调用DrawIndexedPrimitive如下：</p>
<p>m_pd3dDevice-&gt;SetTexture(0,m_pTexture);<br>m_pd3dDevice-&gt;SetTransform(D3DTRANSFORMSTATE_WORLD,&amp;m_Matrix);<br>m_pd3dDevice-&gt;SetRenderState(D3DRENDERSTATE_CULLMODE,D3DCULL_NONE);<br>m_pd3dDevice-&gt;DrawIndexedPrimitive(D3DPT_TRIANGLELIST,D3DFVF_VERTEX, m_Vertex, m_VertexNum,m_IndexTemp,m_IndexNum,NULL);</p>
<p><a class=imagelink title=fig6 href="http://www.renao.cn/blog/wp-content/uploads/2006/04/zhaog-6.jpg"><img id=image160 height=91 alt=fig6 src="http://www.renao.cn/blog/wp-content/uploads/2006/04/zhaog-6.thumbnail.jpg"></a>图6 最终效果示例</p>
<p><strong>二、结束语<br></strong>&nbsp;&nbsp;&nbsp; 本文介绍的三维地形快速生成算法为典型的基于高度图的均匀网格地形生成算法，具有学习三维地形生成算法的入门指导作用。试验证明，在配置为PIII 667MHz，128MB内存，Matrix G400显示卡的计算机中以分辨率为1024&#215;768运行，速度达到35帧/秒以上。该算法的程序代码已经使用在某大型工程中。</p>
<img src ="http://www.cppblog.com/xosen/aggbug/78915.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/xosen/" target="_blank">xosen</a> 2009-04-04 02:39 <a href="http://www.cppblog.com/xosen/articles/78915.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>3D地形多层纹理混合加阴影渲染方法</title><link>http://www.cppblog.com/xosen/articles/78914.html</link><dc:creator>xosen</dc:creator><author>xosen</author><pubDate>Fri, 03 Apr 2009 18:36:00 GMT</pubDate><guid>http://www.cppblog.com/xosen/articles/78914.html</guid><wfw:comment>http://www.cppblog.com/xosen/comments/78914.html</wfw:comment><comments>http://www.cppblog.com/xosen/articles/78914.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/xosen/comments/commentRss/78914.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/xosen/services/trackbacks/78914.html</trackback:ping><description><![CDATA[<p>原文作者：<strong><font color=#ff0000>Dolaham</font><br></strong>原文链接：<br><a href="http://bbs.gameres.com/showthread.asp?threadid=14941"><font color=#336699>http://bbs.gameres.com/showthread.asp?threadid=14941</font></a><br><a href="http://bbs.games.sina.com.cn/?h=http%3A//bbs.games.sina.com.cn/g_forum/00/90/07/view.php%3Ffid%3D11054%26tbid%3D2345&amp;g=15"><font color=#336699>http://bbs.games.sina.com.cn/?h=http%3A//bbs.games.sina.com.cn/g_forum/00/90/07/view.php%3Ffid%3D11054%26tbid%3D2345&amp;g=15</font></a></p>
<p>地 形由于十分庞大，需要大量顶点，所以往往占用很多内存空间，那么就应该在地形贴图上想办法节约空间。很多游戏的地形，虽然看上去不同地点的纹理好像互不相 同，地表纹理十分丰富，但其实真正用的贴图是很少的，之所以还能产生地表纹理变化多端的视觉效果，是因为使用了Alpha混合和阴影，从而产生一种错觉。 2.5D的网络游戏《奇迹》（MU）、全3D引擎Torque（多人联网FPS游戏《部落2》即使用此引擎）、全3D的滑雪游戏《极限滑雪》 （Supreme&nbsp;Snowboarding），都是这么做的。</p>
<p>我对此设想了一个解决方案。需要用到顶点的Diffuse颜色、3个 TextureStage。其实所谓的多层纹理，具体到一个三角形上的时候，很多游戏只用了2层纹理来做Alpha混合（因为也许玩家的显卡只支持2层 MultiTexture混合），然后再一层光照纹理。前2层纹理要做Alpha混合，Alpha来自哪里？若使用Alpha贴图，就必然再增加一层 TextureStage，而我选择使用顶点的Diffuse就可以省掉这一层TextureStage：由Diffuse的Alpha通道提供 Alpha值供前两层TextureStage混合使用。下面是在D3D中的设置：<br><br>//&nbsp;TextureStage&nbsp;0<br>pd3dDevice-&gt;SetTextureStageState(&nbsp;0,&nbsp;D3DTSS_COLOROP,&nbsp;&nbsp;&nbsp;D3DTOP_SELECTARG1&nbsp;);&nbsp;<br>pd3dDevice-&gt;SetTextureStageState(&nbsp;0,&nbsp;D3DTSS_COLORARG1,&nbsp;D3DTA_TEXTURE&nbsp;);<br>pd3dDevice-&gt;SetTextureStageState(&nbsp;0,&nbsp;D3DTSS_ALPHAOP,&nbsp;&nbsp;&nbsp;D3DTOP_DISABLE&nbsp;);<br><br>//&nbsp;TextureStage&nbsp;1<br>pd3dDevice-&gt;SetTextureStageState(&nbsp;1,&nbsp;D3DTSS_COLOROP,&nbsp;&nbsp;&nbsp;D3DTOP_BLENDDIFFUSEALPHA&nbsp;);<br>pd3dDevice-&gt;SetTextureStageState(&nbsp;1,&nbsp;D3DTSS_COLORARG1,&nbsp;D3DTA_TEXTURE&nbsp;);<br>pd3dDevice-&gt;SetTextureStageState(&nbsp;1,&nbsp;D3DTSS_COLORARG2,&nbsp;D3DTA_CURRENT&nbsp;);<br>pd3dDevice-&gt;SetTextureStageState(&nbsp;1,&nbsp;D3DTSS_ALPHAOP,&nbsp;&nbsp;&nbsp;D3DTOP_DISABLE&nbsp;);<br><br>在TextureStage&nbsp;0， 什么也没做，把第一层纹理原封不动的留给TextureStage&nbsp;1；在TextureStage&nbsp;1，COLOROP使用 D3DTOP_BLENDDIFFUSEALPHA，也就是使用来自顶点Diffuse的Alpha值作为混合因子混合第2层纹理和 TextureStage&nbsp;0原封不动留下来的第1层纹理。混合公式如下（来自DX帮助文档）：<br><font color=#ff0000>Result&nbsp;=&nbsp;Arg1&nbsp;*&nbsp;Alpha&nbsp;+&nbsp;Arg2&nbsp;*&nbsp;(1&nbsp;&#8211;&nbsp;Alpha)<br></font>Arg1是来自第2层纹理的颜色，Arg2是来自TextureStage&nbsp;0原封不动留下来的第1层纹理的颜色。并且，这里的Alpha值是D3D插值运算后得到的，因此混合效果很平滑。<br><br>你不相信2层纹理混合就能得到丰富的地表视觉效果吗？你应该相信，因为虽然Arg1和Arg2总是恒定不变的来自2个贴图，因此对三角形上的同一点来说，这两个是常量，但Alpha值却是个变量，Alpha值取不同的值就会产生不同的Result。<br><br>假 设与Arg1相对应的贴图是一个草地的贴图，与Arg2相对应的贴图是一个沙地的贴图，在一片地形区域内，仅使用这两张贴图，但是由于各顶点的 Diffuse的Alpha值不同，你会看到最终渲染出来的这片地形区域，草地的分布并不均匀，有些地方草的样子多一些，有些地方沙的样子多一些。这种效 果看上去已经很自然了。<br><br>当然并不限制你在一个地形场景上使用更多的地表纹理，但是对一个三角形来说，2张纹理已经足够，其它纹理请用在其 它三角形上吧，这可以产生更丰富的地貌。比如你想让另一片地形区域是雪地和岩石的风格，那么在那片区域上就使用1张雪地贴图、1张岩石贴图。现在这个关卡 里面共有4张贴图了，但同一个三角形仍然只使用2张贴图（这是硬件的限制啊！！）。<br><br>现在来说阴影。前面已经使用了顶点Diffuse的Alpha通道，现在他的RGB三个分量也要派上用场了。在D3D中设置如下：<br><br>//&nbsp;TextureStage&nbsp;2<br>pd3dDevice-&gt;SetTextureStageState(&nbsp;2,&nbsp;D3DTSS_COLOROP,&nbsp;&nbsp;&nbsp;D3DTOP_MODULATE&nbsp;);<br>pd3dDevice-&gt;SetTextureStageState(&nbsp;2,&nbsp;D3DTSS_COLORARG1,&nbsp;D3DTA_DIFFUSE&nbsp;);<br>pd3dDevice-&gt;SetTextureStageState(&nbsp;2,&nbsp;D3DTSS_COLORARG2,&nbsp;D3DTA_CURRENT&nbsp;);<br>pd3dDevice-&gt;SetTextureStageState(&nbsp;2,&nbsp;D3DTSS_ALPHAOP,&nbsp;&nbsp;&nbsp;D3DTOP_DISABLE&nbsp;);<br><br>这是第3个TextureStage，COLOROP为相乘，Arg1设为Diffuse，是来自经过D3D插值运算的顶点Diffuse颜色，Arg2是Current就是TextureStage&nbsp;1产生的结果。混合公式如下：<br><font color=#ff0000>Result&nbsp;=&nbsp;Arg1&nbsp;*&nbsp;Arg2</font><br>颜色虽是RGB三个字节（或者其它颜色格式），但D3D会将它们拆成单独的3个RGB通道值（0-1之间的浮点数）。相乘运算可以贴上彩色的光影效果。<br><br>有 一个问题是，人们当初选择使用光照图就是为了能够提高光影的精确度，因为顶点光照只作用于顶点，虽然渲染API会在顶点之间做插值，但这也只在顶点很密集 的时候才会有很精确的光照，如果定点很稀疏甚至不能产生正确的光照效果。这里使用顶点Diffuse，会不会也得不到令人满意的光照效果呢？<br><br>我 想过使用一整张光照图铺在整个地形上，但我忽然想起这与基于heightmap的地形生成技术面临一个同样的问题，精度。使用heightmap，为了减 小游戏体积，这个heightmap不可能很大，不可能每一个高度点只对应一个顶点，而是多个顶点被映射到同一个高度点上去了。一整张光照图也面临同样的 问题，也许多个顶点使用光照图上的同一个像素呢？如果两个顶点用的是光照图上的同一个像素，那顶点之间的空间也肯定是用这个像素点了，这样是不会提高精度 的。把光照图的尺寸增大？那是选择512*512呢，还是1024*1024？<br><br>而实际上，LOD地形的顶点总是很密集的。至少在靠近照相 机的区域是这样的，往往比室内建筑的顶点还要密集。虽然还没密集到可以让D3D的聚光灯产生满意效果的地步，但已足够使用顶点光照模拟地形因高低起伏而产 生的阴影效果。何况业界普遍认为地形渲染的精度是低于室内渲染一个档次的（新游戏FarCry是个例外）。<br><br>更妙的是，顶点光照信息可以存储在高度图里，我们把高度点从一个unsigned&nbsp;char改为一个多字节的数据结构：<br><br>struct&nbsp;HeightPoint<br>{<br>&nbsp;&nbsp;unsigned&nbsp;char&nbsp;height;&nbsp;&nbsp;//&nbsp;用于计算定点的Y坐标值，用于渲染地形高度起伏<br>&nbsp;&nbsp;unsigned&nbsp;char&nbsp;alpha;&nbsp;&nbsp;//&nbsp;定点diffuse的alpha，用于多层纹理混合<br>&nbsp;&nbsp;unsigned&nbsp;char&nbsp;red;&nbsp;&nbsp;//&nbsp;顶点diffuse的red，用于光照<br>&nbsp;&nbsp;unsigned&nbsp;char&nbsp;green;;&nbsp;&nbsp;//&nbsp;顶点diffuse的green，用于光照<br>&nbsp;&nbsp;unsigned&nbsp;char&nbsp;blue;&nbsp;&nbsp;//&nbsp;顶点diffuse的blue，用于光照<br>};<br><br>这样高度图信息不仅包含高度信息，也包含多层纹理混合信息、光照信息。当动态创建地形的时候，在读取高度数据的同时也能得到与该顶点对应的diffuse值，非常方便。<br>下面是用DX自带的MFCTex程序验证理论。3个TextureStage正是使用上面所说的设置方法。<br><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/coderwu/277788/o_pic001.gif"><br>&nbsp;<br>env2.bmp是一个砖墙的贴图，caust11.tga是一个水面波纹的贴图，env3.bmp没有被使用。Diffuse颜色是0x77ff0000，是纯红色，但alpha值为119。<br><br>Stage0 把env2.bmp的图像数据原封不动的保留给Stage1。Stage1的COLOROP使用D3DTOP_BLENDDIFFUSEALPHA的方 法，以diffuse的alpha值（119&nbsp;=&gt;&nbsp;0.53）为混合因子混合caust11.tga和env2.bmp，公式是： ResultOfStage1&nbsp;=&nbsp;caust11.tga&nbsp;*&nbsp;0.53&nbsp;+&nbsp;env2.bmp&nbsp;*&nbsp;(1&nbsp;&#8211;&nbsp;0.53)，得到的视觉结果是我们既能 看到砖墙又能看到水面波纹，他们被半透明的混合起来了。Stage2使用相乘，把diffuse的RGB三分量与ResultOfStage1 （Current就是ResultOfStage1）上对应点的RGB三分量乘起来，由于diffuse的RGB是ff0000纯红色，完全没有 green和blue分量，所以这次得到的视觉结果是整个墙壁偏红色。这样我们就产生了墙壁上被投上了水面折射的波纹，而整个墙壁又被一片红光照亮的效 果。<br><br>下面来看看Torque引擎、《极限滑雪》的地形贴图渲染效果。<br><br>首先是Torque。Torque引擎的Demo所 使用的纹理文件都没有打包，所以我很容易找到这些文理，用Photoshop给它们画上标记，再到游戏中看他们是如何被混合、以及怎样贴到地面上去。这个 Demo只用了3张地形纹理：1个grass.jpg，1个sand.jpg，1个patchy.jpg。每张纹理我先用红色粗线条勾出它的4条边，再在 左上角和右下角用写上它的文件名，我还给它们写上中文译名，grass.jpg是&#8220;草&#8221;，sand.jpg是&#8220;沙&#8221;，patchy.jpg是&#8220;稀疏的草 地&#8221;。来看截图：<br><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/coderwu/277788/o_pic002.jpg"><br><br>每 张地形纹理都是256*256的，从空中这个角度看，每张纹理都被映射到地形上一个相当大的方形区域，所以镜头贴近地面的时候，会变得很模糊，但 Torque会在这时贴上细节纹理（参见后面的截图），一个叫detail1.png的图片文件（还是只有1张，但效果很好），这是另一个技术了，在此不 表。图中，凡是黄色区域都来自sand.jpg。我们看那个左边有&#8220;稀疏的草地&#8221;的方形区域，这个区域用的是patchy.jpg和sand.jpg两张 纹理混合的，&#8220;地&#8221;字就刚好位于混合边界，所以模糊了。混合因子不是顶点diffuse的alpha吗？所以这个区域里面，要有更多的顶点，没问题， Torque使用四叉树来创建地形，这个方形区域里面被细分为更小的方形区域，而且不是平衡的（LOD技术），所以有足够的顶点，同时看上去混合边界是扭 曲的。这个Demo可以切换为线框渲染模式，我好好观察了那些顶点的分布，确实跟Alpha混合的分布相一致。再来一张截图：<br><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/coderwu/277788/o_pic003.jpg"><br><br>从这个截图里面看出，阴影的存在确实起到了丰富视觉效果的作用。看左边那个方形区域，几乎全是沙地，但有阴影的存在，效果就好多了。你可能觉得它的阴影很细致，但别忘了这是一个全3D的FPS游戏，我是在很高的空中俯拍的。<br><br>另 一个游戏《极限滑雪》，这是欧洲某个国家制作的商业游戏。虽是商业游戏，它的纹理文件也没有打包，赤裸裸的jpg、tga，所以我把这些纹理都替换为只有 一个颜色的图片，再勾勒出图片的四条边，打上文件名。由于是个滑雪游戏，所以场景中最常见的颜色就是白色了，所以大范围内只使用同一张纹理用得理直气壮。<br><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/coderwu/277788/o_pic004.jpg"><br><br>当然要加上阴影，不然真的会灼伤玩家的眼睛呢。有了阴影，效果好一些了。再加上彩色的光影，还蛮令人着迷的，看图：<br><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/coderwu/277788/o_pic005.jpg"><br><br>游戏中的山头和公路都需要别的纹理来混合。<br><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/coderwu/277788/o_pic006.jpg"><br><br>看图6的阴影，尤其是左边的，有较为明显的梯级，右边的呈矩形，应该是那棵树的影子，但却很涣散，很不对劲，跟顶点光照的效果十分相像，可能是场景编辑器把计算出来的影子信息存储到顶点的diffuse中去了。<br>这个游戏的雪地纹理只有2张，另一张出现的频率较小。<br><br>附：Torque的细节纹理<br><img alt="" src="http://p.blog.csdn.net/images/p_blog_csdn_net/coderwu/277788/o_pic007.jpg"><br><br><strong><font color=#ff0000>Dolaham的修正：<font color=#000000><br></font></font></strong>我 重新观察了一下Torque引擎Demo的地形，发现在他在一个方块内最多用了3张贴图。这3张贴图就需要3个Texture&nbsp;Stage，再加上2个提 供Alpha值的Stage，总共就是5个Texture&nbsp;Stage。这对显卡要求太高，至少我的显卡只支持4个Texture&nbsp;Stage。那么就必 须使用MultiPass了，多遍渲染。这篇文章说的把Alpha信息存储在顶点Diffuse里面的方法，还是值得考虑的。因为如果是用Alpha图的 话，Alpha图总会被拉伸得很厉害，使得地形上不同贴图之间仍然不能自然过渡。除非你的地形有更大的区域概念，对一个区域使用一张Alpha图，这可能 会解决Alpha图被过度拉伸的问题。不然，你只使用全局唯一的一张Alpha图的话，将很难收到理想的效果。<br>由顶点的Diffuse提供 Alpha有一个好处，那就是D3D会做插值运算，这样就不会让某一点的Alpha扩展成斑块，因为它扩展出来的新值会被D3D插值运算的。那么要实现 Torque那样的最多支持3张纹理混合，同一个地形就需要2份Diffuse。可以考虑使用多流的技术，让Diffuse数据作为一个单独的流。 </p>
<img src ="http://www.cppblog.com/xosen/aggbug/78914.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/xosen/" target="_blank">xosen</a> 2009-04-04 02:36 <a href="http://www.cppblog.com/xosen/articles/78914.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>地形渲染的动态LOD四叉树算法详细实现</title><link>http://www.cppblog.com/xosen/articles/78913.html</link><dc:creator>xosen</dc:creator><author>xosen</author><pubDate>Fri, 03 Apr 2009 18:34:00 GMT</pubDate><guid>http://www.cppblog.com/xosen/articles/78913.html</guid><wfw:comment>http://www.cppblog.com/xosen/comments/78913.html</wfw:comment><comments>http://www.cppblog.com/xosen/articles/78913.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/xosen/comments/commentRss/78913.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/xosen/services/trackbacks/78913.html</trackback:ping><description><![CDATA[<p><span>声明：请将本文档和程序配合使用，旨在使读者费很少的时间和精力就能理解此算法。读者应该熟悉递归程序设计</span><span>,</span><span>以及基本的</span><span>VC OpenGL</span><span>编程</span><span>.</span></p>
<p><span>渲染地形时，如果采用固定地形精度等级，则会随着地形面积的增大，渲染的三角形数量会随着面积的增大而增长。采用动态精度等级，越远处的地形画的三角形面积越大</span><span>(</span><span>精度越低</span><span>)</span><span>，越近的地形面积越小</span><span>(</span><span>精度越高</span><span>)</span><span>，坡度高的地方精度高，平坦的地方精度低，这样能有效地减少画三角形的数量，提高游戏引擎的速度</span><span>.</span></p>
<p><span>采用四叉树算法，可有效把地形不在视野内的网格去掉</span><span>,</span><span>降低程序的时间复杂度</span></p>
<p><span>其基本思路是：先把地形一分为四，用递归的方法对每个网格渲染。对每个网格，如果达到最高精度，则退出；如果不在视野内，也退出。再对符合条件的网格递归下去。</span></p>
<p><span>渲染网格的方法是用</span><span>GL_TRIANGLE_FAN</span><span>方法，如下图</span></p>
<p><span>&lt;!--[if !vml]--&gt;<img height=225 alt="" src="http://dev.gameres.com/Program/Visual/3D/%E5%9C%B0%E5%BD%A2%E6%B8%B2%E6%9F%93%E7%9A%84%E5%8A%A8%E6%80%81LOD%E5%9B%9B%E5%8F%89%E6%A0%91%E7%AE%97%E6%B3%95%E8%AF%A6%E7%BB%86%E5%AE%9E%E7%8E%B0.files/image001.jpg" width=273 border=0 v:shapes="_x0000_s1025">&lt;!--[endif]--&gt;</span></p>
<p><span>但是这样在相邻精度等级相差</span><span>1</span><span>倍或</span><span>1</span><span>倍以上时，渲染的地形会出现裂缝。</span></p>
<p><span>&lt;!--[if !vml]--&gt;<img height=310 alt="" src="http://dev.gameres.com/Program/Visual/3D/%E5%9C%B0%E5%BD%A2%E6%B8%B2%E6%9F%93%E7%9A%84%E5%8A%A8%E6%80%81LOD%E5%9B%9B%E5%8F%89%E6%A0%91%E7%AE%97%E6%B3%95%E8%AF%A6%E7%BB%86%E5%AE%9E%E7%8E%B0.files/image002.jpg" width=349 border=0 v:shapes="_x0000_s1026">&lt;!--[endif]--&gt;</span></p>
<p><span>&lt;!--[if !supportEmptyParas]--&gt;&nbsp;&lt;!--[endif]--&gt;</span></p>
<p><span>解决方法见下图：</span></p>
<p><span>&lt;!--[if !supportEmptyParas]--&gt;&nbsp;&lt;!--[endif]--&gt;</span></p>
<p><span>&lt;!--[if !vml]--&gt;<img height=309 alt="" src="http://dev.gameres.com/Program/Visual/3D/%E5%9C%B0%E5%BD%A2%E6%B8%B2%E6%9F%93%E7%9A%84%E5%8A%A8%E6%80%81LOD%E5%9B%9B%E5%8F%89%E6%A0%91%E7%AE%97%E6%B3%95%E8%AF%A6%E7%BB%86%E5%AE%9E%E7%8E%B0.files/image003.jpg" width=349 border=0 v:shapes="_x0000_s1027">&lt;!--[endif]--&gt;</span></p>
<p><span>这样相邻的网格可以相差任意级精度</span></p>
<p><span>参见下面的截图：看中间的那个大正方形。</span></p>
<p><span>&lt;!--[if !supportEmptyParas]--&gt;&nbsp;&lt;!--[endif]--&gt;</span></p>
<p><span>&lt;!--[if !vml]--&gt;<img height=415 alt="" src="http://dev.gameres.com/Program/Visual/3D/%E5%9C%B0%E5%BD%A2%E6%B8%B2%E6%9F%93%E7%9A%84%E5%8A%A8%E6%80%81LOD%E5%9B%9B%E5%8F%89%E6%A0%91%E7%AE%97%E6%B3%95%E8%AF%A6%E7%BB%86%E5%AE%9E%E7%8E%B0.files/image004.jpg" width=554 border=0 v:shapes="_x0000_s1028">&lt;!--[endif]--&gt;</span></p>
<p><span>&lt;!--[if !supportEmptyParas]--&gt;&nbsp;&lt;!--[endif]--&gt;</span></p>
<p><span>&lt;!--[if !supportEmptyParas]--&gt;&nbsp;&lt;!--[endif]--&gt;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span></p>
<p><span>&lt;!--[if !supportEmptyParas]--&gt;&nbsp;&lt;!--[endif]--&gt;</span></p>
<p><span>程序如下：</span></p>
<p><span>&lt;!--[if !supportEmptyParas]--&gt;&nbsp;&lt;!--[endif]--&gt;</span></p>
<p><span>&lt;!--[if !supportEmptyParas]--&gt;&nbsp;&lt;!--[endif]--&gt;</span></p>
<p><span>&lt;!--[if !supportEmptyParas]--&gt;&nbsp;&lt;!--[endif]--&gt;</span></p>
<p><span>void CTerrain::Render()</span></p>
<p><span>{</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span>&#8230;&#8230;&#8230;</span></p>
<p><span>//</span><span>细分标记数组清</span><span>0</span><span>（这里为了节约内存，一个网格用一个</span><span>bit</span><span>表示）</span></p>
<p><span>m_cFlag.Reset();<span>&nbsp;&nbsp; </span></span></p>
<p><span>//</span><span>在细分标记数组记录需要细分的网格</span></p>
<p><span>Update((m_nMapSize-1)/2,(m_nMapSize-1)/2,(m_nMapSize-1)/2,1);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span><span>根据细分标记数组递归渲染网格</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>RenderQuad((m_nMapSize-1)/2,(m_nMapSize-1)/2,(m_nMapSize-1)/2);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>&#8230;&#8230;.<span>&nbsp;&nbsp;&nbsp;&nbsp; </span></span></p>
<p><span>}</span></p>
<p><span>&lt;!--[if !supportEmptyParas]--&gt;&nbsp;&lt;!--[endif]--&gt;</span></p>
<p><span>//</span><span>渲染网格的递归程序</span></p>
<p><span>//</span><span>参数</span><span>:</span></p>
<p><span>//<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>nXCenter: </span><span>网格中心点坐的</span><span>X</span><span>值</span></p>
<p><span>//<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>nZCenter: </span><span>网格中心点坐的</span><span>Y</span><span>值</span></p>
<p><span>//<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>nSize:</span><span>网格大小</span><span> </span></p>
<p><span>//</span><span>返回</span><span>:</span><span>渲染的三角形数量</span></p>
<p><span>//</span><span>使用前提</span><span>:m_cFlag</span><span>需要细分数组要先调用</span><span>Update</span><span>函数记录好</span><span>.</span></p>
<p><span>int CTerrain::RenderQuad(int nXCenter,int nZCenter,int nSize)</span></p>
<p><span>{</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int nCount = 0;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int nH0 = Height(nXCenter,nZCenter);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int nH1 = Height(nXCenter-nSize,nZCenter-nSize);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int nH2 = Height(nXCenter+nSize,nZCenter-nSize);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int nH3 = Height(nXCenter+nSize,nZCenter+nSize);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int nH4 = Height(nXCenter-nSize,nZCenter+nSize);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span><span>求网格</span><span>5</span><span>个点中高度最大和最小值</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int nMax = nH0,nMin = nH0;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if(nMax&lt;nH1)nMax = nH1;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if(nMax&lt;nH2)nMax = nH2;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if(nMax&lt;nH3)nMax = nH3;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if(nMax&lt;nH4)nMax = nH4;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if(nMin&gt;nH1)nMin = nH1;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if(nMin&gt;nH2)nMin = nH2;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if(nMin&gt;nH3)nMin = nH3;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if(nMin&gt;nH4)nMin = nH4;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>SIZE size;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>size.cx = nSize;size.cy=(nMax-nMin)/2;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span><span>如果此网格不在视野内，返回</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if(!m_pFrustum-&gt;CubeInFrustum(nXCenter,(nMax+nMin)/2,nZCenter,max(size.cx,size.cy)))</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>return 0;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if(m_cFlag.IsTrue(nXCenter,nZCenter))//</span><span>需要细分</span><span>,</span><span>递归渲染</span><span>4</span><span>个子网格</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>{</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>nCount += RenderQuad(nXCenter-nSize/2,nZCenter-nSize/2,nSize/2);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>nCount += RenderQuad(nXCenter+nSize/2,nZCenter-nSize/2,nSize/2);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>nCount += RenderQuad(nXCenter+nSize/2,nZCenter+nSize/2,nSize/2);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>nCount += RenderQuad(nXCenter-nSize/2,nZCenter+nSize/2,nSize/2);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>}</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>else//</span><span>渲染此网格</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>{</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>glBegin( GL_TRIANGLE_FAN );</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>SetTextureCoord(nXCenter,nZCenter);&nbsp;// center</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>glVertex3i(nXCenter, nH0, nZCenter);</span></p>
<p><span>&lt;!--[if !supportEmptyParas]--&gt;&nbsp;&lt;!--[endif]--&gt;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>SetTextureCoord(nXCenter-nSize,nZCenter-nSize); //left top</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>glVertex3i(nXCenter-nSize,nH1 , nZCenter-nSize);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span><span>为了避免出现裂缝而多画三角形扇（就是多加那些红线啦）</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>RemedyTop(nXCenter,nZCenter,nSize);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span></span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>SetTextureCoord(nXCenter+nSize,nZCenter-nSize); //right top</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>glVertex3i(nXCenter+nSize, nH2, nZCenter-nSize);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span><span>为了避免出现裂缝而多画三角形扇（就是多加那些红线啦）</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>RemedyRight(nXCenter,nZCenter,nSize);</span></p>
<p><span>&lt;!--[if !supportEmptyParas]--&gt;&nbsp;&lt;!--[endif]--&gt;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>SetTextureCoord(nXCenter+nSize,nZCenter+nSize); //right bottom</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>glVertex3i(nXCenter+nSize, nH3, nZCenter+nSize);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span><span>为了避免出现裂缝而多画三角形扇（就是多加那些红线啦）</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>RemedyBottom(nXCenter,nZCenter,nSize);</span></p>
<p><span>&lt;!--[if !supportEmptyParas]--&gt;&nbsp;&lt;!--[endif]--&gt;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>SetTextureCoord(nXCenter-nSize,nZCenter+nSize);//left bottom </span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>glVertex3i(nXCenter-nSize, nH4, nZCenter+nSize);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span><span>为了避免出现裂缝而多画三角形扇（就是多加那些红线啦）</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><span>&nbsp;&nbsp; </span>RemedyLeft(nXCenter,nZCenter,nSize);</span></p>
<p><span>&lt;!--[if !supportEmptyParas]--&gt;&nbsp;&lt;!--[endif]--&gt;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>SetTextureCoord(nXCenter-nSize,nZCenter-nSize); //left top</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>glVertex3i(nXCenter-nSize, nH1, nZCenter-nSize);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>glEnd();</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>nCount += 4;//</span><span>这里为求简单，计算渲染的三角形数目并不是十分准确</span><span>,</span><span>比实际要小些</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>}</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>return nCount;</span></p>
<p><span>}</span></p>
<p><span>&lt;!--[if !supportEmptyParas]--&gt;&nbsp;&lt;!--[endif]--&gt;</span></p>
<p><span>&lt;!--[if !supportEmptyParas]--&gt;&nbsp;&lt;!--[endif]--&gt;</span></p>
<p><span>&lt;!--[if !supportEmptyParas]--&gt;&nbsp;&lt;!--[endif]--&gt;</span></p>
<p><span>&lt;!--[if !supportEmptyParas]--&gt;&nbsp;&lt;!--[endif]--&gt;</span></p>
<p><span>//</span><span>更新细分记录数组的递归函数</span></p>
<p><span>//</span><span>参数</span><span>:</span></p>
<p><span>//<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>nXCenter: </span><span>网格中心点坐的</span><span>X</span><span>值</span></p>
<p><span>//<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>nZCenter: </span><span>网格中心点坐的</span><span>Y</span><span>值</span></p>
<p><span>//<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>nSize:</span><span>网格大小</span><span> </span></p>
<p><span>//&nbsp;nLevel:</span><span>当前的细分等级</span><span>(</span><span>其实可由</span><span>nSize</span><span>计算出来</span><span>)<span>&nbsp;&nbsp;&nbsp;&nbsp; </span></span></p>
<p><span>void CTerrain::Update(int nXCenter,int nZCenter,int nSize,int nLevel)</span></p>
<p><span>{</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>CVector3 vPos = m_pCamera-&gt;GetPos();</span></p>
<p><span>//<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int nSize = (m_nMapSize-1)/pow(2,nLevel);//</span><span>由细节级度得到当前渲染矩形的大小</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span><span>求网格</span><span>5</span><span>个点中高度最大和最小值</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int nMax = Height(nXCenter,nZCenter);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int nMin = nMax;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int nH1 = Height(nXCenter-nSize,nZCenter-nSize);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int nH2 = Height(nXCenter+nSize,nZCenter-nSize);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int nH3 = Height(nXCenter+nSize,nZCenter+nSize);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int nH4 = Height(nXCenter-nSize,nZCenter+nSize);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if(nMax&lt;nH1)nMax = nH1;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if(nMax&lt;nH2)nMax = nH2;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if(nMax&lt;nH3)nMax = nH3;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if(nMax&lt;nH4)nMax = nH4;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if(nMin&gt;nH1)nMin = nH1;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if(nMin&gt;nH2)nMin = nH2;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if(nMin&gt;nH3)nMin = nH3;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if(nMin&gt;nH4)nMin = nH4;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span><span>网格的中心点坐标</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>CVector3 vDst(nXCenter,(nMax+nMin)/2,nZCenter);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>SIZE size;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>size.cx = nSize;size.cy=(nMax-nMin)/2;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span><span>如果此网格不在视野内，返回</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if(!m_pFrustum-&gt;CubeInFrustum(nXCenter,(nMax+nMin)/2,nZCenter,max(size.cx,size.cy)))</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>return ;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span><span>视点到网格中心点的距离</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int nDist = Dist(vPos,vDst);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if(nDist&gt;2000)//</span><span>距离大于</span><span>2000</span><span>时才将距离和误差</span><span>(</span><span>坡度</span><span>)2</span><span>个因素考虑</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>{</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span><span>与距离成正比</span><span>,</span><span>与误差</span><span>(</span><span>坡度</span><span>)</span><span>成反比</span><span>,</span><span>还与当前细分等级有关</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if(70.0*Dist(vPos,vDst)/nSize/CalcError(nXCenter,nZCenter,nSize)&gt;nLevel)</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>return;//</span><span>此网格不需要细分</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>}</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>else//</span><span>近距离</span><span>(&lt;2000)</span><span>时只考虑误差</span><span>(</span><span>坡度</span><span>)</span><span>因素</span><span>(</span><span>因为动态</span><span>LOD</span><span>会造成地表呼吸现象</span><span>,</span><span>为减弱此现象不考虑距离因素</span><span>)</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>{</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if(CalcError(nXCenter,nZCenter,nSize)&lt;30)</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>return;//</span><span>此网格不需要细分</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>}</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if(nSize!=MIN_GRID)</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>{<span> </span>////</span><span>此网格需要细分</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>m_cFlag.Set(nXCenter,nZCenter,true);//</span><span>把细分数组相应位设为</span><span>true</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span><span>递归</span><span>4</span><span>个子网格</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>Update(nXCenter-nSize/2,nZCenter-nSize/2,nSize/2,nLevel+1);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>Update(nXCenter+nSize/2,nZCenter-nSize/2,nSize/2,nLevel+1);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>Update(nXCenter+nSize/2,nZCenter+nSize/2,nSize/2,nLevel+1);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>Update(nXCenter-nSize/2,nZCenter+nSize/2,nSize/2,nLevel+1);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>}</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span><span>此网格不需要细分</span></p>
<p><span>}</span></p>
<p><span>&lt;!--[if !supportEmptyParas]--&gt;&nbsp;&lt;!--[endif]--&gt;</span></p>
<p><span>//</span><span>计算误差</span><span>(</span><span>坡度</span><span>)</span><span>的函数</span></p>
<p><span>//</span><span>参数</span><span>:</span></p>
<p><span>//<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>nXCenter: </span><span>网格中心点坐的</span><span>X</span><span>值</span></p>
<p><span>//<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>nZCenter: </span><span>网格中心点坐的</span><span>Y</span><span>值</span></p>
<p><span>//<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>nSize:</span><span>网格大小</span><span> </span></p>
<p><span>//</span><span>返回</span><span>: </span><span>误差大小</span></p>
<p><span>//</span><span>说明</span><span>:</span><span>只是计算某些点细分与不细分的高度值差的和</span></p>
<p><span>inline int CTerrain::CalcError(int nXCenter,int nZCenter,int nSize)</span></p>
<p><span>{</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int nError = 0;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int nH0 = Height(nXCenter,nZCenter);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int nH1 = Height(nXCenter-nSize,nZCenter-nSize);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int nH2 = Height(nXCenter+nSize,nZCenter-nSize);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int nH3 = Height(nXCenter+nSize,nZCenter+nSize);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>int nH4 = Height(nXCenter-nSize,nZCenter+nSize);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>nError += abs(Height(nXCenter,nZCenter-nSize)-(nH1+nH2)/2);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>nError += abs(Height(nXCenter+nSize,nZCenter)-(nH2+nH3)/2);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>nError += abs(Height(nXCenter,nZCenter+nSize)-(nH3+nH4)/2);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>nError += abs(Height(nXCenter-nSize,nZCenter)-(nH4+nH1)/2);</span></p>
<p><span>&lt;!--[if !supportEmptyParas]--&gt;&nbsp;&lt;!--[endif]--&gt;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>nError +=abs(Height(nXCenter-nSize/2,nZCenter-nSize/2)-(nH0+nH1)/2);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>nError +=abs(Height(nXCenter+nSize/2,nZCenter-nSize/2)-(nH0+nH2)/2);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>nError +=abs(Height(nXCenter+nSize/2,nZCenter+nSize/2)-(nH0+nH3)/2);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>nError +=abs(Height(nXCenter-nSize/2,nZCenter+nSize/2)-(nH0+nH4)/2);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>return nError;</span></p>
<p><span>}</span></p>
<p><span>如下图</span><span>:</span></p>
<p><span>&lt;!--[if !vml]--&gt;<img height=279 alt="" src="http://dev.gameres.com/Program/Visual/3D/%E5%9C%B0%E5%BD%A2%E6%B8%B2%E6%9F%93%E7%9A%84%E5%8A%A8%E6%80%81LOD%E5%9B%9B%E5%8F%89%E6%A0%91%E7%AE%97%E6%B3%95%E8%AF%A6%E7%BB%86%E5%AE%9E%E7%8E%B0.files/image005.jpg" width=321 border=0 v:shapes="_x0000_s1029">&lt;!--[endif]--&gt;</span></p>
<p><span>当不细分时红点的高度值是它所在线段的两个端点的平均值</span><span>,</span><span>细分时就是它实际的高度值</span><span>,</span><span>这</span><span>2</span><span>个值是不一样的</span><span>. </span><span>两者这间的高度差就是误差</span><span>,</span><span>把所有红点的误差相加就是此网格的误差</span></p>
<p><span>&lt;!--[if !supportEmptyParas]--&gt;&nbsp;&lt;!--[endif]--&gt;</span></p>
<p><span>//</span><span>修补网格顶部裂缝的函数</span></p>
<p><span>//</span><span>参数</span><span>:</span></p>
<p><span>//<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>nXCenter: </span><span>网格中心点坐的</span><span>X</span><span>值</span></p>
<p><span>//<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>nZCenter: </span><span>网格中心点坐的</span><span>Y</span><span>值</span></p>
<p><span>//<span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>nSize:</span><span>网格大小</span><span> </span></p>
<p><span>void CTerrain::RemedyTop(int nXCenter,int nZCenter,int nSize)</span></p>
<p><span>{</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if(nZCenter-2*nSize&gt;=0)//</span><span>保证寻址不越界</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>{</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>if(!m_cFlag.IsTrue(nXCenter,nZCenter-2*nSize))//</span><span>此网格相邻的顶部网格</span><span>(</span><span>同级的</span><span>)</span><span>没有细分</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>return;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>}</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>else</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>return;</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span><span>此网格相邻的顶部网格</span><span>(</span><span>同级的</span><span>)</span><span>已经细分</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>//</span><span>对顶部一分为二递归</span><span>(</span><span>跨级精度的情况</span><span>)</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>RemedyTop(nXCenter-nSize/2,nZCenter-nSize/2,nSize/2);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>SetTextureCoord(nXCenter,nZCenter-nSize); </span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>glVertex3i(nXCenter,Height(nXCenter,nZCenter-nSize),nZCenter-nSize);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span>RemedyTop(nXCenter+nSize/2,nZCenter-nSize/2,nSize/2);</span></p>
<p><span>}</span></p>
<p><span>其它的修补裂缝的函数也是一样的流程</span><span>.</span></p>
<p><span>&lt;!--[if !supportEmptyParas]--&gt;&nbsp;&lt;!--[endif]--&gt;</span></p>
<p><span>&lt;!--[if !supportEmptyParas]--&gt;&nbsp;&lt;!--[endif]--&gt;</span></p>
<p><span>&lt;!--[if !supportEmptyParas]--&gt;&nbsp;&lt;!--[endif]--&gt;</span></p>
<p><span>近处没有考虑距离因素</span><span>,</span><span>只考虑误差</span><span>(</span><span>坡度</span><span>)</span><span>因素</span></p>
<p><span>&lt;!--[if !vml]--&gt;<img height=415 alt="" src="http://dev.gameres.com/Program/Visual/3D/%E5%9C%B0%E5%BD%A2%E6%B8%B2%E6%9F%93%E7%9A%84%E5%8A%A8%E6%80%81LOD%E5%9B%9B%E5%8F%89%E6%A0%91%E7%AE%97%E6%B3%95%E8%AF%A6%E7%BB%86%E5%AE%9E%E7%8E%B0.files/image006.jpg" width=554 border=0 v:shapes="_x0000_s1030">&lt;!--[endif]--&gt;</span></p>
<p><span>&lt;!--[if !supportEmptyParas]--&gt;&nbsp;&lt;!--[endif]--&gt;</span></p>
<p><span>距离越运</span><span>,</span><span>精度越低</span></p>
<p><span>&lt;!--[if !vml]--&gt;<img height=415 alt="" src="http://dev.gameres.com/Program/Visual/3D/%E5%9C%B0%E5%BD%A2%E6%B8%B2%E6%9F%93%E7%9A%84%E5%8A%A8%E6%80%81LOD%E5%9B%9B%E5%8F%89%E6%A0%91%E7%AE%97%E6%B3%95%E8%AF%A6%E7%BB%86%E5%AE%9E%E7%8E%B0.files/image007.jpg" width=554 border=0 v:shapes="_x0000_s1031">&lt;!--[endif]--&gt;</span></p>
<p><span>&lt;!--[if !supportEmptyParas]--&gt;&nbsp;&lt;!--[endif]--&gt;</span></p>
<p><span>感谢你的阅读</span><span>,</span><span>如果你有什么更好的思路或更好的算法</span><span>(</span><span>比如能消除地表呼吸现象</span><span>),</span><span>请和我联系</span><span>:JerryLi@sina.com</span></p>
&nbsp;
<img src ="http://www.cppblog.com/xosen/aggbug/78913.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/xosen/" target="_blank">xosen</a> 2009-04-04 02:34 <a href="http://www.cppblog.com/xosen/articles/78913.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>简单快速的地形LOD连续变形</title><link>http://www.cppblog.com/xosen/articles/78912.html</link><dc:creator>xosen</dc:creator><author>xosen</author><pubDate>Fri, 03 Apr 2009 18:33:00 GMT</pubDate><guid>http://www.cppblog.com/xosen/articles/78912.html</guid><wfw:comment>http://www.cppblog.com/xosen/comments/78912.html</wfw:comment><comments>http://www.cppblog.com/xosen/articles/78912.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/xosen/comments/commentRss/78912.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/xosen/services/trackbacks/78912.html</trackback:ping><description><![CDATA[在地形渲染中为了减轻渲染负担其中一个技术是就LOD，但其带了的一个问题就是会产生地形&#8220;跳动&#8221;，因为地形的LOD精度发生变化时镶嵌的密度也发生了 变化。要解决这个问题有一个方法就是当高精的模型向底精模型变化时不是突然变化过去的，而是慢慢渐变而来，这样可以极大的减少&#8220;跳动&#8221;。通过设置适当的渐 变距离可以做到让观察者感觉不到地形的变化。
<p>&nbsp;</p>
<p>　　关于这个问题在一篇文章里面也有介绍：Terrain Geomorphing in the Vertex Shader[1]，但是原文介绍的方法过于复杂而且不适合用硬件加速，但是这仍然是一篇学习地形渲染的好文章。下面提出一种更加简单，快速的方法来解决 这个问题。使用这个方法有一个前提条件就是地形块之间的LOD级别相差不能大于1.</p>
<p>　　解决这个问题最基本的思想就是：对高精度的地形块进行形变来模拟低精度的地形块， 当高精地形和低精地形的形态完全一样时，地形块的LOD才变为低精度的。设置高精度地形块的高度为H1， 低精度的地形块的高度为H2，最后根据渐变距离求出一个插值t[0， 1]，最后地形的高度就为H =&nbsp; lerp（H1， H2， t）。</p>
<p>　　为了加速渲染顶点形变的过程由vs去计算。设置地形的最大LOD级别为5，也就说有4种形变可能：0-&gt;1， 1-&gt;2， 2-&gt;3， 3-&gt;4.这样我们可以把四种形变的高度值存到一个float4（yMove）中。</p>
<p>　　对于如何求取低精度地形的高度值可以这样计算：先求出顶点是属于那一个地形四方面，然后求出其在四方面上的uv坐标，最后用二次线性插值来得到 四方面内任意一点的高度值。比如：一个地形四方面这四个高度值分别为h00， h10， h01， h11.设u为四方面x方向的坐标[0，1]， v为四方面y方向的坐标[0，1].那么最后的高度值：</p>
<p>　　h（u， v） = （1.0f-v） * （（1.0f-u）*h00 + u*h10） + v * （ （1.0f-u）*h01 + u*h11 ）</p>
<p>　　在计算好的形变的高度值后就可以在vs内计算形变高度值了，在计算之前还需要传入两个参数：</p>
<p>　　float4 MorphStart， MorphInvRange.其中MorphMin表示形变的起始距离，比如（50， 100， 150， 200）</p>
<p>　　就表示地形在50处开始0-&gt;1形变，100处1-&gt;2形变， 150处2-&gt;3， 依此类推。MorphInvRange是形变距离范围的倒数，比如上例：MoprhInvRange = （1/50， 1/50， 1/50， 1/50）。</p>
<p>　　在vs中计算高度值的代码如下：</p>
<p>　　void vsMain（&nbsp; in float3 Pos&nbsp;&nbsp; ： POSITION0，</p>
<p>　　in float4 yMove ： POSITION1 ）</p>
<p>　　{</p>
<p>　　float viewDist = length（Eye - Pos ）；</p>
<p>　　float4 moprh&nbsp;&nbsp; = saturate（ （viewDist-MorphStart）*MorphInvRange ）；</p>
<p>　　Pos.y&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; += dot（ yMove， morph ）；</p>
<p>　　}</p>
<p>　　根据每个顶点对眼睛的距离来决定最后的形变值，可以保证只要顶点位置相同最后形变后的位置也一定相同，避开了文献[1]中地形边界接缝等等麻烦 的问题。注意在vs中计算morph为什么要用saturate，是因为顶点缓冲中的Pos.y是最高精度地形的高度值，比如要从1-&gt;2级，除了 要加上1-&gt;2的形变高度值外，还要加上0-&gt;1的高度差值， 而且2-&gt;3， 3-&gt;4的形变高度值应该为0.上面这个式子正好能满足上面要求的情况。</p>
<p>　　处理完顶点形变后，还有一个工作要做就是要根据眼睛与地形块的最小距离来求地形块的LOD，设置眼睛与地形块的最小距离为d，那么求地形lod的代码如下：</p>
<p>　　if （ d &lt; MorphStart.y ）&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 0-&gt;1</p>
<p>　　lod = 0；</p>
<p>　　else if （ d &lt; MorphStart.z ） // 1-&gt;2</p>
<p>　　lod = 1；</p>
<p>　　else if （ d &lt; MorphStart.w ） // 2-&gt;3</p>
<p>　　lod = 2；</p>
<p>　　else if （ d &lt; MorphStart.w + 50 ） // 3-&gt;4 （50为最后一级形变的距离）</p>
<p>　　lod = 3；</p>
<p>　　else</p>
<p>　　lod = 4；</p>
<p>　　现在似乎可以做到消除地形&#8220;跳动&#8221;的现像了，但是有一点还需要注意那就是光照。上面做的工作只是消除了几何形变产生的&#8220;跳动&#8221;，还有一个产生 &#8220;跳动&#8221;感觉的是光照颜色，如果你是在vs内计算的顶点光照那么还是会有&#8220;跳动&#8221;，因为顶点的密度变了光照的效果肯定不一样。要解决这个问题就需要用光照 图来代替顶点光照，但是这样就限制了地形的光照是静态的。如果你还是想要动态的地形光照，那就需要用地形的法向图在ps内动态计算，注意：法向图的边界的 地方要特殊处理。</p>
&nbsp;
<img src ="http://www.cppblog.com/xosen/aggbug/78912.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/xosen/" target="_blank">xosen</a> 2009-04-04 02:33 <a href="http://www.cppblog.com/xosen/articles/78912.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一种简单、快速、高效的多边形减面算法</title><link>http://www.cppblog.com/xosen/articles/78911.html</link><dc:creator>xosen</dc:creator><author>xosen</author><pubDate>Fri, 03 Apr 2009 18:31:00 GMT</pubDate><guid>http://www.cppblog.com/xosen/articles/78911.html</guid><wfw:comment>http://www.cppblog.com/xosen/comments/78911.html</wfw:comment><comments>http://www.cppblog.com/xosen/articles/78911.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/xosen/comments/commentRss/78911.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/xosen/services/trackbacks/78911.html</trackback:ping><description><![CDATA[如果你是一个游戏开发者，那么3D 多边形模型已经成为你日常生活中的一部分，并且你一定对一些3D概念例如每秒多边形数量、低面模型以及细节层次等等非常熟悉了。你可能也同样知道多边形减面算法的目的在于通过一个有着大量多边形的高细节的模型生成一个多边形数量比它少、但是看起来却跟原模型很相像的低面模型。这篇文章解释了一种实现自动减面的方法，并且附带的讨论了多边形减面的有用之处。在我们开始之前，我建议你去下载我的一个程序：BUNNYLOD.EXE，它展示了我将要阐述的这项技术。你可以在Game Develop网站上找到它。
<ul>
    <li>
    <h4 style="TEXT-ALIGN: left"><strong>问题的由来</strong></h4>
    </li>
</ul>
<p style="TEXT-ALIGN: left">　　在深入这个很&#8220;牛Ｘ&#8221;的3D算法之前，你可能会问你自己真的有必要关注它吗？要知道，已经有一些商业的插件和工具来为你减少多边形数量了。<br>　　然而，下面的几条理由会告诉你为什么需要实现自己的减面算法：</p>
<ul>
    <li>
    <div style="TEXT-ALIGN: left">你使用的多边形减面工具生成的结果无法满足你的特殊需求，因此你希望做一个自己的工具。</div>
    <li>
    <div style="TEXT-ALIGN: left">你当前使用的多边形减面工具可能无法产生减面过程中的变化信息，而你却希望利用这些变化信息来使不同的细节层次之间的转换更加平滑。</div>
    <li>
    <div style="TEXT-ALIGN: left">你希望将生产过程自动化，这样的话美术人员就仅仅需要创建一个细节适当的模型，然后游戏引擎就能自动创建模型其余的细节层次。</div>
    <li>
    <div style="TEXT-ALIGN: left">你正在制作一个VRML浏览器，你希望提供一个菜单项来简化那些巨大的VRML文件。那些把这些巨大的文件放到网上的超级计算机用户没有想到这些文件在普通家用电脑上显示帧速率会比较低。</div>
    <li>
    <div style="TEXT-ALIGN: left">你在你的游戏中使用的特效改变了物体的几何形状，增加了多边形的数量，你需要一个方法来使你的引擎能够实时地快速减少多边形数量。</div>
    </li>
</ul>
<p style="TEXT-ALIGN: left">　　你对此怀疑？图1展示了一个具体的实例，一个游戏引擎对减少多边形这种特性的需求。</p>
<p class=separator style="CLEAR: both; TEXT-ALIGN: center"><img style="cssFloat: " height=274 alt="" src="http://dev.gameres.com/Program/Visual/3D/BMP3-large.jpg" width=420><br>图1 爆炸效果对多边形数量的影响</p>
<p style="TEXT-ALIGN: left"><span lang=EN-US style="FONT-SIZE: 10.5pt; FONT-FAMILY: Tahoma">　　在Bioware，我实现了实时的爆炸效果，并且把它们应用在了我们开发的一个游戏原型上，以便给我们的出版商留下深刻印象。玩家可以射击和爆破他们瞄准的实心物体表面的任意块。通过子弹撞击而改变游戏环境比典型的&#8220;定点爆破&#8221;这种只能在游戏世界中改变预先设定项的技术更棒。遗憾的是，重复不断的使用爆破效果会在物体上产生大量附加的三角形，如同你在图1中看到的一样。许多添加的面是很小的或者是碎片，不会对游戏的视觉效果产生丝毫的影响——它们仅仅是让游戏更慢。这种情况下就要求有实时的多边形减面功能，所以我开始寻找一种能够高效地完成这项工作的算法。</span></p>
<ul>
    <li>
    <h4 style="TEXT-ALIGN: left"><strong>坍塌边</strong></h4>
    </li>
</ul>
<p style="TEXT-ALIGN: left">　　在我着手处理这个问题之前，我跟亚伯达大学图形实验室的一些人学习了多边形减面。（它让我跟一个团队一起工作，从而弄明白这个非常难的算法是如何工作的，并且弄明白什么样的技术适用于什么样的任务。）最近这个领域出现了很多研究成果，但其中大多数比较好的技术都是 H.Hpppe 的渐进网格算法的改进和变形（参见&#8220;更多的信息&#8221;）。那些技术都是通过重复不断的使用一个简单的边坍塌操作来降低模型的复杂度，见图2。</p>
<p class=separator style="CLEAR: both; TEXT-ALIGN: center"><img style="cssFloat: " height=111 alt="" src="http://dev.gameres.com/Program/Visual/3D/BMP4-medium.jpg" width=200><br>图2 边塌陷</p>
<p class=separator style="CLEAR: both; TEXT-ALIGN: left">　　在这个操作里面，u和v两个顶点（边uv）被选中并且其中一个顶点（这里是u）&#8220;移动&#8221;或者说&#8220;坍塌&#8221;到另一个顶点（这个例子里是v）。下面这些步骤说明如何实现这个操作：<br>1. 去除所有既包含顶点u又包含顶点v的三角形（换一种说法，去除所有以uv为边的三角形）。<br>2. 更新所有剩下的三角形，把所有用到顶点u的地方都用顶点v代替。<br>3. 移除顶点u。<br>重复以上的过程，直到多边形的数量达到了预期数量。每一次重复的过程中，通常会移除一个顶点、两个面、三条边。图3展示了一个简单的例子。</p>
<p class=separator style="CLEAR: both; TEXT-ALIGN: center"><img style="cssFloat: " height=127 alt="" src="http://dev.gameres.com/Program/Visual/3D/BMP6-large.jpg" width=420><br>图3 多边形经过一系列边坍塌之后减少了面数</p>
<ul>
    <li>
    <h4 style="TEXT-ALIGN: left"><strong>选择下一条边进行坍塌</strong></h4>
    </li>
</ul>
<p style="TEXT-ALIGN: left">　　要产生效果比较好的底面模型的诀窍在于要正确地选择坍塌的边，能够在坍塌的时候最小程度的影响模型的视觉变化。研究者提出了各种各样的方法来使在每一次坍塌的时候能够选择出&#8220;最小影响&#8221;的边。但遗憾的是，最好的那种方法非常非常复杂（也就是说，很难实现），并且要花大量时间用于运算。因此这推动我要找到一种能够在游戏运行阶段减少多边形面数的方法，我做了很多实验，最后终于为这个选择边的过程开发了一种简单又超快的方法来生成相当不错的低面模型。</p>
<p style="TEXT-ALIGN: left">　　显然，先要去除那些小细节。同时要注意的是，对于那些在同一平面上的表面，只需要很少的多边形就可以表示，同时高度弯曲的曲面则需要更多的多边形来表示。根据以上这些，我们定义了：一条边是否要坍塌，取决于它的边长与曲率值的乘积。为了找到在uv方向上距离别的三角形最远的u的临接三角形，我们通过比较两个面的法线的点积得到坍塌边uv的曲率值。方程式1展现了用更多正式符号表示的求边坍塌值的公式。详见源码（你可以在Game Developer网站上下载到源代码）。</p>
<p class=separator style="CLEAR: both; TEXT-ALIGN: center"><img style="cssFloat: " height=51 alt="" src="http://dev.gameres.com/Program/Visual/3D/BMP7-large.jpg" width=420><br>Tu是包含顶点u的三角形的集合，Tuv是同时包含顶点u和顶点v的三角形的集合。<br>方程式1 求边坍塌值的方程式</p>
<p style="TEXT-ALIGN: left">　　你可以看到，这个算法在决定哪一条边坍塌的时候对于面的曲率和大小做了平衡。要注意的是顶点u到v的坍塌值不一定和顶点v到u的坍塌值相同。此外，这个公式对于脊状的边的坍塌也是有效的。即使这条脊有可能是一个锐角，或者是直角，都没有关系。图4举例说明了这种情况。非常明显的，在平面区域中间的顶点B，可以被坍塌到顶点A或者顶点C。角上的顶点C应该最后被保留下来。如果把上面的顶点A坍塌到内部的顶点B，那就会非常糟糕。不过，顶点A可以沿着脊坍塌到顶点C，这丝毫不会影响这个模型的外观。</p>
<p class=separator style="CLEAR: both; TEXT-ALIGN: center"><img style="cssFloat: " height=327 alt="" src="http://dev.gameres.com/Program/Visual/3D/BMP12-large.jpg" width=420><br>图４　好的和差的边坍塌</p>
<p style="TEXT-ALIGN: left">　　如果你正在实作你自己的减面算法，你可能希望能够用这个公式做实验，来看看是否满足你的要求。例如，对于一个动画模型，你可能希望能够改进公式，使它能够在判断潜在的坍塌边的时候可以参考不止一个动画关键帧的数据。如果对于你来说，模型质量比减面算法所需要的执行时间更重要的话，你应该考虑使用Hoppe的函数。我们已经添加了很多扩展用来处理贴图坐标、顶点法线、邻接边，以及表面断裂（比如贴图接缝）。</p>
<ul>
    <li>
    <h4 style="TEXT-ALIGN: left"><strong>结果</strong></h4>
    </li>
</ul>
<p style="TEXT-ALIGN: left">　　先显示一个原来的模型，然后显示简化后的模型，这是对多边形减面算法效果的最好证明。大多数的研究论文都用非常高面高细节的模型减面来证明它们的效果，原始模型接近100,000个多边形，简化后的模型只有10,000个多边形。对于3D游戏来说，更恰当（并且跟有挑战性）的测试是生成一个只有几百个多边形的模型，以此展示算法的强大威力。</p>
<p class=separator style="CLEAR: both; TEXT-ALIGN: center"><img style="cssFloat: " height=160 alt="" src="http://dev.gameres.com/Program/Visual/3D/BMP8-large.jpg" width=420><br>图5 453个、200个以及100个顶点的小兔子模型（从左到右）</p>
<p class=separator style="CLEAR: both; TEXT-ALIGN: center"><img style="cssFloat: " height=170 alt="" src="http://dev.gameres.com/Program/Visual/3D/BMP9-medium.jpg" width=200><br>图6 随机选择坍塌边（200个顶点）</p>
<p style="BORDER-RIGHT: medium none; BORDER-TOP: medium none; BORDER-LEFT: medium none; BORDER-BOTTOM: medium none; TEXT-ALIGN: left">　　举个例子，图5展示了一个小兔子的模型，它是从一个由 Viewpoint Datalabs 制作的VRML文件中提取出来的。模型的最初版本（左边）包含有453个顶点和902个多边形。后边显示的是减少到200个顶点（中间）和100个顶点（右边）的模型。希望你能够对图中不同数量多边形模型的视觉外观看起来感到满意。图6展示了由于没有选择出正确的坍塌边而简化的模型，这里坍塌边的选择是随机的。<br>　　</p>
<p class=separator style="CLEAR: both; TEXT-ALIGN: center"><img style="cssFloat: " height=197 alt="" src="http://dev.gameres.com/Program/Visual/3D/BMP10-large.jpg" width=420><br>图7 一个女性的人物模型，左边100%多边形数量，中间20%多边形数量，右边是4%多边形数量</p>
<p style="BORDER-RIGHT: medium none; BORDER-TOP: medium none; BORDER-LEFT: medium none; BORDER-BOTTOM: medium none; TEXT-ALIGN: left">&nbsp;当我们完成了动物实验之后，就要开始把这种算法应用在人物模型上了。图7展示了一个Bioware制作的女性人物模型的三个版本——4,858;1,000以及200个顶点。（根据欧拉公式，我们知道多边形的数量大致为顶点数的两倍。）这些模型图片是用平坦的方式渲染的，你能够明显的看到模型之间的不同之处。当我们使用平滑的方式渲染并且应用上贴图的话，那么这些差别就不会那么明显了。<br>　</p>
<ul>
    <li>
    <h4 style="TEXT-ALIGN: left"><strong>实际应用</strong></h4>
    </li>
</ul>
<p style="TEXT-ALIGN: left">　　我们最初的目标比较简单：我们想要找到一种方法可以减少由于过多的爆破特效造成的过多的多边形。但是，经过开发这个多边形减面算法并且在人物模型上得出的比预期好的结果，我们觉得这个技术完全可以用于在游戏引擎中生成模型的细节层次(LOD)。预计这个在基本算法基础上改进的新的版本可以整合进Bioware的3D引擎中。现在，我们的美术人员只需要为每一个游戏中的物体创建一个细致的模型就可以了。一个预处理的过程就可以为模型减面。然后，如果游戏每秒的帧速率低于预定的限度，或者游戏中的一个物体离摄像机相当远的时候，我们就可以拿一个低面的模型来代替高细节的模型。可以在游戏运行期间来做这些事情从而增加游戏的可伸缩性。游戏可以根据当前运行系统的马力来调整这些东西。<br>　</p>
<ul>
    <li>
    <h4 style="TEXT-ALIGN: left"><strong>实现细节</strong></h4>
    </li>
</ul>
<p style="TEXT-ALIGN: left">　　这种算法仅仅能够运用于三角形。如果需要的话可以把其它更多边的多边形简单地分解为三角形，除了这点就没有别的限制了。事实上，许多应用只用三角形。</p>
<p style="TEXT-ALIGN: left">　　大多数储存多边形物体的数据结构都是用一组顶点数据和一组三角形数据组成，其中三角形数据中包含了指向顶点数据的顶点索引数据。比如说：<br>Vector vertices[];<br>class Triangle {<br>　int v[3]; // indices into vertex list<br>} triangles[];<br>&nbsp;VRML中使用的索引面集合节点数据是这种数据结构的另一个例子。当一个物体中的两个三角形有相同的顶点的时候，它们有相同的索引值（因此它们共享顶点列表中的相同的顶点）。</p>
<p style="TEXT-ALIGN: left"><span lang=EN-US style="FONT-SIZE: 10.5pt; FONT-FAMILY: Times New Roman">&nbsp; </span><font face=Arial size=3>　 </font></p>
<p>
<table id=table5 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td style="FONT-SIZE: 76%; BORDER-LEFT-COLOR: #d4d0c8; BORDER-BOTTOM-COLOR: #d4d0c8; BORDER-TOP-COLOR: #d4d0c8; BACKGROUND-COLOR: transparent; BORDER-RIGHT-COLOR: #d4d0c8">
            <div>
            <p style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="COLOR: black; FONT-FAMILY: Courier New"><font size=2>class Triangle {</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="COLOR: black; FONT-FAMILY: Courier New"><font size=2>public:</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="COLOR: black; FONT-FAMILY: Courier New"><font size=2>Vertex * vertex[3];// the 3 points that make this tri</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="COLOR: black; FONT-FAMILY: Courier New"><font size=2>Vector normal; // orthogonal unit vector</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="COLOR: black; FONT-FAMILY: Courier New"><font size=2>Triangle(Vertex *v0,Vertex *v1,Vertex *v2);</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="COLOR: black; FONT-FAMILY: Courier New"><font size=2>~Triangle();</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="COLOR: black; FONT-FAMILY: Courier New"><font size=2>void ComputeNormal();</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="COLOR: black; FONT-FAMILY: Courier New"><font size=2>void ReplaceVertex(Vertex *vold,Vertex *vnew);</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="COLOR: black; FONT-FAMILY: Courier New"><font size=2>int HasVertex(Vertex *v);</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="COLOR: black; FONT-FAMILY: Courier New"><font size=2>};</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="COLOR: black; FONT-FAMILY: Courier New"><font size=2>class Vertex {</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="COLOR: black; FONT-FAMILY: Courier New"><font size=2>public:</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="COLOR: black; FONT-FAMILY: Courier New"><font size=2>Vector position; // location of this point</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="COLOR: black; FONT-FAMILY: Courier New"><font size=2>int id; // place of vertex in original list</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="COLOR: black; FONT-FAMILY: Courier New"><font size=2>List&lt;Vertex *&gt; neighbor; // adjacent vertices</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="COLOR: black; FONT-FAMILY: Courier New"><font size=2>List&lt;Triangle *&gt; face; // adjacent triangles</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="COLOR: black; FONT-FAMILY: Courier New"><font size=2>float cost; // cached cost of collapsing edge</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="COLOR: black; FONT-FAMILY: Courier New"><font size=2>Vertex * collapse; // candidate vertex for collapse</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="COLOR: black; FONT-FAMILY: Courier New"><font size=2>Vertex(Vector v,int _id);</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="COLOR: black; FONT-FAMILY: Courier New"><font size=2>~Vertex();</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="COLOR: black; FONT-FAMILY: Courier New"><font size=2>void RemoveIfNonNeighbor(Vertex *n);</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="COLOR: black; FONT-FAMILY: Courier New"><font size=2>};</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="COLOR: black; FONT-FAMILY: Courier New"><font size=2>List&lt;Vertex *&gt; vertices;</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="COLOR: black; FONT-FAMILY: Courier New"><font size=2>List&lt;Triangle *&gt; triangles;</font></span></strong></p>
            </div>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p>&nbsp;</p>
<p style="TEXT-ALIGN: left">程序清单1 扩展后的数据结构</p>
<p style="TEXT-ALIGN: left">　　我们根据我们的多边形减面算法的需要对这个数据结构进行了添加。一个主要的改进是我们现在需要访问的信息已经不仅仅是每个三角形使用哪些顶点——我们同样要知道每个顶点被哪些三角形使用。此外，我们应该可以直接访问每一个顶点的邻接顶点（也就是边）。程序清单1展示了添加后的数据结构。<br>　　<br>　　<span lang=EN-US style="FONT-SIZE: 10.5pt; FONT-FAMILY: Times New Roman"> </span><br><font face=Arial size=3>程序清单2 坍塌值的确定以及进行边的坍塌操作<br>　　　<br>　　成员函数 ReplaceVertex() 在多边形减面的过程中被用来处理边坍塌。数据结构中的顶点、三角形的添加、删除、或者替换必须保持正确，构造函数、析构函数以及另外的成员函数保证了这个过程的正确性。我们保存了面法线，因为它们在边选择的方程运算中被大量地用到。为了避免每次重新运算，我们还把每个顶点选择最优坍塌边以及坍塌值记录了下来。因为那些成员函数的实现是非常直观的，因此我没有将它们包含到这篇文章里面。如果你感兴趣，就去Game Developer网站上找到这个算法的源代码，然后简单的找一下就可以了。程序清单2包含了坍塌值的计算代码和进行边坍塌操作的代码。<br>　　　　<br>　　有了这几个函数之后，多边形减面的操作就变得很简单了。先初始化物体的顶点和三角形数据，然后按照下面这样做：<br>while(vertices.num &gt; desired) {<br>　　Vertex *mn = MinimumCostEdge();<br>　　Collapse(mn,mn-&gt;collapse);<br>}<br>　　在BUNNYLOD.EXE这个演示中，没有使用这么简单的循环。它还为了动画创建了一个附加的数据结构。　 </font></p>
<p>
<table id=table6 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td style="FONT-SIZE: 76%; BORDER-LEFT-COLOR: #d4d0c8; BORDER-BOTTOM-COLOR: #d4d0c8; BORDER-TOP-COLOR: #d4d0c8; BACKGROUND-COLOR: transparent; BORDER-RIGHT-COLOR: #d4d0c8">
            <div>
            <p style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>float ComputeEdgeCollapseCost(Vertex *u,Vertex *v) {</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>// if we collapse edge uv by moving u to v then how</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>// much different will the model change, i.e. the &#8220;error&#8221;.</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>float edgelength = magnitude(v-&gt;position - u-&gt;position);</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>float curvature=0;</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>// find the &#8220;sides&#8221; triangles that are on the edge uv</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>List&lt;Triangle *&gt; sides;</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>for(i=0;i&lt;u-&gt;face.num;i++) {</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 42pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>if(u-&gt;face[i]-&gt;HasVertex(v)){</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 42pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>sides.Add(u-&gt;face[i]);</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-INDENT: 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>}</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>}</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>// use the triangle facing most away from the sides</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>// to determine our curvature term</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>for(i=0;i&lt;u-&gt;face.num;i++) {</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 42pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>float mincurv=1;</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 42pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>for(int j=0;j &lt; sides.num;j++) {</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 63pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>// use dot product of face normals.</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 63pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>float dotprod = u-&gt;face[i]-&gt;normal ^ sides[j]-&gt;normal;</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 63pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>mincurv = min(mincurv,(1-dotprod)/2.0f);</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-INDENT: 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>}</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-INDENT: 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>curvature = max(curvature,mincurv);</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>}</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>return edgelength * curvature;</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>}</font></span></strong></p>
            </div>
            </td>
        </tr>
    </tbody>
</table>
<table id=table7 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td style="FONT-SIZE: 76%; BORDER-LEFT-COLOR: #d4d0c8; BORDER-BOTTOM-COLOR: #d4d0c8; BORDER-TOP-COLOR: #d4d0c8; BACKGROUND-COLOR: transparent; BORDER-RIGHT-COLOR: #d4d0c8">
            <div>
            <p style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>void ComputeEdgeCostAtVertex(Vertex *v) {</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>if(v-&gt;neighbor.num==0) {</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 42pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>v-&gt;collapse=NULL;</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 42pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>v-&gt;cost=-0.01f;</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 42pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>return;</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>}</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>v-&gt;cost = 1000000;</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>v-&gt;collapse=NULL;</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>// search all neighboring edges for &#8220;least cost&#8221; edge</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>for(int i=0;i &lt; v-&gt;neighbor.num;i++) {</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-INDENT: 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>float c;</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-INDENT: 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>c = ComputeEdgeCollapseCost(v,v-&gt;neighbor[i]);</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 42pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>if(c &lt; v-&gt;cost) {</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 63pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>v-&gt;collapse=v-neighbor[i];</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 63pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>v-&gt;cost=c;</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 42pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>}</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>}</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>}</font></span></strong></p>
            </div>
            </td>
        </tr>
    </tbody>
</table>
<table id=table8 cellSpacing=0 cellPadding=0 width="100%">
    <tbody>
        <tr>
            <td style="FONT-SIZE: 76%; BORDER-LEFT-COLOR: #d4d0c8; BORDER-BOTTOM-COLOR: #d4d0c8; BORDER-TOP-COLOR: #d4d0c8; BACKGROUND-COLOR: transparent; BORDER-RIGHT-COLOR: #d4d0c8">
            <div>
            <p style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>void Collapse(Vertex *u,Vertex *v){</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>// Collapse the edge uv by moving vertex u onto v</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>if(!v) {</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 42pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>// u is a vertex all by itself so just delete it</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 42pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>delete u;</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 42pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>return;</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>}</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>int i;</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>List&lt;Vertex *&gt;tmp;</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>// make tmp a list of all the neighbors of u</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>for(i=0;i&lt;u-&gt;neighbor.num;i++) {</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-INDENT: 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>tmp.Add(u-&gt;neighbor[i]);</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>}</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>// delete triangles on edge uv:</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>for(i=u-&gt;face.num-1;i&gt;=0;i--) {</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 42pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>if(u-&gt;face[i]-&gt;HasVertex(v)) {</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 63pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>delete(u-&gt;face[i]);</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 42pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>}</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>}</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>// update remaining triangles to have v instead of u</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>for(i=u-&gt;face.num-1;i&gt;=0;i--) {</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-INDENT: 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>u-&gt;face[i]-&gt;ReplaceVertex(u,v);</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>}</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt; TEXT-INDENT: 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>delete u;</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 10.5pt; TEXT-INDENT: 10.5pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>// recompute the edge collapse costs in neighborhood</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>for(i=0;i&lt;tmp.num;i++) {</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 42pt; TEXT-INDENT: 10.5pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>ComputeEdgeCostAtVertex(tmp[i]);</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt 21pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>}</font></span></strong></p>
            <p style="MARGIN: 0cm 0cm 0pt; TEXT-ALIGN: left" align=left><strong><span lang=EN-US style="FONT-FAMILY: Courier New"><font size=2>}</font></span></strong></p>
            </div>
            </td>
        </tr>
    </tbody>
</table>
</p>
<p>&nbsp;</p>
<ul>
    <li>
    <h4 style="TEXT-ALIGN: left"><strong>更好地利用数据</strong></h4>
    </li>
</ul>
<p style="TEXT-ALIGN: left">　　相比把用过之后移除的顶点、三角形数据信息丢弃，还不如把它们都保留下来，以便以后需要使用这些数据的时候不必重新运算多边形减面。这个特性很容易就能实现，只要把每一个坍塌后的顶点以及坍塌的顺序保存下来就可以了。<br>　　BUNNYLOD.EXE这个演示就是使用这个方法。一开始，小兔子模型在大约1秒钟时间内从450个顶点减少到0个。然后，模型会不断的增加细节，并且是在一些特殊的多边形数量的阶段，左边的进度条会同时通过动画方式来表示这个过程。另外还有一种动画方式是从0到全部顶点不断增加。<br>　　　　<br>　　边坍塌序列也可以用在渐进传输中。就像交错存储的.GIF和.JPG图片可以在网络传输中不断增加细节一样，一个物体的顶点可以通过坍塌过程的倒序排列来进行数据广播。接受的计算机可以不断的根据接收到的数据流来重建并且显示这个模型。这个主意非常棒，但是或许现在来看和游戏开发者还没有什么关系。</p>
<p style="TEXT-ALIGN: left">　　模型的LOD在很多游戏中是一个非常重要的组件。根据我们的算法生成的坍塌序列，可以生成很多细节层次的模型来表示模型的不同的LOD。在交换模型的时候有一个问题就是玩家常常会注意到它的发生（这种现象叫做&#8220;跳出&#8221;）。一个对付&#8220;跳出&#8221;现象的解决方案是在两个模型中间做平滑变形。为了能够在两个模型之间做变形，必须把其中一个模型的顶点映射到另一个模型上。幸运的是，这些信息可以从边的坍塌序列中提取出来。BUNNYLOD.EXE也演示了变形的例子。</p>
<ul>
    <li>
    <h4 style="TEXT-ALIGN: left"><strong>可供选择的边坍塌技术</strong></h4>
    </li>
</ul>
<p style="TEXT-ALIGN: left">　　多边形减面算法不是创建低面模型的唯一选择。美术人员做出来的底面模型往往比通过算法算出来的低面模型更好。其中一个原因是算法无法从宏观上把握模型。从一方面来讲，美术人员了解他（她）所创建的模型（比如兔子、椅子，等等），并且能够从审美的角度上决定如何去减少物体的面数。人类的视觉系统会偏向于某几个细节，比如说眼睛和嘴部，并且很少去关心其它部位的细节，比如锁骨或者膝盖。另一方面，我们这个简单的算法仅仅比较了很少的点积和边长，并且明显缺乏一种智慧来自动识别那些人感觉比较重要的部位并进行优化。使用多边形减面算法的优势在于能够让这个过程自动化。<br>　　</p>
<p class=separator style="CLEAR: both; TEXT-ALIGN: center"><img style="cssFloat: " height=141 alt="" src="http://dev.gameres.com/Program/Visual/3D/BMP11-large.jpg" width=420>　　<br>　　图8 技术对比</p>
<p class=separator style="CLEAR: both; TEXT-ALIGN: left">　<br>　　另一种在游戏中使用的制作LOD的技术是使用参数化曲面来描述几何物体，参数曲面片可以镶嵌在需要细化的部位。Shiny的MESSIAH引擎使用了相似的方法。当然，这些基于表面的方法更加可取（也许是最佳的）。图8举了一个2D的例子说明了这个优势。一个正八边形通过参数化的方法去掉一条边生成了一个正七边形。如果用坍塌掉一条边的话正八边形就生成了一个不规则的图形。</p>
<p class=separator style="CLEAR: both; TEXT-ALIGN: left">　　遗憾的是，并不是所有的情况都适用参数化曲面的。有一些情况要求物体在渲染时生成多边形的时候相邻的表面能够很好的吻合在一起（没有裂缝和T型连接）。并且，有很多锯齿状的物体使用参数化表面无法取得良好的效果，这是因为需要的表面也许并不会比多边形的数量少。以多边形为基础的减面的方法一般来说更加有用，并且可以工作在当前类型的模型上。</p>
<p style="TEXT-ALIGN: left">　　我希望我提供的这些信息和例子程序能够派上用场，虽然这篇文章没有触及到贴图坐标、顶点法线、邻接边、单一拓扑、贴图接缝等等。这些题目都留作给读者的练习题。此外，对此算法的变化和改进都是有探索价值的。一个令人兴奋的主题是适应性简化，可以根据运行时的参数来使模型的不同部分使用不同的细节层次进行渲染。这个对于室外地形环境非常有用，这样的话离视点近的部分就可以又更多的细节表现。</p>
<ul>
    <li>
    <h4 style="TEXT-ALIGN: left"><strong>更多的信息</strong></h4>
    </li>
</ul>
<p style="TEXT-ALIGN: left">　　最近多边形减面已经成为一个很热门的搜索主题，并且大部分的文献都能够在计算机图形学会议的会议记录里面找到。另外一些资料你可以看：</p>
<ul>
    <li>
    <div style="TEXT-ALIGN: left">Cohen, J., M. Olano, and D. Manocha. &#8220;Appearance-Preserving Simplification&#8221;, SIGGRAPH &#8216;98.</div>
    <li>
    <div style="TEXT-ALIGN: left">Hoppe, H. &#8220;Progressive Meshes,&#8221; SIGGRAPH &#8216;96, pp. 99-108.</div>
    <li>
    <div style="TEXT-ALIGN: left">Luebke, D. and C. Erikson. &#8220;View-Dependent Simplification of Arbitrary Polygonal Environments&#8221;, SIGGRAPH &#8216;97, pp. 199-207.</div>
    <li>
    <div style="TEXT-ALIGN: left">I have a demo on my university web site at <a href="http://www.cs.ualberta.ca/~melax/polychop"><u><font color=#0000ff>http://www.cs.ualberta.ca/~melax/polychop</font></u></a></div>
    <li>
    <div style="TEXT-ALIGN: left">H. Hoppe, the Guru of polygon reduction, maintains a web site at <a href="http://research.microsoft.com/~hoppe/"><u><font color=#0000ff>p://research.microsoft.com/~hoppe/</font></u></a><br></div>
    </li>
</ul>
<img src ="http://www.cppblog.com/xosen/aggbug/78911.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/xosen/" target="_blank">xosen</a> 2009-04-04 02:31 <a href="http://www.cppblog.com/xosen/articles/78911.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>