棋盘类游戏中的栅格地形渲染

棋盘类游戏中的栅格地形渲染

张嘉华 梁成 李桂清

(华南理工大学计算机科学与工程学院 广东 广州 510640)

摘要:本文提出了适合战棋类游戏的三维栅格地形渲染策略,把Catmull-Clark细分曲面思想应用到规则栅格地形,探讨了栅格内顶点和像素的各种属性的计算方法,实现了栅格之间的平衡过度,介绍了在GPU中的实现细节,采用了纹理样式集,几何实例,顶点程序段纹理访问等技术。本文还给出了Vertex Shader和Pixel Shader的伪代码,对于有一定3D图形编程能力的游戏开发人员能够快速再现文本工作。

关键词:战棋类游戏、栅格地形、图形处理器,几何实例

Rendering Grid Terrain for SLG Games

Zhang Jiahua Liang Cheng Li Guiqing

(College of Computer Science and Engineering, South China University Of Technology , Guangzhou Guangdong, 510640, China)

Abstract This paper proposes a 3D grid terrain rendering approach for SLG games, applying the idea of Catmull-Clark Subdivision Surface to regular terrain grids, Methods for calculating various attributes of vertex and pixel in the grid is introduced. This paper also introduces the detail implement on GPU, using technologies such as style texture collection, geometry instancing, vertex texture fetch. This paper gives the pseudocodes of vertex shader and pixel shader. it is helpful for game programmer to implement our work in a short time.

Key words SLG, Tile Terrain, GPU, Geometry Instancing

引言

战棋类游戏(SLG)主要在栅格地形上进行,玩家根据行动顺序指定自方角色沿着栅格行动,进行战斗或冒险。这一类游戏有着庞大的代表性游戏:《超时空英雄传说》系列、《火焰之纹章》、《神奇传说》系列、《天使帝国》系列等。过往都是在二维平面上展现游戏棋盘栅格,随着三维图形技术,特别是现代GPU的迅速发展,已经有很多有代表性的三维SLG游戏,例如:《三国志11》、《信长野望之革新》、《英雄无敌5》等。因此,三维栅格地形的渲染成为了SLG游戏图形模块重要的一部分。

clip_image002

图1:SLG游戏的地图栅格

如图1,游戏场景设计人员往往在地图这些栅格上进行编辑,设置栅格的颜色,纹理样式等以表示这个栅格是雪地,草地还是水体,对于三维SLG游戏还要设置这个栅格的地形高度。

clip_image004

图2:带有渐变和过度的游戏地图栅格

随着游戏的发展,每个栅格若采用单调的颜色来表示往往不能满足需求,因此希望对于场景设计人员来说仍然能够以栅格为单位设置属性,但是栅格与栅格之间能够表现出渐变过渡。如图2,从左到右依次经历水体,沙地,泥地,草地,沼泽地的变化。那么,如何能够既让地形编辑人员能够方便地对每个地形栅格设置属性,同时又能在栅格之间表现出平滑的过度,就是本文研究的重点。

本文的主要贡献包括:1)介绍了一种适合SLG类游戏栅格地形绘制的方法;2)介绍了栅格内顶点和像素的属性位置计算方法,实现了栅格之间的平衡过度;3)介绍了GPU实现细节,采用了纹理集,Geometry Instancing,Vertex Texture Fetch等技术,并给出了Vertex Shader和Pixel Shader的伪代码。对于有一定3D图形编程能力的游戏开发人员能够快速再现文本工作。

接下来的章节将如下组织:第1节先介绍对栅格地形绘制有影响的相关工作;第2节介绍栅格地形绘制的基本框架;第3节介绍了栅格内顶点和像素的属性位置计算方法为第4节GPU实现提供了理论基础;第5节给出了本文方法的运行效果。

1 相关工作

地形的绘制已经有非常多成熟的方案,也有许多适合图形硬件的方案,大致可以分为基于二叉树、四叉树等的LOD方案,适合硬件加速的分块LOD方案、视点依赖的针对GPU的几何裁剪图方案等。

