两年前第一次下载speed tree demo运行时,立刻被精美的画面所震撼,场景中大片的树木尤其吸引我的眼球,speed tree使用何种技术来渲染大范围的植物一直令我不解。因为在很多游戏中对树木的渲染都可以很明显地看到多边形的痕迹,而在speed tree中你几乎看不到任何破绽,繁茂的树叶、精确的光照、shadow、self-shadow,这些元素构造起了一个完美的树木。
直到前几个月无意中玩完美世界时发现它使用了speed tree来渲染树木,由于洪恩丑陋的美工得以使我发现了其中的端倪。我立刻又拿起以前下载的demo仔细研究了几天,终于发现speed tree是如何渲染树木,下面就自己的看法和大家探讨一下。
1 植物模型
在speed tree中将所有的植物模型保存在.spt文件中,一个model按照我的观察应该分为三部分:主干、支干、树叶。主干是指树木本体和其最粗大的枝干,这主要是由mesh构成,一个比较有意思的地方在于speed tree并不是使用通常的顶点来保存这个mesh,而是使用多条贝塞尔曲线来保存整个mesh,在运行时再转换为mesh,按照我的考虑这样做有两大优势:一个是非常方便做mesh的lod,因为贝塞尔曲线是一个函数,可以快速获得任意位置的顶点坐标,这样不必象一般的mesh那样预计算lod数据,减少了内存的占用量。另一个是非常方便做碰撞检测,使用曲面要比一般的mesh使用的bv tree检查碰撞效率要高很多。
支干是指树木上较细的枝干,在真实的树木上枝干是非常多的,如果使用mesh来对这一部分建模,那么渲染的负担是非常可观的,因此对这一部分speed tree使用了一个简化模型,每一个支干由两个quad mesh构成,这两个quad形成一个V字型,然后将支干的贴图渲染到quad上,通常情况下支干的主枝位于两个quad的接缝处,这样做完alpha blend后会是一个V型的立体模型,不仔细观察很难发现这只是有一张纹理产生的支干。
对于树木的树叶speed tree处理起来更加简单,就是一个billboard加上树叶的纹理,这样大量渲染时负担也不是太重。
2 光照
speed tree中对树木的光照分为三部分进行处理:对于主干部分使用的是per-pixel lighting,因为speed tree中带有normal map。支干部分由于是两个quad mesh因此计算光照时使用面法线,这样一个quad总是一个面为亮面另一个为暗面。而对于树叶稍微有些复杂,由于树叶是illboard,因此不能用法线,而是根据树叶的位置来确定光照值,根据我的观察speed tree根据树种的不同使用了多种光照模型来确定光照,最简单的一种是通过sunlignt的方向获得一个和其垂直的通过树木中心点的水平轴线,将树叶分成两部分,一部分渲染为带光照,一部分渲染为不带光照。
3 阴影
speed tree中将阴影分成三部分计算,首先是树木在地面上的阴影,这一部分最好计算,直接使用shadow map计算出来树木的阴影。其次是self-shadow(自阴影),这一部分是难度比较大的地方,应当是通过预计算出来,然后将其保存到一张shadow map中。最后是其他物体投影到自身上的阴影,这一部分难度最大,按照我的想法这一部分也是分别处理,对于树叶和支干应当是取其中心点坐标,将其变换到投影物体的光照坐标系中,获得投影物体的shadow map投影面的uv坐标,检查shadow map指定位置的亮度信息作为自身的光照值。而对于主干的mesh,则需要将所有的顶点变换到投影物体的光照坐标系中,计算顶点在shadow map的uv坐标,另开一个pass或添加一个纹理单元使用投影物体的shadow map渲染(这样在speed tree中渲染主干需要四张纹理,一张树木表面纹理,一张normal map,一张self-shadow map,一张projective shadow map)。
4 优化
通过以上技术树木的渲染已经非常真实,但是如果希望在每一帧渲染大量的树木还需要做大量的工作,在speed tree主要使用以下几个技术进行优化渲染。
(1)对超过一定距离的树木使用billboard渲染,这种技术也被称为impostoring(替换体)技术,这在现在的游戏使用非常普遍,很多游戏对树木这样的物体都是使用一张预先作好的树木贴图,这样做的后果是看起来非常不真实(如涂鸦的引擎),在speed tree也是预先做好的贴图,但是使用的时候添加了过度的细节,最远的时候是一张贴图,随着距离的拉近在贴图的位置开始出现主干的mesh,接着是支干,接下来逐渐出现树叶,这时候树叶的billboard会比较大,随着距离接近开始变小,随着树叶增加billboard贴图开始消失。一个有趣的现象是speed tree使用了称为bumped billboard技术,也就是给billboard贴图附加了一个normal map,这样在billboard随着视角发生旋转时表面的光照也会出现变化。实际上也可以通过使用RTT技术直接将树木渲染到纹理上作为billboard的贴图,在far cry就是这样做的,由于是隔帧计算因此渲染负担也不是太重。
(2)限制场景中树木的种类,并且保证同一树种使用相同的方位。在speed tree的demo中可以看到同一场景中出现的树种也不过四五种,而且同一树种的方位和大小都是一样的。这样做的好处显而易见,可以避免过多的转换renderer的状态,如果同一树种的方位和大小相同,那么相应的lighting和shadow都是一样的,(只存在porjective shadow map的差别,可以将这一部分另开一个pass单独进行渲染),在sun position不变的情况下只需要计算一次,这样对树木分组渲染时几乎可以将batch装满(在现在的GPU上batch中顶点的最佳数量应当在16000左右,一般情况下树木主干的顶点
不过200多,这样一个batch可以差不多渲染80棵树木,这样的效率是惊人的)。(3)对于mesh做LOD这应当不需要再讨论了。
其他话题:
apeed tree大部分的lighting和shadow都应当是real-time计算的,只有self-shadow应当是预计算出来,如果使用动态光照的话(如day and night cycle的场景)会有些麻烦,要是能够找到一个较好的方案来real-time计算self-shadow,那么speed tree提供的树木渲染方案就可以称的上完美了。
speed tree还有一个令人感兴趣的地方是它的地形引擎,使用了细节纹理和静态lightmap,有趣的是它的lod,竟然只有两级LOD,有点象geomipmap,又存在不同,而且好象在terrain mesh下面还有一层细节较低的mesh,有点象cry engine的做法,但是具体的原因我不是太清楚。不过它的地形引擎的效率并不是太好,主要是同时渲染了太多的顶点,而且它的OC效果并不明显,在wireframe模式下看不出有哪些地方被剔除。
最后提供一张speed tree demo的截图,这张截图有些特殊,是我将树木纹理中alpha通道删除后得到的,可以非常清楚地看到speed tree中一个树木的模型到底是什么样,呵呵,非常有意思。
本主题包含附件:
sf_200662912445.jpg (149591bytes)
其他话题:
apeed tree大部分的lighting和shadow都应当是real-time计算的,只有self-shadow应当是预计算出来,如果使用动态光照的话(如day and night cycle的场景)会有些麻烦,要是能够找到一个较好的方案来real-time计算self-shadow,那么speed tree提供的树木渲染方案就可以称的上完美了。
speed tree还有一个令人感兴趣的地方是它的地形引擎,使用了细节纹理和静态lightmap,有趣的是它的lod,竟然只有两级LOD,有点象geomipmap,又存在不同,而且好象在terrain mesh下面还有一层细节较低的mesh,有点象cry engine的做法,但是具体的原因我不是太清楚。不过它的地形引擎的效率并不是太好,主要是同时渲染了太多的顶点,而且它的OC效果并不明显,在wireframe模式下看不出有哪些地方被剔除。
最后提供一张speed tree demo的截图,这张截图有些特殊,是我将树木纹理中alpha通道删除后得到的,可以非常清楚地看到speed tree中一个树木的模型到底是什么样,呵呵,非常有意思。
题外话
哈哈,最开始在潭拓寺制作的卧龙松、蟠龙松的制作中,我们也用了类似方式,只是没想到在游戏中也用类似方式,哈哈,想想很好玩,那两棵树是一个女同事在潭拓寺住了一周的结果