多线程架构上的实时深海模拟

本白皮书探讨了如何使用线程技术,在模拟工作负载下的多处理器上进行深海波实时模拟。

 

简介

计算机图形专家很久以来一直尝试对真实世界进行建模。在设计令人沉醉的体验时,我们的目标是设计出有如真实世界一般逼真外形风格的环境。这些模拟的起源可以追溯到研究应用科学的物理学家和计算科学家以前的深入思考。在本文中,我们将研究超凡的深海波模拟。为了改进我们解决方案的效果,我们采用了多线程工作负载,以充分利用双路设备的优势。我们在双路设备上进行实时演示,同时这一实现也能在带有集成图形显示的解决方案,如英特尔® 965G 高速芯片组以及移动式英特尔® 965 高速芯片组家族上实时运行。

首先,我们介绍了先期所做的一系列工作。接着,我们制定了正弦波相加计算方法,用于我们的实施。然后,我们详细介绍了我们的实施情况,包括我们的线程处理机制。演示提供了源代码,可用于您自己的多线程海洋环境渲染的扩展和实施中。

先期工作

很多人都曾研究过水波模拟,其中最为成功的当数 Tessendorf。他所进行的深海波模拟甚至已用于了电影《泰坦尼克号*》和《未来水世界*》中的特效 [5],[6]。此后,其它研究人员和开发人员也纷纷开始关注实时模拟这一技术领域。《水面交互模拟(Interactive Simulation of Water Surfaces)》一书的作者 Miguel Gomez [2] 介绍了一种高度场(height field)的隐式解法。这种解决方案虽然适用于某些情况,但它有一个最大的缺陷,即为了便于计算下一个待渲染的网格,人们至少需要维持两个网格(一个先前网格和一个当前网格)。该解决方案还存在一个缺陷,即人们必须获取相邻信息,才能计算每个顶点在下一帧的位置。而 Mark Finch 在其著作《实物模型的有效水波模拟(Effective Water Simulation from Physical Models )》[3] 中提出了一种不需要这些信息的显式解法,该方案具有如下优势:

  • 无需利用相邻信息来更新位置坐标,简化了并行处理。
  • 由于无需相邻信息,顶点着色器的实施过程也得到了简化,这样开发人员便能更专注于图形子系统上的水模拟。
  • 完全参数化的模拟过程,可实现对几何的精确控制。
  • 为了进一步简化顶点着色器实施中的并行操作,必要时可仅基于本地顶点数据对法线进行更新。或者也可以利用相邻信息进行法线更新。我们将在文中比较这两种方法在处理器中的操作结果。
  • 易于扩充和扩展(scale and extend):我们计划在水流模拟中添加各种特性,而参数化的解决方案能够简化这一过程。
  • 算法用途广泛:无论是波长较大、频率较低的表面波,还是模拟由风造成的频率较高的表面波,我们都可以使用相同的方法。

John Isidoro [4] 在其著作《渲染海洋波(Rendering Ocean Water)》中使用了一种正弦波相加算法,该算法与 [3] 中所描述的方法相类似。它展示了用于实施的相关组装级顶点和像素着色器代码,并在低级 DX8 顶点与像素着色器组装中逐步解释和实现了这一方法。我们按照 [3] 中提供的思路展示了一种基于处理器的算法,该算法可映射到 HLSL,或交由处理器计算。

理论

首先,让我们来回顾一些物理学中有关波的基本定义[Giancoli85],[3] 。

振幅:物体离开平衡位置,到达波峰或波谷时相对平衡位置的最大距离。从波峰到波谷的最大位移在数值上等于振幅的两倍。

波长:两个相邻波峰(或波谷)之间的距离。

速度:单位时间内波的传播距离。速度用相位常数 φ 来表示,φ =速度 x (2 π)/波长……

3.1 波生成的正弦波相加算法

图 3-1. 水波图象

如图 3-1,波长是两个相邻波峰之间的距离,速度是单位周期内波的传播距离,而振幅是从平衡位置到波峰顶点的距离。

3.2 静态波建模