[Duchaineau et al, 1997]提出采用ROAM二元自适应合并分割,通过实时合并和分割三角形来调整地形精度,需要在CPU上不断进行调整。该方法具有灵活的视点依赖的误差评价,能够用户指定三角形数量,具有帧一致性(frame-to-frame coherence)等优点,但该方法并不符合现代GPU Large Batch和少DIP Call的原则。[Röttger et al, 1998]提出对于地形高程绘制采用连续LOD模型来处理,使用四叉树来描述地形高程,对内存需求很少,支持geomorphing,并且能够实现快速处理地形高程的分页(paging of large elevation grids)。

随着2002年后GPU的迅速发展,不再需要在CPU上进行大量严格简化,而需要在CPU简化和GPU批量处理之间取得一个较好的平衡点。[Ulrich, 2002]提出了分块LOD方案。每一个块(chunk)是一个规则格网集,在格网边缘采用垂直裙子方法来填补块与块之间的裂缝。由于每块是一个离散化的整体,有利于GPU批量绘制,该方法有低CPU overhead高渲染吞吐量、能够在aggregate内使用三角网、纹理LOD与地形几何LOD相适应、out-of-core存储、有效平滑的顶点渐变(morphing)、无顶点跃动(poping)和在视点移动时低CPU负荷等优点。但也具有下面这些缺点:数据集必须是静态的,比传统的动态LOD方案需要使用更多的三角形、块内无法进行遮挡剔除。[Larsen ,2003]提出采用硬件优化的LOD地形方案,与Chunk LOD相似基于规则格网四叉树,不同的是对于每个块内部,再内建自适应四叉树,使用Vertex Shader在每个顶点上处理geomorphing。[Ulrich, 2002]提出的分块LOD方法对于栅格地形来说是适用的,因为可以把地形上若干栅格组织成一个块,以块为单位进行视锥剔除和绘制,能够很好地利用GPU,[Larsen ,2003]提出的方法则为我们在Vertex Shader计算顶点属性和位置提供了启发。

信长野望之革新与三国志11的地形引擎和栅格划分类似,都是对地形划分为一个个栅格,如图1,对于棋盘地形中的每个栅格,都有进行LOD处理。这种LOD类似于[Ulrich, 2002]的方法,从测试中只观察出两级LOD。对于图3(a),白色区域选定的栅格由4×4×2个规则三角片构成,当摄像机镜头拉离地表时,LOD级别发生了改变,选定的白色区域的栅格变为图3(b)中的2×2×2个规则三角片构成。

clip_image006

(a)

clip_image008

(b)

图3:三国志11线框渲染,红色边框内的白色线框区域为鼠标选定的某个栅格

2 基本框架

整个地形都是按栅格进行划分的。图4是一个栅格(grid)的定义,栅格的纹理样式和颜色都定义在中心采样点上,对于输入256×256的原始地形,就有256×256个栅格。每个栅格由四个子区域(sub rect)组成,每个子区域由4个顶点构成。读者肯定会问一个问题,为何不直接对每个栅格用一个顶点表示其中心点呢?这是因为不同栅格的各种属性之间需要过渡,并且边界上的顶点属性并不是都刚好满足周围4个采样点的双线性插值关系,需要在栅格边界上放置经过计算的顶点以保证属性过渡正确,并且使栅格内非边界区域能展现该栅格设置的采样点属性。

因此,每个子区域2×2个顶点,共2×2×4=16个顶点。对于一个256×256原始采样点的图来说,那么就需要256×256×16个顶点。那么读者又会问,为何不是一个栅格只要周围8个边界上的顶点和一个中心顶点呢?这是由于每个子区域都是由它周围4个采样点按照一定比例混合过渡的。如图2,8号顶点和2号顶点则被看作在不同的子区域,对它有权重影响的周围4个采样点是不一样的,因此两个顶点的4层纹理坐标也有差别,如果不采用Shader是难以实现每个栅格只用9个顶点。

clip_image009

图4:1个栅格(Tile)的定义

地图设计编辑人员往往希望只需要对每个栅格独立设置各种属性,包括纹理样式和颜色等,这和细分曲面很类似,往往希望通过修改低分辨率的控制网格但产生高分辨率光滑的网格。因此,对某个栅格(i,j)设置属性就等价于为中心采样点设置属性。栅格中心采样点的某种属性定义为a(i,j),栅格中每个顶点也拥有自己的属性,如图4中构成该栅格的16个顶点的对应属性为{ak(i,j)|k=0,1,..,15}。这里需要说明的是,v3v6v9v12四个顶点从二维投影来看与采样点是重合的,但是从三维空间上来看,最终计算得到的位置并不一定与采样点重合的,这取决于计算模板,对于Catmull-Clark模式这4个顶点将逼近通过采样点所构造的极限光滑曲面。

