Chinaunix首页 | 论坛 | 博客
  • 博客访问: 375159
  • 博文数量: 37
  • 博客积分: 6000
  • 博客等级: 准将
  • 技术积分: 1266
  • 用 户 组: 普通用户
  • 注册时间: 2006-03-16 19:52
文章分类

全部博文(37)

文章存档

2011年(1)

2010年(7)

2009年(12)

2008年(17)

我的朋友

分类:

2008-09-29 18:49:44

简单说明:

这份文档不是论文,我称之为笔记,或者.note;这份文档free for all,你可以随便使用。如果你想引用,请注明出处http://blog.csdn.net/xjyhust,以及作者 熊家煜。 Ok,由于这个文档只是v1.0,所以缺少很多实际的测试结果,在后面的版本中我会加进来;但是里面的技术是不会有太大的变化的。最后,希望这个文档对你有用。

无缝地形技术简介

mesh部分,使用分块的高度图来保存信息;纹理部分,每块地形使用8张diffuser纹理,4张normal map纹理,使用调色板纹理查询权值进行混合。分别对待场景编辑器和引擎运行时的地形处理方法,使效率和灵活性都能得到保障。

无缝网格的实现

地形的实现,主要针对2D的高度图,保存的精度是float 32bit。为了支持超大的无缝地形,必须将地形进行分块,在运行期间,内存中只保存可视的一块区域。

LOD的实现

地形分块以后的最小单位,我们先称之为brick。离摄像机较近的brick,有较为细节的lod,定义这个时候的lod为低lod;离摄像机较远的brick,有较为粗略的lod,定义这个时候的lod为高lod。(不要弄错了,近处lod低,远处lod高。)

Lod的大致方法就是,lod为level 0时,渲染每个顶点;level为1时,每2个顶点渲染一个顶点;level 为i时,每(2的i次方)个顶点渲染一个顶点...... 具体参看下图:

clip_image002

    Level i                         Level i + 1

为了解决地形“龟裂”的问题,需要对不同level之间缺少的顶点进行处理。有几种不同的处理方式,我的方式是:

假设这个时候level i和level i+1相接,我将level i+1的brick和level i-1临界的那条边界上缺少的顶点加入原有的三角形列表中:

clip_image004

为了解决这个边界“龟裂”的问题,我们需要有不同的三角带。由于在World Craft中,lod的分布都是按照环形的:

clip_image006

所以每个lod的三角带需要有这样五种不同的情况,对于level 为i的brick。情况一,他只和level为i的brick相接;情况二至五,他有且仅有一侧和level i-1的brick相接。

性能和优化

对于brick渲染的优化,我们引入了一个sheet的概念。Sheet是一个更大的划分单位,在我的实践过程中,sheet为4*4个brick或者8*8个brick组成(根据brick的大小不同),sheet共用一个vertex buffer,这样可以使效率更高一些,虽然也是draw n次,但是不需要修改vertex buffer,只要修改index buffer,对待lod相同的brick,index buffer也是一样的,只需要修改偏移量。引入sheet,也是为了后面的texture的优化需要,不需要频繁的切换material,也不用写过于复杂的sort就可以达到优化的目的。

编辑器和引擎运行时的不同

有这样几个原因使我们有理由分开对待这两种情况:

1. 编辑器需要及时的相应用户的输入,这就要求每一次对地形的改变在较短时间内完成

2. 引擎不需要灵活性,所以渲染的数据需要尽量的优化

编辑器中没有sheet的实现(只有概念),即每个brick有单独的vertex buffer,brick要尽可能的小,比如,17*17大小。这样做大大减小了vertex buffer的修改范围,用户的修改会及时得到反馈,不会出现太长的“假死”。

而引擎运行时,sheet的大小为513*513或者257*257,每个brick的大小为65*65或者33*33。我比较倾向513*513的sheet和33*33的brick。(具体是多少比较合理,在这份.note里面我没有列出详细的比较数据,在v2.0里面会列出一些测试的实际数据)

World Craft的地形编辑

World Craft中,使用一个较大的height map来表示一个sheet的,比如513*513大小的height map,然后每个brick的大小为17*17,对应height map的某个17*17的区域,由于brick单独使用vertex buffer,所以,相邻的brick之间共用height map上的17个pixel。

以rise/lower地形笔刷为例,具体的流程如下:

1. 判断鼠标拾取点(和地形相交的点)

2. 计算拾取点所对应的height map上的点

3. 将笔刷(一样也是32bit-float的位图)正确的操作到height map上

4. 更新所影响到的区域(只更新区域内的brick)

有几点需要注意的地方:

1. 更新brick的时候,只更新顶点的position,而不更新normal,否则速度会很慢;normal的更新只在每次鼠标抬起的时候进行;最后的效果非常不错。

