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  阅读次数:10824

2006 - 2024,推荐分辨率 1024*768 以上,推荐浏览器 Chrome、Edge 等现代浏览器,截止 2021 年 12 月 5 日的访问次数:1872 万 9823 站长邮箱

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号