首先让我们熟悉一下基础知识。如果您的应用中未用到某些参数,您可以将其忽略,并根据以下步骤生成您的程序波几何图形。首先,我们选择了一个正弦波,以确保我们的水波具有周期固定、可以控制的参数化特性:

我们在模拟过程中发现,定好空间坐标十分必要,以便使每个正弦波的高度值都保持在 0 到 1 之间。这样更便于我们将水波模拟与实际情况联系起来。现在,我们要对正弦波进行转换,使其数值永远保持为正:

转换后,我们所得的数值为正,并将得出大于 1.0 的数值。因此我们需对结果进行缩放,以使其限制在 0 到 1 的高度范围内:

在很多情况下,尤其是进行深海模拟时,我们都需要进行这种大规模的正弦运动。但是,很多时候我们需要模拟更剧烈的海浪波动,如一场正在逼近的风暴。为了模拟这种效果,我们在模拟框架内引入了“陡度”这一指数:

接下来,我们希望调整波的高度,即振幅。为此我们在模拟框架内加入了一个比例因子:

3.3 动态波建模

现在,我们得到了一个能够控制其陡度与振幅的正弦波。但是,我们还希望能够对水面进行更多的控制。例如,我们希望控制水波的速度、方向和波长。

由于我们所模拟的是一个二维高度场,因此横竖两个方向的运动我们都要考虑在内。为此,我们采用点积的方式将 x、y 坐标轴引入波动方向向量。为了简化计算,我们假定方向向量与水平面平行,以省略 z 坐标轴。横竖两向量之间的点积结果即为标量值,用 S 表示:

接下来,我们希望能对波动的频率进行控制。物理学原理告诉我们,波长与频率的关系可表示为:频率= 2 x /波长。因此我们可以将波长作为已知量输入公式,计算出频率。我们将其代入函数,使其能够影响正弦函数中数值的周期:

现在,我们得到了一个能够通过方向与波长来确定位置的正弦波,但它并未在水面上真正传播开来。为此,我们最后还要引入一个变量,来区分波的不同速度。从物理学原理中我们可以得知,相位常量与速度存在如下关系:

由此我们的等式可变换为(t 代表时间):

最终,我们得到了一个能够控制波长、振幅、速度、方向、位置与陡度的完整函数:

 


其中



 

3.4 波的构建

如果仅模拟简单的情形,那么一个波动函数就足够了。但是,真正的深海表面极其复杂,因此我们需要模拟更高程度的变量。通过观察海面我们不难发现:海面上存在众多来自不同方向的水波,它们无时无刻不在相互干涉,并因此产生众多各不相同的波峰和波谷。为了模拟这些现象,我们需要通过对各个点的位置进行叠加来实现对多个水波的模拟。为此,我们将正弦波的数量限制在四个以内,因为这个数量足以模拟真正的海面变化了。下列等式可用来模拟某一位置(x、y)的高度:

3.5 表面法线

为了对海面进行阴影处理,就一定要了解表面法线的概念。假定我们使用的是细分表面,那么就可通过上面的等式 3 对每个顶点的位置进行更新,然后重新计算面法线以得出新的表面法线信息,最后针对每个顶点对这些数据求平均值。但是,我们需要考虑另一种情况,即 [3] 中展示的显式解法。我们可以沿 x 和 y 轴方向进行求导,以确定表面法线的变化速率。我们称其为“副法线向量(binormal vector)”和“切向量(tangent vector)”。在此之前我们已经指出,根据函数“位置(x,y,t) = (x,y,f(x,y,t))”,任何一个给定位置 (x、y) 都有一个表面高度。高度场的副法线与切(假设已进行简化,认为高度场沿 x-y 网格方向)为:


经简化后可表示为



经简化后可表示为

两式结合可得出:

现在,我们要对 f(x,y,t) 函数求导,并将所有正在合成的正弦波的导数相加,从而得出最终位置。为此,我们需要根据几何模拟中每个正弦波的 x 和 y 值对 f(x,y,t) 进行微分:

其中

关于 y 的微分与此类似:

为了得出最终表面法线,我们根据以下方法计算了每一组件并对结果进行了标准化处理:

3.6 多线程

以往,很多游戏引擎设计师都在游戏中使用了线程技术。这种架构决策一般用于完善功能性,以便在任务级对线程进行良好映射。它常用于简化代码,而非提升性能。现在,我们要研究一下如何利用多线程提升性能。在此,我们仅研究最简单的双线程情况:一条线程处理初始化、渲染以及游戏引擎的其它任务,另一条则专门执行水波模拟。

从高层次上讲,游戏可分为三大任务:初始化、环境更新以及渲染。这里我们主要讨论工作负载中的环境更新线程化过程,因为这一过程贯穿整个游戏,并能为我们提供最大价值。图 3-2 显示了工作负载的划分过程。线程 A 用于管理游戏初始化与渲染,线程 B 则负责处理水波模拟的顶点位置与法线生成。

接下来,我们要考虑图形应用线程化的具体实施问题。由于线程化 DirectX 会极大降低应用性能,因此我们要尽量避免这种情况的发生。DirectX 造成性能降低的原因在于,线程安全版 DirectX 版本每次只允许一条线程访问 API。有时这一特性是必要的,但在水波模拟中,我们决定将所有渲染指令放在一条线程内。

图 3-2. 双线程模拟

该图示简要显示了双线程模拟的工作方式,以及如何为渲染循环中的每个迭代平衡水波模拟负载。TN 代表在每条线程中执行操作所耗费的时间。

线程 A 是主线程,负责控制初始化、人工智能、用户互动、渲染和停机顺序。线程 B 专门处理水波模拟。在本案例中,由于全部任务仅与水波模拟有关,因此线程 A 将处于空闲状态。一种思考方式是线程 A 与线程 B 的工作负载应加以平衡,这样每条线程就无需在空闲状态下等待另一条线程完成任务,或至少将浪费的时间缩短到最低程度。随着我们不断增加用于模拟的线程数量,这种工作负载平衡技术可归纳为:最大限度利用线程优势,同时将工作负载尽可能平衡地分配给可用线程。

实施

第一种实施借鉴了 [2] 的思路。但是这种方案需要用到相邻信息,因此并不适用于并行实施 [1]。此外,它还缺乏控制表面的参数,这也不符合我们的要求。将水波表面作为一种弹性薄膜进行建模,迫使我们形成一种“增能并释放(add energy then let it go)”的思路,而事实上我们也确实需要一种可重复的起伏型水波模拟。因此,我们偏向于采用 [3] 中所描述的实施方案。该实施方案具有诸多优势,我们会在第二章详述。

图 4-1. 深海水波模拟

图 4-1 展示了我们的实施方案。在图的左上方我们可以看到每个正弦波的控制选项,分别用来控制不同的表面属性。点击图片右边的按钮,我们就能在线程实施和非线程实施之间切换、调整法线的计算,并保存参数以备日后调用。我们的演示出自 [11] 中的 BasicHLSL 演示。

4.1 用户界面

[3] 中工作的一大特性就是能够控制所有的表面参数。我们的实施方案也具有实时控制所有表面参数的能力:等式 2 中的振幅、速度、方向以及指数,此外,这些参数还可保存,并在日后调用以供模拟。为了比较线程版本与非线程版本,我们还在图形用户界面的右侧提供了一个切换按钮。水波控制按钮可以被移除,以免妨碍我们对水波模拟的观察。

4.2 利用 C++ 实施带有指数的正弦波相加

接下来,我们要讲解利用多线程处理器实施正弦波相加的具体方法。该方法由等式 2 直接转换而来。

void CSinWaterMesh::TakeStepSumOfWavesWithExp( float t,

int numOfWavesToSum )