考虑到用户编辑和选择的最小单位是栅格,那么为了表现该栅格,最小需要16个顶点来表现一个栅格。因此,在对地形进行LOD处理时,不能够通过合并栅格来减少顶点数量,至少也要维持每个栅格4×4=16个顶点。有鉴于此,本文采用了[Ulrich, 2002]的方案分块来进行处理,每4×4=16个栅格组成一个块(chunk),每个块内LOD时最低分辨率约束不能低于(4×4)×(4×4)=256个顶点。LOD采用4个级别,最高级别时每个块内有128×128=16384个顶点。由于栅格的子区域四层纹理是不同的,因此,不能对栅格与栅格边界上的顶点进行合并减少顶点。在每帧,视锥剔除和LOD级别计算等都是以块为单位进行。

对于地形范围比较少的场景,也就是采样点少于等于256×256的场景,可以预先构建好整个地图所有的块,并生成每个块的所有LOD级别,但是这样存在较多空间冗余,因为每帧绘制中,大部分的块都采用较低的LOD级别,而采用较高的级别只有1至2个视点正在关注的感兴趣的块。如果,每个块都生产好各个LOD级别,一个块所有LOD级别共需要256+1024+4096+16384=21760个顶点,整个256×256个栅格的场景共有64×64=4096个块,那么整个场景生成时就有21760×4096=89128960个顶点,而实际每帧绘制的地形也就约2×16384+6×4096+16×1024+64×256=90112个顶点(2,6,16,64为平均每周视锥剔除后场景中各个LOD级别块的数量),也就是绘制的顶点约为总顶点数量的0.1%。为了解决这个问题,已经有许多Out-Of-Core的外存地形以及内存分页映射的地形数据加载调度算法,这些算法对于海量数据(例如TB级的数字地球的金字塔影像数据库)来说是合适的,但是我们整个场景只有256×256个栅格,也就是65536个采样点,大部分高分辨率LOD级别的顶点数据都是插值得到的,并不是真的存在那么多高分辨率采样点,之所以存在那么多顶点,主要一个原因就是对应一个采样点的栅格就产生了至少16个顶点。

由于整个场景的栅格一般只有256×256数量,没有必要运行时进行内外存数据交换,但是我们又希望当镜头靠近时能插值出高分辨率的顶点。因此,是否有一种无需生成好具体顶点,能够延迟具体顶点位置和属性计算到绘制时再在GPU处理的方法呢?

Geometry Instancing技术和Vertex Texture Fetch技术使得这样的想法成为了现实。在CPU中,只要为其中一个块生成好各个LOD级别的顶点缓冲,这里记这些顶点缓冲为VBββ为LOD级别,取0~3。顶点缓冲VBβ中各个顶点的位置只需要填上相对于块内的位置,绘制时在Vertex Shader再计算具体的位置。有了这样的基础后,绘制时对于每个块根据自己当前帧的LOD级别选择对应的VBβ来绘制。如果采用Geometry Instancing方式绘制,为同一个LOD的各个块设置一个实例数组放到实例缓冲,通过SetStreamSourceFreq设置后绘制;对于不支持Geometry Instancing的GPU,可以对于同一个LOD级别的各个块分别调用DIP call。这使得同处于β这个LOD级别的各个块都可以共享一个顶点缓冲VBβ。这大幅度减低了顶点数量,在CPU分配顶点缓冲时只需要21760个顶点。

为了能够在绘制时再计算出具体的顶点位置和属性,因此需要用到Shader Model 3.0的Vertex Texture Fetch技术。首先,我们把场景地图中的256×256个采样点的位置、颜色和选用的纹理样式分别存放到3张纹理TPTCTSTP中的一个像素TP(i,j)对应采样点的位置,记为p(i,j),TC中一个像素PC(i,j)对应采样点的颜色,记为c(i,j),TS中一个像素TS(i,j)对应采样点选用的纹理样式t(i,j)。在GPU的Vertex Shader根据输入的每个待插值顶点在块内的编号得到顶点在栅格内的编号k,通过tex2DLOD指令从这3张纹理读出临近4个采样点的数据,加权得到这个插值顶点的数据。具体顶点的属性和位置计算在第3节介绍。