2. 涂刷height map的时候,引入一个笔刷密度的概念,表示每隔多少像素才进行一次涂刷;当鼠标移动过快或者过慢的时候,不会产生不均匀的效果。大概的方法就是:记录每次的图刷点,如果图刷点之间的累计距离超过了笔刷密度的定义,就在最近的涂刷路径上的正确地方进行涂刷。

3. 顶点normal的更新,是自己写得,没有用到nvidia的nvMeshMender库,不知道为什么,用nvMeshMender的时候速度奇慢,也许是网格类型的mesh的顶点重复比较多。

顶点normal的计算

clip_image008

如上图,对于每个顶点vert(图中的黑点),计算其周围的6个三角形的normal,然后取平均值。我不知道这个是不是正确的算法,反正最后的效果是正确的。 ;)

无缝纹理的实现

纹理调色板

纹理调色板的做法就是:调色板纹理上的点对应的rgba值作为权值,分别表示使用4张不同纹理的权值。如果调色板上的点只有红色,那么就是表示最后地形上面的该点只使用了第一张纹理;如果调色板的点有4中不同的颜色,分别为0.1,0.2,0.3,0.4,地形上最后的颜色就是0.1份纹理1,0.2份纹理2,0.3纹理3,0.4份纹理4的混合。具体的算法后面有hlsl给出。

为什么这样做,而不是使用一张全局的纹理?具体原因我之前的一篇blog也说道了,这里我直接粘贴过来:

简单的重复纹理是现在最常用的方法,但是效果一般。使用全局的纹理,如果纹理不是超级大,效果也是很一般:一般高度图中的两个像素之间的距离就是对应真实地形中的一米,一米的世界,要用贴图来表现细节,就是用512*512大小的贴图,或者是256*256的,那么,如果用不重复的纹理,实现一个1公里*1公里的世界,需要的贴图大小是256k * 256k,如果效果稍微差一点,就算是一米内只有64个像素,那样也是64k*64k的纹理。。。所以这样算下来,使用全局的贴图的唯一办法就是用detailed map,而我觉得,这是个丑陋的做法,对于一个以室外场景为主的mmo的网游来说,地形是给人印象最大的图像元素,而用统一的细节来欺骗效果的做法,给人的印象自然很糟糕。

使用八张不同的纹理

使用8张不同的纹理,进行混合,还需另外两张不同的调色板,加上8张normal map,一共加起来是18张纹理,这在某些硬件上面不能实现(好像是只有dx10的硬件才能实现),另外在设置寄存器的时候需要改变相当多的变量。

比较节约的做法就是使用texture atlas,参考nvidia的相应工具(还有代码和文档)。将每4张颜色纹理“打包”成一个纹理,这样,最后就是2大张颜色纹理,两大张normal map;两张调色板纹理,一共是6张纹理,在所有ps2_0以上的硬件上都可以实现。

使用atlas的时候,需要注意纹理坐标的改变,原来的纹理坐标是[0, 1],应该转换到[0 +texOffset, 1 – texOffset],其中texOffset= 1 / ( 2*tex_width);

clip_image010clip_image012

        整合后的diffuse map                                           整合后的normal map

具体实现 pixel shader

texture g_palTex;

texture g_tex0; // Base color texture

sampler tex0 =

sampler_state

{

Texture = < g_tex0 >;

MipFilter = point;

MinFilter = point;

MagFilter = linear;

addressu =wrap;

addressv =wrap;

addressw =wrap;

};

sampler palTex =

sampler_state

{

Texture = < g_palTex >;

MipFilter = linear;

MinFilter = linear;

MagFilter = linear;

};

struct VS_OUTPUT

{

float4 position : POSITION;

float4 texCoord : TEXCOORD0;

float3 diffuse : TEXCOORD1;

float3 normal : TEXCOORD2;

};

float4 RenderWithTexturePS( VS_OUTPUT i ) : COLOR

{

float2 coord0 = i.texCoord.xy * g_fTexRepeat;

coord0 = frac( coord0 );

coord0 = coord0 * 0.5;

float2 coord1 = coord0 + float2( 0.5, 0 );

float2 coord2 = coord0 + float2( 0, 0.5 );

float2 coord3 = coord0 + float2( 0.5, 0.5 );

float4 palColor = tex2D( palTex, i.texCoord.zw ).rgba;

palColor = normalize( palColor );

float4 vDiffuse = tex2D( tex0, coord0 ) * palColor.x +

tex2D( tex0, coord1 ) * palColor.y +

tex2D( tex0, coord2 ) * palColor.z +

tex2D( tex0, coord3 ) * palColor.w;

return vDiffuse * float4( i.diffuse, 1 );

}

以上实现为:一张调色板纹理,一大张diffuse map(4张不同的纹理整合成的)。顶点中的texCoord.xy为局部的diffuse 纹理的坐标,texCoord.zw为全局的调色板纹理坐标。

熊家煜,QY_TEK

Emile:

主页:http://blog.csdn.net/xjyhust

阅读(858) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~