{

for( int i=0; i<m_iNumRows; i++ )

{

for( int j=0; j<m_iNumCols; j++ )

{

for( int k=0; k<numOfWavesToSum; k++ )

{

CVector3 posVect;

float dotresult = 0.0f;

float phase_constant = 0.0f;

float final = 0.0f;

posVect.Init( m_pVB[i*m_iNumCols+j].x,

m_pVB[i*m_iNumCols+j].y,

0.0f );

if( m_bSumWave[k] )

{

dotresult = m_direction[k].Dot( &posVect );

dotresult *= ( 2*(float)MYPI ) / m_wavelength[k];

phase_constant = t*

( (m_speed[k]*2*(float)MYPI) / m_wavelength[k] );

final = ( dotresult + phase_constant );

final = ( sin(final) + 1.0f ) / 2.0f;

 

final = m_amplitude[k] * pow( final, m_kexp[k] );

}

else

{

final = 0.0f;

}

 

if( k!=0 )

{

m_pVB[i*m_iNumCols+j].z += final;

}

else

{

// The first wave calculated will overwrite the

// summation from the last frame.

m_pVB[i*m_iNumCols+j].z = final;

}

}

}

}

}

4.3 法线计算

等式 4 阐述了一种用于生成法线的显式算法,也是我们认为最满意的一种方法。但是我们发现,对面法线取平均值的方法可以更快地计算出顶点法线。加快实施方案的关键在于不必逐帧搜索,即可确定每个顶点的相邻位置。为此,我们预先计算并列出了每个顶点的相邻点。这种计算不适用于基于 DX9 GPU 的执行,因为我们无法访问相邻数据,但是对于处理器来说,这种方法要比推导计算要快得多。因此,利用 GPU 时等式 4 仍是进行法线计算的最佳方法,但对 CPU 来说,对面法线求平均值的传统方法速度要更快一些(其中包括计算新面法线的时间)。

4.4 多线程

第一个实施方案旨在获得演示的多线程版本,以正确运行和计算已更新的表面法线与位置信息。为了做到这点,最简单的方法就是创建一个将网格更新功能包含在线程内的函数,并在网格需要更新时创建一条新的线程。因此,每一帧都会启用一条新的线程,以计算该网格的最新高度值。这种实施方案虽然可行,但性能表现却并不理想。

在第二种实施方案中,我们将原先的按需线程模式替换成了“线程库(thread pool)”模式。在实施该模式时,我们仅需添加一条线程来辅助主渲染线程,因此线程库中只存在一条线程。采用线程库的主要目的是在程序启动时创建线程,并在主线程使用。这样就避免了关闭线程后重新加载同一线程造成的效率损失。线程库的缺点是:资源会在不运行时分配给库内的线程。此外,由于线程库的实施方式不同,某些线程会在空闲等待循环时占用处理器循环,造成不必要的浪费。为了弥补这一缺陷,可以采用周期轮询或操作系统同步对象的策略。这种方法可避免死循环造成的处理器资源浪费;取而代之的是定期轮询查看是否有我们需要运行的数据。该方法旨在通过创建单一线程来消除第二条线程的渲染循环成本,从而有效降低相关系统开销。我们为线程 A 加入一个额外工作负载,同时让线程 B 计算网格与法线,以此模拟实时游戏引擎负载。该工作负载耗时不定,可表示独立运行于水波求解器以外的水波模拟的其它方面。该实施应用广泛,包括用户交互以及碰撞检测、人工智能等其它物理计算。水波模拟工作可在任务级进行划分,即将表面计算分配给一条线程,将法线生成分配给另一条线程。另一个可选方案就是执行循环层面分解并将网格分配给多条线程,同时在需要相邻信息时合并网格,以获取一定开销。

线程创建

创建线程需要用到__beginthreadex(…) 函数。我们通过权衡 [1] 中介绍的线程化过程的 win32 函数和 C 运行时实施,最终选择了这种实施方法。基本上,__beginthreadex(..) 函数出错较少,且相比功能相同的 CreateThread(..) 函数更为可靠。事实上,这一点已得到了我们的实验证实。Microsoft Visual Studio.Net 2005* 的技术文档指出,将 CreateThread(…) 函数用于 C 运行时,可在线程调用 ExitThread(…) 时出现个别的内存溢出现象。

#include <process.h>

HANDLE hThreadHandle; //unsigned long

DWORD dwThreadid;

.

.

hThreadHandle = (HANDLE) _beginthreadex(void *security,

unsigned stack_size,

unsigned (_stdcall *)(void *),

void *arg,

unsigned initflag,

unsigned *threadaddr,

);

