1.介绍
在电子游戏中模拟速度的一种最好的方法就是使用运动模糊。运动模糊是游戏中最重要的效果之一,尤其是在赛车游戏中,因为它可以增加真实感和速度感。运动模糊还可以帮助游戏画面的平滑,尤其是对于帧速小于等于30的游戏来说。但是,在已有的游戏引擎加入运动模糊是具有挑战性的,因为大多数运动模糊技术需要重新渲染场景,以产生基于每个像素的速度缓冲。这种多轮渲染的方法是具有限制性的:许多应用程序在将场景多次送往图形管线时,并不能保持原有的帧速。
其他生成基于每个像素的速度映射图的方法包括使用多个渲染目标,然后将速度信息输出到其中一个渲染目标。这种方法的主要不足在于需要改变场景的着色器,增加相应的代码,以计算速度并将其输出到第二个渲染目标。这种方法的另一个不足在于渲染到多个目标在一些平台上可能降低性能。另外,一些平台只有有限的渲染内存,要求一种机制来在1280*720或者更大的帧缓存上使用多个渲染目标。
在这章中,我们将介绍一项使用深度缓冲作为纹理输入到片段着色器程序来生成场景速度映射图的技术。片段着色器程序使用深度值和视图投影矩阵,为每一个像素计算世界坐标中的位置,而深度值保存在深度缓存中。一旦我们计算出该像素在世界坐标中的位置,我们就可以使用前一帧的视图投影矩阵对它进行变换。然后我们可以计算当前帧和前一帧中视口坐标中该位置的差别,以生成每一个像素的速度值。运动模糊的效果可以通过使用速度向量作为方向来组合帧缓存上的多重采样,然后进行归一化最后产生模糊的效果。
这项技术的好处在于可以放在后期处理阶段。这将使得这项技术可以很容易地集成到已有的图形引擎中,这些引擎所运行的硬件可能允许在深度缓存上采样作为纹理。
图1和图2展示了使用运动模糊前后的区别。
图1 使用了运动模糊的场景
图2 没有使用运动模糊的场景
2.从深度缓存提取物体的位置
当一个物体被渲染并且它的深度值被写入深度缓存时,保存在深度缓存的值是三角面片的z值的插值,该z值是这样计算出来的:首先三角形的三个顶点被世界视图投影矩阵转换,然后xyz坐标被齐次坐标中的w值所除。通过使用深度缓存作为纹理,我们可以提取出渲染到深度缓存的物体的世界坐标中的位置。我们先将视口中的位置使用视图投影矩阵的逆矩阵进行转化,然后用w值乘以结果,得出对应的世界坐标中的位置。令视口位置为像素在视口空间的位置,也就是说,x、y坐标的值在-1到1的范围内,而原点在屏幕的中心。那个像素在深度缓存中的深度值作为z值,而w值设为1。
我们可以将某一像素的视口位置定义为H:
令M为世界视图投影矩阵,而W为该像素的世界坐标:
下面是HLSL/Cg代码:
// Get the depth buffer value at this pixel.
float zOverW = tex2D(depthTexture, texCoord);
// H is the viewport position at this pixel in the range -1 to 1.
float4 H = float4(texCoord.x * 2 - 1, (1 - texCoord.y) * 2 - 1,
zOverW, 1);
// Transform by the view-projection inverse.
float4 D = mul(H, g_ViewProjectionInverseMatrix);
// Divide by w to get the world position.
float4 worldPos = D / D.w;
一旦我们计算出世界坐标,我们可以使用前一帧的视图投影坐标对其进行变换,然后得到屏幕位置上的差别,以计算像素的速度,代码如下:
// Current viewport position
float4 currentPos = H;
// Use the world position, and transform by the previous view-
// projection matrix.
float4 previousPos = mul(worldPos, g_previousViewProjectionMatrix);
// Convert to nonhomogeneous points [-1,1] by dividing by w.
previousPos /= previousPos.w;
// Use this frame's position and last frame's to compute the pixel
// velocity.
float2 velocity = (currentPos - previousPos)/2.f;
请求深度缓存作为纹理使用的方法取决于使用的平台,依赖于图形API。访问深度缓存作为纹理的细节在Gilham 2006中有详细讨论。如果硬件不支持从深度缓存中采样作为纹理的话,就要使用多个渲染目标然后将深度输出到一个单独的渲染目标,或者将深度值输出到颜色缓存的alpha通道。
3.运行运动模糊
一旦我们有了像素的速度,我们可以在颜色缓存中沿着那个方向采样,累计颜色值来得到运动模糊的值,如下代码:
// Get the initial color at this pixel.
float4 color = tex2D(sceneSampler, texCoord);
texCoord += velocity;
for(int i = 1; i < g_numSamples; ++i, texCoord += velocity)
{
// Sample the color buffer along the velocity vector.
float4 currentColor = tex2D(sceneSampler, texCoord);
// Add the current color to our color sum.
color += currentColor;
}
// Average all of the samples to get the final blur color.
float4 finalColor = color / numSamples;
我们可以在图3中看到运行结果,注意到接近观察者的地形比远处的地形更模糊。
图3 运动模糊的运行结果
4.处理动态的物体
这项技术使用在静止的物体上效果非常好,因为它只考虑到摄像机的运动。但是,如果需要更加精确地记录动态物体的速度,那么我们可以生成独立的速度纹理。
为了生成缸体动态物体的速度纹理,首先要使用当前帧的视图投影矩阵以及前一帧的视图投影矩阵对物体进行变换,然后计算视口位置的差异。接着将两个变换后的位置传入片段着色器,并计算速度。这项技术在DirectX 9 SDK's 的运动模糊示例中被描述。
5.屏蔽的物体
取决于应用程序,你可能需要屏蔽场景的某个部分,不对此部分使用运动模糊。比如,在赛车游戏中,你需要保持所有车辆的清晰度和细节,而不对其进行模糊。一种简单的方法是渲染一个掩模到一个单独的纹理或者到颜色缓存的alpha通道,然后使用这个掩模来决定什么像素应该被模糊。
6.额外的工作
使用深度缓存计算物体的世界坐标的技术是非常有用的。我们可以使用这项技术实现深度域的效果,正如Gilham 2006中所描述,还有场景雾也可以使用深度缓存作为后期处理来实现。
7.总结
在这章中,我们讨论了一种使用深度缓存中的深度值来计算物体的世界坐标的方法,并且介绍了那些信息如何用于在一个游戏引擎中实现运动模糊的效果。将运动模糊作为后期处理实现的技术可以很简单地集成到一个已有的渲染引擎中,并能够提供比传统的多轮解决方法更好的性能。
8.参考文档
[1]Gilham, David. 2006. "Real-Time Depth-of-Field Implemented with a Post-Processing Only Technique." In Shader X5, edited by Wolfgang Engel, pp. 163–175. Charles River Media.
[2]Microsoft Corporation. 2006. "DirectX 9.0 Programmer's Reference."