转载:http://www.cnblogs.com/kenkao/archive/2011/08/25/2153752.html
感谢原创者。
又称“全屏泛光”,是大名鼎鼎的虚幻3游戏引擎中最通用的后期特效技术~
Bloom特效的实现主要依赖于PostProcess框架,即实时绘制当前场景到一后台渲染表面,而后针对其对应贴图进行像素级渲染~
大家还记得我们之前实现的水面效果中的反射和折射贴图吗?此即为PostProcess的典型应用,与Bloom特效有异曲同工之妙。
下面,我们就来揭秘这个Bloom特效的实现流程~
本节我们实现的Bloom特效,在流程上总共分为4步:
1.提取原场景贴图中的亮色;
2.针对提取贴图进行横向模糊;
3.在横向模糊基础上进行纵向模糊;
4.所得贴图与原场景贴图叠加得最终效果图。
所用到的Shader主要有三个,分别对应如上4个步骤中的第1步、第2、3步和第4步。我们来看源代码:
BloomExtract.fx
/*
-------------------------------------
代码清单:BloomExtract.fx
来自:
http://create.msdn.com/en-US/
(AppHub)
-------------------------------------
*/
//
提取原始场景贴图中明亮的部分
//
这是应用全屏泛光效果的第一步
sampler TextureSampler : register(s0);
float
BloomThreshold;
float4 ThePixelShader(float2 texCoord : TEXCOORD0) : COLOR0
{
//
提取原有像素颜色
float4 c
=
tex2D(TextureSampler, texCoord);
//
在BloomThreshold参数基础上筛选较明亮的像素
return
saturate((c
-
BloomThreshold)
/
(
1
-
BloomThreshold));
}
technique BloomExtract
{
pass Pass1
{
PixelShader
=
compile ps_2_0 ThePixelShader();
}
}
GaussianBlur.fx
/*
-------------------------------------
代码清单:GaussianBlur.fx
来自:
http://create.msdn.com/en-US/
(AppHub)
-------------------------------------
*/
//
高斯模糊过滤
//
这个特效要应用两次,一次为横向模糊,另一次为横向模糊基础上的纵向模糊,以减少算法上的时间复杂度
//
这是应用Bloom特效的中间步骤
sampler TextureSampler : register(s0);
#define
SAMPLE_COUNT 15
//
偏移数组
float2 SampleOffsets[SAMPLE_COUNT];
//
权重数组
float
SampleWeights[SAMPLE_COUNT];
float4 ThePixelShader(float2 texCoord : TEXCOORD0) : COLOR0
{
float4 c
=
0
;
//
按偏移及权重数组叠加周围颜色值到该像素
//
相对原理,即可理解为该像素颜色按特定权重发散到周围偏移像素
for
(
int
i
=
0
; i
<
SAMPLE_COUNT; i
++
)
{
c
+=
tex2D(TextureSampler, texCoord
+
SampleOffsets[i])
*
SampleWeights[i];
}
return
c;
}
technique GaussianBlur
{
pass Pass1
{
PixelShader
=
compile ps_2_0 ThePixelShader();
}
}
BloomCombine.fx
/*
-------------------------------------
代码清单:BloomCombine.fx
来自:
http://create.msdn.com/en-US/
(AppHub)
-------------------------------------
*/
//
按照特定比例混合原始场景贴图及高斯模糊贴图,产生泛光效果
//
这是全屏泛光特效的最后一步
//
模糊场景纹理采样器
sampler BloomSampler : register(s0);
//
原始场景纹理及采样器定义
texture BaseTexture;
sampler BaseSampler
=
sampler_state
{
Texture
=
(BaseTexture);
MinFilter
=
Linear;
MagFilter
=
Linear;
MipFilter
=
Point;
AddressU
=
Clamp;
AddressV
=
Clamp;
};
float
BloomIntensity;
float
BaseIntensity;
float
BloomSaturation;
float
BaseSaturation;
//
减缓颜色的饱和度
float4 AdjustSaturation(float4 color,
float
saturation)
{
//
人眼更喜欢绿光,因此选取0.3, 0.59, 0.11三个值
float
grey
=
dot(color, float3(
0.3
,
0.59
,
0.11
));
return
lerp(grey, color, saturation);
}
float4 ThePixelShader(float2 texCoord : TEXCOORD0) : COLOR0
{
//
提取原始场景贴图及模糊场景贴图的像素颜色
float4 bloom
=
tex2D(BloomSampler, texCoord);
float4
base
=
tex2D(BaseSampler, texCoord);
//
柔化原有像素颜色
bloom
=
AdjustSaturation(bloom, BloomSaturation)
*
BloomIntensity;
base
=
AdjustSaturation(
base
, BaseSaturation)
*
BaseIntensity;
//
结合模糊像素值微调原始像素值
base
*=
(
1
-
saturate(bloom));
//
叠加原始场景贴图及模糊场景贴图,即在原有像素基础上叠加模糊后的像素,实现发光效果
return
base
+
bloom;
}
technique BloomCombine
{
pass Pass1
{
PixelShader
=
compile ps_2_0 ThePixelShader();
}
}
三个Shader均来自于微软WP7开发者俱乐部,如有引用,敬请标明AppHub字样及其站点网址:http://create.msdn.com/en-US/,以示对作者原创版权的尊重!
具备相应的Shader之后,下面我们来看如何运用他们来实现Bloom特效的四个关键步骤~
因为最终的效果贴图本质上是一张与屏幕大小相同的纹理,因此,我们使用原来构建的CSpriteBatch进行绘制。而CSpriteBatch提供的接口是针对于CTexture2D的,所以我们首先要增强并完善CTexture2D类的功能~
以下提供两个函数,使得CTexture2D可以直接产生渲染贴图,并允许获取其后台渲染表面:
bool
CTexture2D::CreateRenderTarget(UINT SizeX, UINT SizeY)
{
//
创建渲染贴图
HRESULT hr;
hr
=
D3DXCreateTexture(g_pD3DDevice, SizeX, SizeY,
1
,
D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT,
&
m_pTexture);
if
(FAILED(hr))
return
false
;
m_Width
=
SizeX;
m_Height
=
SizeY;
m_SurRect.top
=
0
;
m_SurRect.left
=
0
;
m_SurRect.right
=
m_Width;
m_SurRect.bottom
=
m_Height;
return
true
;
}
bool
CTexture2D::GetRenderSurface(IDirect3DSurface9
**
pOutSurface)
{
//
获得贴图的渲染表面
HRESULT hr;
hr
=
m_pTexture
->
GetSurfaceLevel(
0
, pOutSurface);
if
(FAILED(hr))
return
false
;
else
return
true
;
}
之后,就可以着手构建我们的CBloomEffect效果类了:
/*
-------------------------------------
代码清单:BloomEffect.h
来自:
http://www.cnblogs.com/kenkao
-------------------------------------
*/
#include
"
D3DEffect.h
"
#include
"
Texture2D.h
"
#pragma
once
//
Bloom参数体
typedef
struct
_BloomParam
{
char
*
_Name;
//
Bloom特效名称
float
_BloomThreshold;
//
饱和度
float
_BlurAmount;
//
模糊程度
float
_BloomIntensity;
//
模糊剧烈度
float
_BaseIntensity;
//
原始剧烈度
float
_BloomSaturation;
//
模糊饱和度
float
_BaseSaturation;
//
原始饱和度
_BloomParam(){}
_BloomParam(
char
*
name,
float
bloomThreshold,
float
blurAmount,
float
bloomIntensity,
float
baseIntensity,
float
bloomSaturation,
float
baseSaturation)
{
_Name
=
name; _BloomThreshold
=
bloomThreshold; _BlurAmount
=
blurAmount;
_BloomIntensity
=
bloomIntensity; _BaseIntensity
=
baseIntensity;
_BloomSaturation
=
bloomSaturation; _BaseSaturation
=
baseSaturation;
}
}BloomParam;
class
CBloomEffect
{
public
:
CBloomEffect(
void
);
~
CBloomEffect(
void
);
public
:
void
Create();
//
创建Bloom特效
void
Release();
//
释放Bloom特效
void
DrawScene();
//
场景绘制
void
Begin();
//
开启Bloom
void
End();
//
关闭Bloom
public
:
void
SetParam(BloomParam
*
pParam){m_pParam
=
pParam;}
//
参数体设置
BloomParam
*
GetParam() {
return
m_pParam;}
//
参数体获取
private
:
void
PostScene();
//
Bloom场景投递
void
PostSurface(
//
应用特效投递贴图到特定缓存表面,本质就是将一个贴图应用Shader之后的效果写入另一个贴图
CTexture2D
*
pTexture,
//
后台纹理
IDirect3DSurface9
*
pSurface,
//
渲染表面
CD3DEffect
*
pEffect);
//
目标特效
bool
LoadContent();
//
加载内容
bool
GetSurfaces();
//
获取渲染表面
private
:
float
ComputeGaussian(
float
n);
//
计算高斯模糊参数
void
SetBlurEffectParam(
float
dx,
float
dy);
//
计算偏移数组及权重数组
private
:
CD3DEffect
*
m_pBloomExtractEffect;
//
Bloom依次用到的三个特效
CD3DEffect
*
m_pGaussianBlurEffect;
CD3DEffect
*
m_pBloomCombineEffect;
private
:
CTexture2D
*
m_pResolveTarget;
//
原始贴图
CTexture2D
*
m_pTexture1;
//
模糊贴图
CTexture2D
*
m_pTexture2;
//
临时模糊贴图
IDirect3DSurface9
*
m_pResolveSurface;
//
原始贴图渲染表面
IDirect3DSurface9
*
m_pSurface1;
//
模糊贴图渲染表面
IDirect3DSurface9
*
m_pSurface2;
//
临时贴图渲染表面
IDirect3DSurface9
*
m_pOriSurface;
//
初始渲染表面
private
:
BloomParam
*
m_pParam;
//
Bloom参数体
};
BloomEffect.cpp
该类共产生了3个渲染表面,并在渲染过程中发生了相互叠加,嗯…有点混乱…
我们不妨来看一看每个步骤所得到的渲染效果,而后再针对4个步骤进行分析。如下是我在渲染过程中提取的效果图:
有了这个流程图,大家的思路是不是清晰了一些呢?下面,大家结合这个流程图来分析各个步骤:
第一步:
应用BloomExtract特效提取原始场景贴图m_pResolveTarget中较明亮的颜色绘制到m_pTexture1贴图中(m_pResolveTarget--->m_pTexture1)
第二步:
应用GaussianBlur特效,在m_pTexture1贴图基础上进行横向高斯模糊到m_pTexture2贴图(m_pTexture1--->m_pTexture2)
第三步:
再次应用GaussianBlur特效,在横向模糊之后的m_pTexture2贴图基础上进行纵向高斯模糊,得到最终的模糊贴图m_pTexture1(m_pTexture2--->m_pTexture1)
注意:此时,m_pTexture1贴图即为最终的模糊效果贴图
如果大家不明白高斯模糊的内部原理,可以参看老师为大家翻译的这篇文章:http://shiba.hpe.sh.cn/jiaoyanzu/WULI/showArticle.aspx?articleId=518&classId=4
第四步:
应用BloomCombine特效,叠加原始场景贴图m_pResolveTarget及两次模糊之后的场景贴图m_pTexture1,从而实现发光效果(m_pResolveTarget+m_pTexture1)
怎么样?大家明白了吗?呵呵~
我们来看主体代码:
D3DGame.cpp
对Bloom参数体各个成员变量数值进行交叉组合,则我们可以得到一系列不同的Bloom效果:
还在羡慕那些专业游戏中美轮美奂的后期特效吗?现在我们也有能力实现了~ 大家赶快动手尝试一下吧 ^ ^
以上,谢谢~