3. 属性和位置的插值计算

第2节介绍了栅格地形绘制的基本思路,那么如何计算栅格内的16个顶点和栅格覆盖的各个像素的属性和位置呢?下面3.1节将介绍顶点除位置外各种属性包括纹理、颜色的计算,3.2节进一步介绍像素属性的计算,3.3节介绍顶点的位置计算。

3.1. 顶点属性计算

clip_image010

图5:某个栅格与其邻接栅格示意图

为了实现栅格间的各种属性(纹理,颜色等)的融合,先来考察最为简单的双线性混合。下面推导栅格上每个顶点的属性权重比例。对于浅黄色子区域(8-9-10-11),参见图5,实质上是采样点A-B-D-E所构成的区域的右下角,因此顶点9的某个属性如下计算:a9=0.25(aA+aB+aD+aE),wi为四维向量,分别代表影响该子区域的四种类型纹理的权重,其分量顺序对应每个子区域的纹理样式编码,均为左下,左上,右下,右上,例如w0=(0.25,0.25,0.25,0.25),类似可以得到下面关系:

clip_image012

clip_image014

clip_image016

clip_image018

(1)

3.2. 像素属性计算

不但要根据采样点得到顶点颜色,更进一步需要根据采样点得到栅格内某个像素的颜色。虽然现代的GPU都已经能够在硬件实现双线性插值,但是为了后文讨论方便,下面还是先对每个像素的属性计算先作进一步推导。

nclip_image019

图6:逐像素纹理混合

假设栅格坐标如图6,栅格内顶点像素范围取值从-0.5至0.5,取u1=floor(u),u2=ceil(u),v1=floor(v),v2=ceil(v),那么对于栅格内任意一个像素,影响其的四个栅格(采样点)分别为(i+u1,j+v1),(i+u2,j+v1),(i+u1,j+v2),(i+u2,j+v2),对于如图4,假设像素处于右下角子区域,那么则u1=0,u2=1,v1=-1,v2=0,影响它的四个栅格,也就是提供它融合权重的四个采样点分别为(i+0,j-1),(i+1,j-1),(i+0,j+0),(i+1,j+0)。接着计算混合比例u’=fmod(u,1.0),v’=fmod(v,1.0),即对1.0求浮点模,那么栅格内任意点(uv)的属性可以通过公式(2)双线性插值计算得到:

clip_image021(2)

3.3 顶点位置计算

考虑到每个栅格的顶点位置是定义在栅格的中心采样点上,为了使每个栅格周围的16个顶点位置能够更加平滑,因此可以考虑采用细分曲面的Catmull-Clark细分模式来计算16个顶位置。

clip_image022

(a) V顶点模板 (b) E顶点模板 (c) F顶点模板

图7:Catmull-Clark细分模板

根据图7中Catmull-Clark细分模式的三种模板,从图5可以看出,0,5,10,15号顶点属于F顶点,1,2,4,7,8,11,13,14号顶点属于E顶点,3,6,9,12号顶点属于V顶点,因此有下面的式子;

clip_image024

(3)

应用了Catmull-Clark模式计算栅格的16个顶点坐标后,整个地形能显得更加平滑,但是Catmull-Clark模式会改变顶点的位置以逼近极限的光滑曲面,因此考虑一种能够保持顶点位置的模板。这种模板需要更大范围的领域模板顶点去计算一个新的顶点位置

Dafdasfasdf

(4)

4.实现细节

在具体实现时,首先在CPU生成好各个LOD级别的顶点缓冲VBβ。在绘制时为每个块进行视锥剔除,剔除后剩下需要绘制的块选择合适的LOD级别调用绘制函数。在GPU每帧处理中,Vertex Shader根据原始输入的采样点的数据,计算栅格地形顶点的具体位置、颜色、4层纹理的纹理坐标以及权重,然后GPU对顶点颜色,权重以及纹理坐标进行双线性插值产生Pixel Shader每个像素的输入。在Pixel Shader中,对于每个输入像素,根据插值得到的4层纹理坐标访问纹理得到4层纹理的颜色,根据权重对各层纹理颜色进行混合,再和输入的像素颜色混合,得到最终的Pixel Shader输出的颜色。

4.1. 纹理样式集

