XNA Shader编程教程20 – 景深
原文地址:http://digierr.spaces.live.com/blog/cns!2B7007E9EC2AE37B!662.entry。

本示例展示了一个实现景深效果的简单后期处理shader,虽然景深效果的计算量很大,但这个示例并不慢。这个shader是一个后期处理effect,它可以很容易地在任何场景中实现。
景深(Depth of Field)
景深( DoF )是一个你几乎每天都会遇到的效果。如果你盯着一个物体看,你会发现这个物体的前景和背景会变得模糊。使用相机时也会遇到相同的情况,这时你需要调整相机的焦距让照片不至变得模糊。下面是一张具有景深效果的照片:

瓶盖在焦距上(in-focus),背景在焦点之外,叫做散焦(out-of-focus)。in-focus平面叫做焦平面(in-focal-plane)。
那么我们如何在XNA中实现以上效果?
首先我们需要知道场景中任一点的深度,这可以使用深度缓冲实现(可参见XNA Shader编程教程14-透射)。

然后我们需要一张纹理的两个版本,一个是处于焦点之上的正常场景,一个是落在焦点之外的模糊过的场景。
要模糊一个场景,我们需要实现一个新的后期处理shader(post process shader)!这个shader会提取周围像素颜色的平均值。模糊shader会使用一个距离变量调整如何采样包含场景的纹理坐标,它会提取当前像素的左上、右上、左下和右下的像素,相加并除以4,这样就获得了它们的平均值。
下图中我们可以看到实际的模糊效果。

这个shader根据shader中设置的距离(本例中为0.003)计算了周围像素的平均值。
然后,要让场景模糊得更好,我们将模糊过的纹理绘制到另一张也使用模糊shader的纹理,这样场景就被模糊了两次。
现在我们获得了三张纹理。一张包含原始的场景,一张包含经过两次模糊的场景和一张深度纹理,那接下去做什么呢?我们需要在焦距之外显示模糊过的场景,落在焦距上的绘制正常的场景,两者之间的场景则需要对这两张纹理进行混合。
要做到这一步,我们需要知道如何表示焦点(离开眼睛的距离),聚焦范围(从in-focus到out-of-focus的距离),近裁平面和修改过的远裁平面。修改过的远裁平面是这样计算的:将给定的远裁平面(假设为)除以( Far - Near ),如果近裁平面为1,则结果为300/(300-1)。这样,使用下面的方程就可以将远裁平面转换到正确的[0,1]区间中。
下面的公式可以根据给定的焦点距离和范围计算像素是否在焦点上:

这个公式中,我们取负的近裁平面,并将它乘以修改过的远裁平面,然后除以离开焦点的距离,但这个距离还要减去修改过的远裁平面。然后使用这个结果计算这个点离开shader中设置的distance点的距离,并基于Range的值进行淡出。
以上操作会返回一个介于0至1之间的值,0表示在焦点上,1表示在焦点外。本例中聚焦范围很小( 10 ),焦点离眼睛的距离为70,截图如下:

现在我们就可以使用这个纹理了,其中白色表示模糊的场景,黑色表示正常的场景,灰色表示两者的混合。
实现shader
在程序中有3个shader。第一个是基于教程14的深度纹理shader,第2个是模糊后期处理shader,最后一个是景深shader。
首先是模糊shader:
// 模糊程度( 即提取当前像素的周围像素的范围)
float BlurDistance = 0.003f;
// This will use the texture bound to the object( like from the sprite batch ).
sampler ColorMapSampler : register(s0);
float4 PixelShader(float2 Tex: TEXCOORD0) : COLOR
{
float4 Color;
// 使用修改过的纹理坐标从ColorMapSampler采样颜色,并将颜色叠加。
Color = tex2D( ColorMapSampler, float2(Tex.x+BlurDistance, Tex.y+BlurDistance));
Color += tex2D( ColorMapSampler, float2(Tex.x-BlurDistance, Tex.y-BlurDistance));
Color += tex2D( ColorMapSampler, float2(Tex.x+BlurDistance, Tex.y-BlurDistance));
Color += tex2D( ColorMapSampler, float2(Tex.x-BlurDistance, Tex.y+BlurDistance));
// 我们需要将颜色除以叠加次数(本例中为4)以获得平均颜色
Color = Color / 4;
// 返回经过模糊的颜色
return Color;
}
这里我们根据给定的范围( 0.003 )获取了邻近像素的平均值并返回,获得了一个模糊过的场景。
然后我们需要实现深度shader。
我们首先需要声明一些变量用来计算像素是否在焦点上:
float Distance; float Range; float Near; float Far;
然后需要设置纹理的纹理采样器:
sampler SceneSampler : register(s0);
texture D1M;
sampler D1MSampler = sampler_state
{
Texture = ;
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Linear;
AddressU = Clamp;
AddressV = Clamp;
};
texture BlurScene;
sampler BlurSceneSampler = sampler_state
{
Texture = ;
MinFilter = Linear;
MagFilter = Linear;
MipFilter = Linear;
AddressU = Clamp;
AddressV = Clamp;
};
现在就做好了实现后期处理effect的准备:
float4 PixelShader(float2 Tex: TEXCOORD0) : COLOR
{
// 获取正常场景的颜色
float4 NormalScene = tex2D(SceneSampler, Tex);
// 获取模糊过的场景的颜色
float4 BlurScene = tex2D(BlurSceneSampler, Tex);
// 获取深度纹理
float fDepth = tex2D(D1MSampler, Tex).r;
// 颠倒深度,这样背景为白色,近处为黑色
fDepth = 1 - fDepth;
// Calculate the distance from the selected distance and range on our DoF effect, set from the application
float fSceneZ = ( -Near * Far ) / ( fDepth - Far);
float blurFactor = saturate(abs(fSceneZ-Distance)/Range);
// Based on how far the texel is from "distance" in Distance, stored in blurFactor, mix the scene
return lerp(NormalScene,BlurScene,blurFactor);
}
上述代码中,我们从正常的场景纹理、模糊过的场景纹理和反相过(因为我们想让远处的物体呈现为白色,近处的物体呈现为黑色)的深度纹理中提取像素颜色:
float fSceneZ = ( -Near * Far ) / ( fDepth - Far); float blurFactor = saturate(abs(fSceneZ-Distance)/Range);
这里我们使用了公式20.1计算像素是否在焦点上,并将当前像素的结果放置在blurFactor中。
现在使用blurFactor,我们就可以混合正常场景(如果blurFactor为0则完全可见)和模糊过的场景(如果blurFactor为1则完全可见)。
return lerp(NormalScene,BlurScene,blurFactor);
好了以上就是shader代码!
使用shader
要使用这个shader,我们需要绘制深度缓冲纹理、法线纹理和模糊化的场景纹理。然后我们需要设置shader参数,让它包含我们需要的距离和范围。本例中,我们知道相机离开物体中心的距离为80,所以我们将焦点距离设置为70,范围设置为10。
void SetShaderParameters( float fD, float fR, float nC, float fC )
{
focusDistance = fD;
focusRange = fR;
nearClip = nC;
farClip = fC;
farClip = farClip / ( farClip - nearClip );
effectPostDoF.Parameters["Distance"].SetValue(focusDistance);
effectPostDoF.Parameters["Range"].SetValue(focusRange);
effectPostDoF.Parameters["Near"].SetValue(nearClip);
effectPostDoF.Parameters["Far"].SetValue(farClip);
}
以上就是只使用一个pass的景深shader。如你所见,它非常简单也无需太多的运算,性能损失不多。
本例中,你可以使用Xbox手柄右摇杆改变焦点距离和范围,左摇杆旋转模型。
文件下载(已下载 2328 次)发布时间:2010/6/25 上午11:53:36 阅读次数:11484