__beginthreadex(..) 的调用参数如下:第一个参数用于安全属性结构。如果将参数值设置为零,则表明线程处于默认安全等级。第二个参数为堆栈空间大小。如果将该参数值设为“0”,那么堆栈空间将与当前线程的大小相同。下一个参数是用户函数的地址,新线程开始执行时会调用该函数。其余三个参数如下:Arg 是转送到新线程的数值,initflag 是在线程创建之时用于控制线程状态的附加标记,最后一个参数用于将地址写入线程标识符。对于我们的应用,您可在以下代码中查看调用:

hThreadHandle = (HANDLE) _beginthreadex( NULL,

0,

LaunchTakeStepThread,

(void*)&g_time,

0,

(unsigned int*)&dwThreadId );

线程执行

线程创建好后,就会通过 sleep() 函数进入等待状态,直到被“告知”进入工作状态。sleep() 函数以毫秒为单位传送休眠时间。如果传送值为“0”,线程便会放弃当前的时间片段并开始等待,直到被再次调用。一旦我们准备让线程进入执行状态,主线程将通过增加一个共享变量来通知帮助线程(helper thread)。共享变量位于内存当中,由两条线程共享。主线程通过增加数值来通知帮助线程数值处理完成,准备接收下一个网格任务。接下来帮助线程会计算下一组数据,并设置数值,然后转入休眠状态,直至再次被主线程唤醒。这就是“生产者/消费者关系(producer/consumer relationship)”。

帮助线程主体——生产者:

while(we are not exiting the thread)

{

TakeStep();

Time += Time_Increment;

bStepComplete = true;

while(bStepComplete && !bExitThread)

{

Sleep(0);
}

}

主线程负责处理帮助线程产生的网格,并通知帮助线程继续计算下一个网格。

while(1)

{

while(!bStepComplete)

{

// Wait for the grid update to finish

Sleep(0);

}

 

//Copy the vertex info to the “real” VB.

g_pGrid->CopyVBToRenderVB();

 

//Allow the other thread to compute a new set of vertices

g_pGrid->ResetStepComplete(); //resets bStepComplete

}

ResetStepDone() 函数依靠互斥算法。对于关键段代码,我们必须输入关键段、设置一个数值,然后离开关键段。互锁方面,我们将采用互锁递减操作。所有问题都会在 4.5 部分中加以讨论。

线程删除

利用上面提到的实施方案,帮助线程将在我们以 _beginthreadex(…) 开始的函数执行结束时自动删除。某些情况下线程删除还会用到 _endthreadex(..) 函数,但在我们的实施中这一点并非必要。

4.5 互斥算法

在线程执行部分中我们已经提到,我们所做的模拟从根本上讲属于“生产者/消费者”关系,其中帮助线程和主线程分别担当了水波网格处理的生产者和消费者的角色。在典型的“生产者/消费者”模式中,生产者将完整的数据项目传送到存储空间,以供消费者移除并消费。其中数据队列的大小将限制生产者与“后面”的消费者之间的距离。我们决定保持生产者与消费者的随帧同步,禁止生产者在完成当前帧的消费之前处理下一组帧数据。因为在很多游戏中,下一帧往往要依赖于当前帧的数据,如人工智能、物理运算或玩家输入。

我们的实施通过两条线程轮询状态变量 m_bStepDone 来实现同步,其中 m_bStepDone 用于报告网格是否在上次消费网格后进行了更新。为了保护这一状态变量,我们分别尝试了 Aaron Cohen 在著作《Win32 多线程编程:互锁访问与关键段 [1]:互锁访问与关键段(Win32 Multithreaded Programming: interlocked accesses and critical sections [1]: interlocked accesses and critical sections)》中介绍的两种同步访问方法。选择哪种方法要视应用而定。当使用共享变量时,互锁是最简便也是最佳的选择关键段方法的应用范围更广,它可应用于代码中的任意位置,从而将粒度更大的代码段或数据段囊括其中。文中所涉及的案例支持任意一种实施的编译。它们是 Windows 操作系统中速度最快的可用同步原语。

