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