为了减少纹理上下文的切换,可以考虑把各种样式纹理组合到一张大的纹理集S上,本文采用2048×2048的大纹理作为纹理集S来存放16×16=256张样式纹理,图8是一张16×16组合的纹理集。每个样式纹理大小为64×64个像素。

clip_image026

图8:16×16纹理集组合

生成了纹理集后纹理坐标需要从样式纹理坐标到纹理集坐标的转换:

clip_image028 (5)

其中u’,v’为转换后纹理集的坐标,uv为样式纹理的坐标,pq为样式纹理在大的纹理集S中的横纵序号。

4.2. CPU预处理

接下来需要在CPU中生成好各个LOD级别的顶点缓冲VBβ,那么需要先定义顶点缓冲中顶点的结构。由于顶点并不再存放实际顶点位置,每个顶点只需要3个纹理通道,一个存放顶点所处栅格在块内的编码,一个存放顶点在栅格内的编码k,一个存放顶点在栅格内的归一化坐标:

struct GridVertex

{

public:

float2 mn;//顶点所处栅格在块内序号,TexCoord0

float k; //顶点在栅格内序号,TexCoord1

float2 uv;//顶点在栅格内归一化坐标,TexCoord2

};

顶点在栅格内的归一化坐标实际上都可以在Vertex Shader结合常量表设置的每个绘制块最左下角栅格序号推导得到,但是为了减少Vertex Shader运算,因此直接在CPU计算好了。

在CPU中,每个栅格的中心点属性和位置存放在在纹理TPTCTS当中,那么对每个栅格的属性和位置修改可以看作对纹理TPTCTS的某个像素的再渲染,通过渲染到纹理(RTT)来实现。这使得实时编辑和修改栅格成为了可能,并且在修改完成后无需对顶点缓冲锁定和解锁,无需重更改顶点缓冲中顶点属性和位置。

4.3 Vertex Shader

4.2节中介绍的顶点的位置和属性计算需要在Vertex Shader进行,沿用第2节中对p(i,j),c(i,j),t(i,j)的定义,栅格内每个顶点vk可以根据公式(2)看作u,v落在栅格边界上的顶点处理,在Vertex Shader如下计算:

uniform int left_tile;

uniform int bottom_tile;

VertexOut VS_main(VertexIn In)

{

float u=In.uv.x;

float v=In.uv.y;

u1=floor(u);

u2=ceil(u);

v1=floor(v);

v2=ceil(v);

float i=In.mn.x+ left_tile;

float j=In.mn.y+ bottom_tile;

VertexOut Out;

Out.uv0=f(t(i+u1,j+v1));

Out.uv1=f(t(i+u2,j+v1));

Out.uv2=f(t(i+u1,j+v2));

Out.uv3=f(t(i+u2,j+v2));

c(u,v)=CalcByEquation(2);

Out.c0= c(u,v);

w(u,v)=CalcByEquation(2);// 四层纹理权重

Out.w= w(u,v);

p(u,v)=CalcByEquation(3);

float4 worldpos=p(u,v);

Out.pos=mul(worldpos,ViewProjection);

return Out;

}

算法1:Vertex Shader的伪代码

这段Vertex Shader首先根据输入的顶点在栅格内的归一化坐标uv以及在这个块绘制前通过Shader常量设置的这个块左下角栅格序号left_tile和bottom_tile计算得到四个采样点坐标的横纵分量u1,v1,u2,v2,接着得到这4个采样点坐标(i+u1,j+v1),(i+u2,j+v1),(i+u1,j+v2),(i+u2,j+v2),接着利用Vertex Texture Fetch技术支持的tex2DLOD指令从纹理G读取影响当前顶点的4个栅格采样点数据,然后根据公式(2)和公式(3)计算出这个顶点属性和位置输出。

4.4 Piel Shader

对于Pixel Shader输入的每个像素的各种属性,其中像素颜色可以由GPU根据4个顶点颜色进行双线性插值得到,在CPU计算好每个顶点的颜色即可,纹理的双线性插值由于需要根据不同纹理坐标采样了再进行插值,因此可以在CPU根据顶点在栅格内坐标计算后四个层次的纹理坐标,在Piel Shader输入前由GPU双线性插值得到每个像素分别对应4层纹理的坐标,对四个纹理层次进行纹理采样(Sample)得到4层纹理的颜色,然后根据权重得到最终的纹理颜色。