互锁访问可通过一系列函数实现,这些函数可直接映射到面向“读取-修改-写入”场景中的原子处理器指令集。这些原子操作可避免变量因线程间读/写排序问题而进入错误状态。

互锁操作仅包括三个函数:

InterlockedIncrement()、InterlockedDecrement() 和 InterlockedExchange()。

InterlockedIncrement 函数与 InterlockedDecrement 函数可分别用于增加或减少较长的数值。InterlockedExchange 函数可允许在单原子操作中进行数值交换。

关键段属于同步原语,可用于通过互斥算法原则保护代码或数据。在执行受保护的代码或访问受保护的内存空间前,线程一定会用到关键段。如果关键段不可用(即另一线程已经获取了该关键段),那么该线程就会被锁定,直到关键段恢复到可用状态为止。请注意,根据应用的需求,一些不同的资源、代码和/或数据可能会受到某一关键段中单一例程的保护。

Visual Studio 属性

请确保正确设置 Visual Studio 中的属性。特别是面向 VC++ 的编译器标记:Project(工程)->Properties(属性)->Configuration Properties(配置属性)->C/C++->Code Generation(代码生成)->Runtime Library(运行时库)->select appropriate multi-threaded library(选择合适的多线程库)。图 4-2 显示了这些选项在运行时库(Runtime Library)中属性页面(Property Pages)的具体位置。


图 4-2. 运行时库段中的属性页面位置

性能

5.1 代码优化

完成工作负载开发后,我们使用了英特尔® VTune™ 性能分析器来进行性能分析。英特尔® VTune™ 性能分析器表明:帮助线程在大多数时间内都在执行函数 LookupTriIndex(),针对每个顶点进行六次三角指数查找,以确定对哪些三角法线计算平均值,从而得出最终的顶点法线。LookupTriIndex() 本身属于线性函数,因此整个法线计算过程都是 O(n2)。现在需要再次执行法线计算过程。我们有两个选择:加快法线查找流程,或利用波动等式的部分导数直接计算法线。为了进行比较,我们对两种选择均进行了实施,并采用了原始算法。


图 5-1. 单路性能与双路性能的比较

图 5-1 显示了三种不同网格的每秒帧数情况。我们将单路(UP)与双路(DP)实施方案进行了比较。工作负载的执行时间有所增加,并模拟了游戏引擎的其它方面,如人工智能、冲突检测等。

5.2 性能分析

我们需要从几方面来讨论性能。为了验证我们的实施方案,我们创建了 3 个工作负载来模拟 3 种不同的场景,每个工作负载所需的处理时间依次增多;处理器 1 将在完成工作后向线程 2 请求结果。在单路情况下,性能会随模拟工作负载的增加而下降。根据我们预计,会有更多的工作负载需要处理,但是只有一颗处理器来执行。请注意,在双路情况下,无论工作负载量如何(无负载、负载 1 或负载 2),性能(每秒帧数)都没有任何差异或仅存在细微差异。换句话说,在双路系统中,只要采用适当的平衡负载解决方案,即使是负载 2 也能流畅运行。但对于 3 个网格来说,负载 3 会导致双路系统中明显的帧速率下降。这表明:在主线程上模拟工作负载消耗的时间要多于在帮助线程上处理网格和法线生成的时间。因此相比几何计算,模拟工作负载对性能的影响要更大一些。

5.3 负载平衡

相对性能方面,我们发现在主线程中没有模拟工作负载时,网格越大,双路的优势就越小。当工作负载 1 和 2 处理较大网格时,我们知道绝对性能没有改变,而相对性能会随着工作负载的增加而相应提升。这时人们就有可能产生疑问:在绝对性能下降的情况下,性能怎么会随之上升呢(如工作负载 3 在两个较大网格上的表现所示)?这一结果表明:工作负载 3 在网格大小上比负载 2 更“接近”理想的平衡状态。我们可以据此推测,理想的工作负载平衡应该介于负载 2 和负载 3 之间。但再观察一下 40x40 的网格,理想的工作负载又似乎应在负载 1 和负载 2 之间。最终结果正如我们所料:当每条线程都具有相对线程开销来说较大的工作负载,且负载间均实现良好平衡时,线程性能达到最佳

