SpeedTree 学习与实践笔记(2009-04-08 10:57:05)
标签:it
分类:Speedtree
文章引自:http://www.cnblogs.com/flying_bat/archive/2008/12/14/1354884.html
1. 该插件的特点:
api 无关。它本身只是数据结构和逻辑架构,没有任何渲染语句子,因此为了把它应用到自
己的引擎里,需要为之添加渲染相关的语句。而根据 sdk 的讲解,推荐用户为之搭建中间架
构,用来联系 SPEEDTREE 与自己的引擎。这样做起码有两点好处,搭建的中间架构(也推荐
别加任何 api 相关的语句),因此,即使你以后换了 api(譬如从 gl 换成 dx),中间架构还是
可以继续沿用的。还有一个好处就是,当 speedtree 更新版本的时候,你也无须修改你的引
擎,而只需要修改相对简单而且稳定的中间架构。
2. 该插件的具体特性:
注意,下面具体特性分析都是基于 SDK 里一个叫“DirectX9”的例子进行的,在这个例子里,
它给出了最基本的使用方法,同时也向用户展示了它的基本特性。
A. 树的基本渲染
通过大场景的测试,DP 的个数大致是树木棵数的两到三倍。详细分析下,发现
它一棵树分三部分绘制:树干和大树枝(branches),小树枝(fronds),树叶(leaves)
Branches:使用模型来绘制
Fronds:使用两个十字交叉的面模拟小树枝,为了节省三角形。
Leaves:使用 billboard 方式绘制,这样就能产生视觉效果比较好的叶子了。
它这样划分是出于以下三方面的考虑:这几部分的渲染状态不一样,动画的状态不一样,做
LOD 的时候也不一样。具体看下面的介绍。
B. 树的阴影系统
它包括两方面的阴影。首先是树干上的阴影。其次是整棵树在地面的投影。
树干的自阴影(self shadow)是预先生成的,至于生成的算法,可能是可以根据可穿透的
光线跟踪,也可能是结合 shadow map 的逐象素地生成光照贴图(把树干的面都展开后,在
对应的地方画上阴影).有了该光照贴图,那渲染树干的时候就可以跟树干本身的纹理进行
混合产生比较真实的效果。
而整棵树在地面的透影子,则是使用一个矩形画出来的,阴影贴图也是预先生成好。渲染的
时候浮在地面。
C. 树的动画
树的三部分的动画状态都是不一样的。这对优化有极其重要的作用。风小的时候,或是树离
眼睛比较远的时候,可以不动树枝,而只是动树叶。而具体他们是怎么动的:
树叶的动画:就是一个 billboard 的来回平移以及他本身绕视坐标系统 Z 轴的转动。
树枝的动画:通过它引擎本身计算出来的矩阵进行动画。
而至于它具体怎么渲染动画的,它提供了基于 CPU 和 GPU 的方法。
基于 CPU 的方法是:创建顶点缓冲的时候, 使用 D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY
标记(这种方法能提高 CPU 修改和更新该缓冲的速度),渲染的时候实时更新顶点位置。
基于 GPU 的方法是:通过自定义的顶点 shader 程序进行,更新动画的时候,向 shader 传递
常量数组。
D. 树的光照
它可以打开和关闭实时光照,对于实时光照,树干部分又分两种情况,对于没有法线贴图的
树干,使用 per-vertex 的光照。而对于有法线贴图的,则使用 per-pixcel 光照。至于给不
给树干渲染法线贴图,则根据具体的程序决定。
而对于树叶的渲染,因为它是一个 billboard,因此也无法通过其法线来计算光照。它其实
是根据这个 billboard 的位置来确定其亮度的。通过把整棵树当成一个球来分析,而每个
billboard 的位置就相当于是球上的一点,结合光的方向,计算出该点的亮度。
E.
LOD 的特点
其强大的 LOD 系统,为实现大规模的场景提供了有力的支持。这里的 LOD 分三方面:顶点的
LOD,纹理的 LOD,动画的 LOD。
(1) 顶点的 LOD:首先是针对树干,因为这里的树干是实实在在的模型。至于树干的建
立,它里面是采用贝塞尔曲线来描述整个 mesh 的,贝塞尔曲线的描述方式无疑给即时高效
率的 LOD 计算提供了可行性。同时这还针对树枝,远了之后,小树枝就不渲染了。到了一定
的距离的时候,整棵树其实就变成一个 billboard 了。
(2) 纹理的 LOD:树干上在最高精度的时候会有三套纹理:基本纹理,光照贴图,法线
贴图。随着 LOD 的进行,可以依次减去法线贴图,光照贴图,最后是本身贴图,最后只为树
干渲染一种颜色。
(3) 动画的 LOD:现在有三种动画,大树枝(模型)的动画,小树枝(两个交叉面)的
动画,以及树叶的动画。随着 LOD 的进行,依次去掉大树杆的动画,小树杆的动画,最后是
树叶的动画。这也是符合视觉效果的。
F. 文件系统
用场景来分析的话,一个场景是.stf 文件(Speed Tree Forest).该文件描述了每棵树的
相关属性。而一棵树是通过一个.spt(Speed Tree)文件来描述的.用文本编辑器打开,就
能看到里面记录了该树的所有信息。而该插件为此开发了配套了树木编辑器材。使用该编辑
器,打开.spt 文件之后,就可以对该树进行浏览以及编辑。
3.Speedtree 使用实践
它提供给用户的一个最主要的类就是 CSpeedTreeRT.这是一个 speedtree 对外界的接口,从
SpeedTreeRT.h 中可以看到,这个类其实是包括了该插件的核心类.因此,我们在使用该插
件的时候,其实全都是通过这个接口。
譬如 CSpeedTreeRT::SetCamera(eye, viewDir),通知它内部现在的摄像机的信息,然后它
内部就根据这些信息计算出正确的 billboard.
而如何加载一棵树呢?使用 CSpeedTreeRT::LoadTree(const char *treefile);输入一
个”.spt”文件,然后我们设置光照和风效果的方法如
CSpeedTreeRT::SetBranchWindMethod,SetFrondWindMethod,SetBranchLightingMethod,
SetLeafLightingMethod, SetLodLimits 等,接着执行 CSpeedTreeRT::Compute(),然后它
里面就开始进行黑盒处理,最后我们就可以获取其几何数据(CspeedTreeRT::GetGeometry)
进行渲染。获取之前还可以手动去设置 LOD 级别 CSpeedTreeRT::SetLodLevel,然后你获取
到的就是经过 LOD 处理的几何数据。
不过有一点需要要注意的是,speedtree 里面用的是右手坐标系(尽管它说可以通过 define
Y_UP 来改变坐标系统,但我没发现 define 改了之后有什么变化,很奇怪)。笔者开始的时候
完全没注意到这点,发现搬到自己的架构后,树全都是横着的。当时死活发现不了问题,就
去旋转每棵树。然后又发现那些树叶也无法正常地旋转成 billboard,又查了很久。后来终
于发现,是因为 speedtree 内部使用右手坐标系进行计算。而我的架构是使用左手,这样一
来,连传给 speedtree camera 的数据都要修改了, CSpeedTreeRT::SetCamera(eye, viewDir),
其中的 eye,eyeDir,都得经过变换再传进去:
float3 viewDir=pCamera->GetViewDir();
float3 eye=pCamera->GetEye();
float afDirection[3];
afDirection[0] = viewDir.x;
afDirection[2] = viewDir.y;
afDirection[1] = -viewDir.z;
CSpeedTreeRT::SetCamera(eye, afDirection);
4.把 speedtree 加到自己的引擎中去
以上所说的 CSpeedTreeRT 接口,笔者在使用的时候都是让一个 CSpeedTreeRT 对象汇聚到自
己设计的一个 tree 类里。通过这种方式来封装 speedtree,搭建中间架构。CSpeedTreeRT
这接口也许多静态函数,譬如 SetCamera,参照它的 DEMO,
直接“CSpeedTreeRT::SetCamera(eye, eyeDir);”但要实现完美地跟自己的引擎相结合,
也并不是一件容易的事情。主要是,自己的引擎本来就有一套完整的渲染系统,LOD 系统,
动画系统,而且跟 speedtree 的方式也不一样。一个极端的做法就是,对于 SpeedTreeRT,
屏蔽其实时计算,而是根据自己引擎的系统计算,这样的话,其实是只利用了 SpeedTree
的数据结果了。而另外一个极端就是,不管 speedtree 和自己引擎的关系,只保留简单的
耦合,各自使用各自的系统,只是让他们的渲染行为(LOD,光照效果等)保持一致性。至
于更好的办法,笔者也是在研讨中,我非常希望能跟读者进行探讨,这也是笔者写本笔记的
动机之一。
已投稿到: 排行榜 圈子
阅读(97)|评