在Piel Shader里,对于每个子区域的每个像素,GPU会根据四个角点的权重向量和颜色向量进行双线性插值得到具体每个像素的权重向量w(u,v)和c0(u,v)作为Piel Shader的输入,在Piel Shader只需要对四种纹理分别根据w(u,v)的四个分量为权重进行tex2D纹理采样,得到该像素的纹理颜色:

float4 PS_main(PielIn In) : COLOR0

{

float4 tc[4];

tc[0]=tex2D(S,In.uv0);

tc[1]=tex2D(S,In.uv1);

tc[2]=tex2D(S,In.uv2);

tc[3]=tex2D(S,In.uv3);

float4 tc=In.w[0]*tc[0]+In.w[1]*tc[1]+

In.w[2]*tc[2]+In.w[3]*tc[3];

return tc+In.c0-0.5f;

}

算法2:Piel Shader的伪代码

5 结果与讨论

我们在配置有NV8400m GPU,2G内存的计算机上进行了测试,每秒能够保持100帧以上的FPS值。图9是我们开发的一个SLG游戏《武林群侠传》的地图编辑器,绿色区域是正在选择编辑的3个栅格,用户可以对选择范围内的栅格进行各种地形操作:诸如抬升高程,降低高程,铲平到水平面;设置该范围栅格的颜色;设置该范围栅格用的纹理样式。用户可以改变选择的栅格范围大小(绿色区域大小),有1X,3X,5X和自定义范围4种选择。

对于用户上述对绿色编辑区域的任何修改,实时通过渲染到纹理(RTT),更新纹理TPTCTS对应区域像素,即使更新整张256×256纹理,即重绘整个栅格采样点数据纹理也不到1ms时间,用户完全察觉不到,能够实时响应用户的操作。

clip_image030

图9:栅格地形渲染效果

与英雄无敌5附带的地图编辑器等相比,本文应用了Geometry Instancing和Vertex Texture Fetch技术,任何的编辑修改操作,都直接通过渲染到纹理更新显存上的数据,无需任何对顶点缓冲和索引缓冲的锁定和解锁,避免了对于编辑范围较大时的锁定卡帧现象。同时无需预先分配大量的显存存放各个级别LOD的数据。另一方面,本文提出的方法能够产生光滑的地形曲面,在本质上与细分曲面很类似,细分曲面是对原始低分辨率的控制网格进行修改,产生光滑的极限曲面,本文方法是对低分辨率的规则格网上的采样点数据进行修改,产生平滑的地形。不同的是,本文方法还要考虑栅格所具有的纹理颜色属性,并要保证这些属性能够平滑过渡。

参考文献

1. Mark Duchaineau, Murray Wolinsky, David E. Sigeti, Mark C. Miller, Charles Aldrich, Mark B. Mineev-Weinstein. Real-time Optimally Adapting Meshes. IEEE Visualization 1997 , 81~88.

2. Stephan Röttger, W. Heidrich, Ph. Slusallek, H.-P. Seidel. Real-Time Generation of Continuous Levels of Detail for Height Fields. Procceeding of WSCG 1998, 315~322.

3. Thatcher Ulrich. Chunked LOD: Rendering Massive Terrains using Chunked Level of Detail Control,Course Notes of ACM SIGGRAPH 2002.

4. Bent Dalgaard Larsen, Niels Jørgen Christensen. Real-time Terrain Rendering using Smooth Hardware Optimized Level of Detail. Journal of WSCG 2003, 11(2):282~289.

5. Frank Losasso, Hugues Hoppe, Geometry clipmaps: Terrain rendering using nested regular grids,Proceedings of ACM SIGGRAPH 2004, 23(3): 769~776.

6. Arul Asirvatham,Hugues Hoppe,Terrain rendering using GPU-based geometry clipmaps,GPU Gems2 Chapter 2, 27~44.

7. Nick Brettell. Terrain Rendering Using Geometry Clipmaps. Dissertation, Canterbury University 2005.

8. Malte Clasen, Hans-Christian Hege. Terrain Rendering using Spherical Clipmaps. Proceedings of EuroVis06(Joint Eurographics - IEEE VGTC Symposium on Visualization 2006).

posted @ 2011-09-08 18:04  ~哇哇~  阅读(1974)  评论(0编辑  收藏  举报