未来工作

至此,我们已经完成了对深海波动几何模型的创建,但是要达到真正逼真的海水模拟,还有许多问题需要我们解决。首先,我们需要利用深海波动中的基本原理计算出法线图。这将进一步改进我们的模拟技术,并能更好地表现出风吹过水面产生的较高频率的波动——这不是真正的几何模型,但它具有其它情形中法线图的所有优点和缺陷。在表面光照和阴影方面,改进空间也十分广阔。最重要的是,光照计算需要考虑反射与折射向量。我们还希望加入一些 [8] 中描述的技术,利用高动态范围成像(High Dynamic Range Imaging)技术进一步改进光照效果。此外,我们还希望研究水面泡沫的模拟。

我们也有一些关于线程化的问题尚未讨论。例如,我们希望将该实施方案与循环层分解进行对比。此外,我们还希望了解该实施方案在增加线程时的表现如何、在 CPU 上实施一部分线程化,在 GPU 上实施另一部分线程化的效果,以及利用操作系统级同步对象进行线程同步的表现。

作者简介

Adam Lake 是软件解决方案事业部的一名高级软件工程师,主要负责带领团队专门开发下一代计算机图形算法与架构的现代游戏技术项目(The Modern Game Technologies Project)。阅读更多信息

David Reagin 是英特尔公司的一名软件工程师,他曾连续七年负责微处理器设计的验证工作,之后投身于图形开发与宣传领域。他拥有乔治亚理工学院计算机科学的学士学位。

 

参考资料

1. [Cohen98] Aaron Cohen 和 Mike Woodring。《Win 32 多线程编程(Win32 Multithreaded Programming)》。O’Reilly & Associates出版。1998 年。

2. [Gomez00] Miguel Gomez。《水面的交互式模拟(Interactive Simulation of Water Surfaces)》。《游戏编程精粹 1(Game Programming Gems 1)》,Mark Deloura 编辑。第187-194页。

3. [Finch04] Mark Finch。《物理模型中的有效水波模拟(Effective Water Simulation from Physical Models)》。《GPU 精粹:实时图形编程的技术、技巧与诀窍(GPU Gems:Programming Techniques, Tips, and Tricks for Real-Time Graphics)》。Randima Fernando 编辑。第 5-29 页。2004 年。

4. [Isidoro02] John Isidoro、Alex Vlachos 和 Chris Brennan。《海水渲染(Rendering Ocean Water)》。《Direct3D ShaderX:顶点和像素着色器的技巧和诀窍(Direct3D ShaderX: Vertex and Pixel Shader Tips and Tricks)》。第 347-356 页。2002 年。

5. [Tessendorf01] 《海水模拟(Simulating Ocean Water)》。《SIGGRAPH2001 会议记录(SIGGRAPH2001 Course Notes)》。2001 年。

6. [IMDB04] 《泰坦尼克号》致谢名单。http://us.imdb.com/title/tt0120338/

7. [Vterrain04] 网站:http://www.vterrain.org/Water/。水波模拟索引。2004 年。

8. [Lake04] Adam Lake 和 Cody Northrop,《实时高动态范围环境映射(Real-Time High Dynamic Range Environment Mapping)》,即将出版。

9. [QuickMath04]http://www.quickmath.com/。2004 年 11 月 4 日。

10. [Giancoli85] Douglas Giancoli。《物理学原理与应用(Physics, Principles with Application)》第二版。Prentice Hall 出版公司,1985 年。

11. [MSSDK04] 微软公司 DirectX 9.0 SDK 2004 年夏季更新版。http://www.microsoft.com/downloads/search.aspx?displaylang=en&categoryid=2。2004 年 8 月。

 

其它图片

WaveDemo code sample (ZIP 2.9 MB)


[1] 我们并不否认该方案的可行性,但它要比我们选择的显式解法更难一些。

有关编译器优化的更完整信息,请参阅优化通知