郭胜,吴晓昶,卢卷彬
英特尔公司软件与服务事业部
•1. 介绍
云是户外场景的主要组成部分,没有云的户外场景是不真实的。当前,大多数游戏主要使用2D照片纹理贴到天空盒的方式渲染云,这种方法适合于视点接近地面的场景,当视点接近或穿透云层时--比如在山顶上或从飞行器中观察云景--上述方法无法提供真实的视觉体验。在飞行模拟类游戏中,玩家需要能从各种角度看到立体的、外形可动态变化的和具有自然光照效果的云朵,当飞行器接近和穿越云层时,要求获得真实的视觉体验。实现这些特征要用体积云技术对云进行3D建模、光照和渲染。然而,体积云算法本质上是计算密集的,这对它们在游戏中的应用提出了巨大挑战。虽然已有一些云系统能支持在游戏中实时渲染大范围体积云,但出于性能考虑,这些系统不得不或多或少放弃一些体积云在运行时的真实的动态特征。
当前,多核处理器平台已经成为PC市场的主流,现代处理器的架构将向更多核的趋势发展。然而,由于传统的游戏架构并没有基于多核系统设计,大部分运行在多核处理器上的游戏并不能有效使用所有的CPU核的资源。这为在游戏中生成逼真的体积云提供了额外的性能空间。
本文提出了一种能在主流多核平台上的游戏中使用的渲染动态体积云的方案。我们的方案基于已存在的算法,重点在改进算法的实现,以及使用多线程框架和SSE指令优化性能。我们开发了演示程序LuckyCloud用于实现和评估我们的方案。演示程序的性能测试表明我们的方案在多核平台上有良好的性能伸缩性。与先前的静态云系统相比,我们的方案实现了云的实时动态模拟和光照,并且没有增加游戏主体更多的性能负担。
•2. 背景
过去几十年中,计算机图形学的研究领域中已经出现大量模拟、光照和渲染体积云的技术。这些技术大部分需要消耗大量的计算资源,其中一些技术在早期平台上甚至属于脱机渲染方法。这使在游戏中引入体积云极具挑战性。与生成单独的体积云图像的应用不同,游戏需要实时处理游戏逻辑和渲染场景的各种对象,因此留给生成体积云的性能空间非常有限。为了保证游戏的性能,一些游戏中的云系统不得不脱机预处理一些复杂的计算--如建模、着色(shading)等,这失去了某些云的动态特征,比如基于物理规律的形状演化、可变的自然散射光照,以及逼真的飞入效果等。
我们的方案主要受到Harris【2】和Dobashi【1】的启发。Harris提出了一种能在飞行模拟游戏中使用的云系统。该系统使用粒子系统建模和渲染体积云。通过用imposter简化远距离的云的渲染,该系统在生成大规模云景时依然具有很高的实时性能。Harris使用一种简化Rayleigh散射模型实现了云对光线的各向异性的多次散射,使不同角度能观察到云的不同颜色。为了加速云的光照计算,Harris通过显卡计算每个云粒子的入射光照强度。然而,这种方法要求依次把每个云粒子的入射颜色值从帧缓冲中读回到CPU,作为下一趟渲染时使用。这种读回操作的开销很大。通常场景中有成百上千个云粒子,频繁的显卡读回操作甚至有可能抵消显卡加速带来的好处,反而使性能更差。为了保证实时的体积云渲染,Harris的云系统预先脱机计算每个场景的云粒子的颜色,在运行时仅渲染云粒子。因此在Harris的方法中,光线的强度和方向都是固定的。我们的方案采用了Harris的光照模型,但使用了不同的实现方法,能够支持运行时的动态的光照计算。
Harris的方法仅能渲染静态的云。为了模拟云的动态演化,我们的方案采用了Dobashi的云模拟方法。该方法采用单元自动控制法。云的模拟空间使用一个三维网格表示,网格中的每个单元都有三个二元状态:分别是水汽(hum),相变因子(act)和云(cld);每个状态的值不是0就是1。如图1所示,通过在每个时间步应用一组状态转换规则,可以模拟云的形态的演化,包括云的形成,消散以及随风飘动。根据每个网格单元是否有云,可以插值计算出模拟空间的密度分布,被后续云的光照和渲染过程使用。相对于其他模拟方法来说,Dobashi的方法计算开销较小,却能产生的逼真的体积云的动画。这是我们选择此方法的主要原因。
图 1: Dobashi的单元自动控制模拟法【1】
•3. 我们的方案(solution)
我们的方案中,实时生成云图像的过程包括三个主要阶段:模拟,光照和渲染。模拟和光照在CPU上执行,渲染主要由GPU完成。模拟阶段采用Dobashi的单元自控法建模动态云,并产生云介质的密度分布;光照阶段计算光线透过云密度空间时云粒子的散射色。我们使用了Harris的光照模型,但在CPU而不是在GPU上实现相关算法;渲染阶段与Dobashi和Harris的实现类似,使用传统的公告板泼溅法渲染着色后的云粒子,合成最终的体积云。
我们基于以下考虑,建议使用基于CPU的方法实现模拟和光照:
•1. 在CPU上执行光照可避免Harris的方法中频繁地读回帧缓冲像素导致的性能瓶颈。
•2. 基于CPU的实现能减少云的渲染占用的GPU资源,降低了游戏对GPU的功能和性能要求,使PC游戏能兼容更广泛的显卡。
•3. 当前,多核平台是PC游戏运行的主流平台。但大多数游戏往往不能充分利用多核处理器中所有核的能力。那些游戏未使用的计算资源可被用于来处理和加速云的模拟和光照,从而尽可能降低云的渲染对游戏性能的影响。
图2显示了基于CPU的光照实现方法:
•1. 从太阳投射一条光线到云粒子上,沿着光线方向在云模拟空间产生几个采样点。
•2. 基于Harris的光照方程【2】一次计算每个采样点的入射光线强度,直到获得云粒子的入射光强度。在这个过程中,每个采样点的密度由它周围的网格单元的密度插值而成。
•3. 基于Harris的光照方程计算云粒子向视点方向散射的光线强度,把它作为渲染云粒子的颜色。
图 2: 我们的光照实现方法
在实现了动态体积云的所有相关算法后,我们采用一种多线程框架渲染整个云景,并使用SSE指令优化单个云的模拟和光照性能。
•3.1多线程框架
我们的多线程框架分为两个层次。高层进行任务分解(task decomposition)。为了尽可能减少云对游戏性能的影响,我们把云景的模拟和光照阶段从游戏循环的主线程中分离出来,放到一个独立的云景线程中执行。云景的渲染保留在主线程中,和游戏场景的其他部分一起渲染,因为D3D并不建议把渲染任务放到不同的线程中执行。由于游戏中,云和光线的动态变化比较缓慢,因此云的模拟和光照并不需要每帧更新,也就说,主线程不需要每帧等待云景线程产生最新的数据,它可以反复使用先前计算好的数据进行渲染,然后在适当的同步时间获得新的数据。在这种方式下,云景线程的执行可以跨多个帧。主线程和云景线程之间的数据同步可以有多种方式,比如每隔几帧、几秒、或者当云景线程完成一次更新任务后再同步。最后一种方式又称为自由同步模式,这使主线程在执行过程中不会被云景线程中断,因此可以使主线程的性能接近渲染静态云时的性能。我们的方案缺省情况下使用这种同步方法。多线程框架的低层是在云景线程中采用Fork-Join的模式进行数据分解。由于游戏中的云景通常由多片云组成,每片云的模拟和光照计算是独立的,因此可以把每个云实例的更新任务作为一个分解的粒度,分别由不同的子线程并行执行。数据分解可进一步提高多核处理器的利用率,加快云景的更新速度。
我们的多线程框架使用一个基于Intel TBB(Threading Building Blocks)【6】的任务管理器和TBB的parallel_for结构来实现。TBB提供了用于并行编程的C++模板,使开发者能把注意力放在计算任务而不是线程细节上。多线程框架的伪代码如下图所示。
bool bSubmitNewTask = false; if ( bFreeStepMode ){ bSubmitNewTask = pTaskManager->isJobDone(); } else{ pTaskManager->waitUntilDone(); bSubmitNewTask = true; } if (bSubmitNewTask){ GetNewDataFromCloudScapeThread (); pTaskManager->submitJob(CloudScapeThreadFunction); } ...... for(int i=0; i< uNumClouds; i++ ) cloudArray[i].render(); |
图3: 主线程(游戏循环)中的伪代码
TaskManager管理云景线程和实现任务并行(图3)。模拟和光照计算包含在线程函数CloudScapeThreadFunction中,在适当的同步点被提交到云景线程执行。
tbb::parallel_for ( tbb::blocked_range<int>( 0, uNumClouds, uNumClouds/uNumThreads), *pForLoopToUpdateClouds ); |
图 4: 云景线程函数中的TBB Parallel_for 结构
云景线程函数中的parallel_for结构实现了数据并行(图4)。这个结构把一个for循环的迭代平均划分到几个子线程中。for循环结构pForLoopToUpdateClouds包含实际的每个云的模拟和光照计算(图5)。
for(int i=range.begin(); i!= range.end(); i++ ) { cloudArray[i].simulation(); cloudArray[i].illumination(); }
|