运动模糊在现如今的 3D 游戏中是一项非常流行的技术,他主要是为运动的物体添加一个模糊效果,增强了玩家真实感。运动模糊有多种实现方式,有的是基于相机来实现,主要专注于相机的运动;有的是基于对象的,在这一课中会学习其中一种方法来完成这个功能。
运动模糊背后的原理就是我们可以计算出两帧之间每个像素运动的向量(即运动矢量)。通过沿着这个运动矢量在当前颜色缓存中进行采样并对他们取平均值就能得到代表当前物体运动的像素。这就是我们需要完成的全部,下面我们总结一下实现运动模糊的具体步骤。
这一课中是基于骨骼动画( 38 课)的,这里我们会回顾实现运动模糊所需要增加的代码。
(tutorial41.cpp:157) |
virtual void RenderSceneCB() |
{ |
CalcFPS(); |
m_pGameCamera->OnRender(); |
RenderPass(); |
MotionBlurPass(); |
RenderFPS(); |
glutSwapBuffers(); |
} |
(tutorial41.cpp:172) |
void RenderPass() |
{ |
m_intermediateBuffer.BindForWriting(); |
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
m_pSkinningTech->Enable(); |
vector Transforms; |
float RunningTime = (float)((double)GetCurrentTimeMillis() - (double)m_startTime) / 1000.0f; |
m_mesh.BoneTransform(RunningTime, Transforms); |
for (uint i = 0 ; i < Transforms.size() ; i++) { |
m_pSkinningTech->SetBoneTransform(i, Transforms[i]); |
m_pSkinningTech->SetPrevBoneTransform(i, m_prevTransforms[i]); |
} |
m_pSkinningTech->SetEyeWorldPos(m_pGameCamera->GetPos()); |
m_pipeline.SetCamera(m_pGameCamera->GetPos(), m_pGameCamera->GetTarget(), m_pGameCamera->GetUp()); |
m_pipeline.SetPerspectiveProj(m_persProjInfo); |
m_pipeline.Scale(0.1f, 0.1f, 0.1f); |
Vector3f Pos(m_position); |
m_pipeline.WorldPos(Pos); |
m_pipeline.Rotate(270.0f, 180.0f, 0.0f); |
m_pSkinningTech->SetWVP(m_pipeline.GetWVPTrans()); |
m_pSkinningTech->SetWorldMatrix(m_pipeline.GetWorldTrans()); |
m_mesh.Render(); |
m_prevTransforms = Transforms; |
} |
除此之外你可以看到我们在 'Tutorial41' 类中添加了成员变量用于保存上一帧中的骨骼变换信息,我们同样会把这个数据传递到骨骼动画着色器中,在介绍到 GLSL 代码时我们就可以看到如何来使用这些信息的了。
(tutorial41.cpp:209) |
void MotionBlurPass() |
{ |
m_intermediateBuffer.BindForReading(); |
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); |
m_pMotionBlurTech->Enable(); |
m_quad.Render(); |
} |
(skinning.vs) |
#version 330 |
layout (location = 0) in vec3 Position; |
layout (location = 1) in vec2 TexCoord; |
layout (location = 2) in vec3 Normal; |
layout (location = 3) in ivec4 BoneIDs; |
layout (location = 4) in vec4 Weights; |
out vec2 TexCoord0; |
out vec3 Normal0; |
out vec3 WorldPos0; |
out vec4 ClipSpacePos0; |
out vec4 PrevClipSpacePos0; |
const int MAX_BONES = 100; |
uniform mat4 gWVP; |
uniform mat4 gWorld; |
uniform mat4 gBones[MAX_BONES]; |
uniform mat4 gPrevBones[MAX_BONES]; |
void main() |
{ |
mat4 BoneTransform = gBones[BoneIDs[0]] * Weights[0]; |
BoneTransform += gBones[BoneIDs[1]] * Weights[1]; |
BoneTransform += gBones[BoneIDs[2]] * Weights[2]; |
BoneTransform += gBones[BoneIDs[3]] * Weights[3]; |
vec4 PosL = BoneTransform * vec4(Position, 1.0); |
vec4 ClipSpacePos = gWVP * PosL; |
gl_Position = ClipSpacePos; |
TexCoord0 = TexCoord; |
vec4 NormalL = BoneTransform * vec4(Normal, 0.0); |
Normal0 = (gWorld * NormalL).xyz; |
WorldPos0 = (gWorld * PosL).xyz; |
mat4 PrevBoneTransform = gPrevBones[BoneIDs[0]] * Weights[0]; |
PrevBoneTransform += gPrevBones[BoneIDs[1]] * Weights[1]; |
PrevBoneTransform += gPrevBones[BoneIDs[2]] * Weights[2]; |
PrevBoneTransform += gPrevBones[BoneIDs[3]] * Weights[3]; |
ClipSpacePos0 = ClipSpacePos; |
vec4 PrevPosL = PrevBoneTransform * vec4(Position, 1.0); |
PrevClipSpacePos0 = gWVP * PrevPosL; |
} |
(skinning.fs:123) |
layout (location = 0) out vec3 FragColor; |
layout (location = 1) out vec2 MotionVector; |
void main() |
{ |
VSOutput In; |
In.TexCoord = TexCoord0; |
In.Normal = normalize(Normal0); |
In.WorldPos = WorldPos0; |
vec4 TotalLight = CalcDirectionalLight(In); |
for (int i = 0 ; i < gNumPointLights ; i++) { |
TotalLight += CalcPointLight(gPointLights[i], In); |
} |
for (int i = 0 ; i < gNumSpotLights ; i++) { |
TotalLight += CalcSpotLight(gSpotLights[i], In); |
} |
vec4 Color = texture(gColorMap, TexCoord0) * TotalLight; |
FragColor = Color.xyz; |
vec3 NDCPos = (ClipSpacePos0 / ClipSpacePos0.w).xyz; |
vec3 PrevNDCPos = (PrevClipSpacePos0 / PrevClipSpacePos0.w).xyz; |
MotionVector = (NDCPos - PrevNDCPos).xy; |
} |
需要注意的是运动向量只是一个 2D 向量,因为他们是位于屏幕这个 2D 平面上的。对应的运动向量缓存的类型我们同样是设置为 GL_RG 。
(motion_blur.vs) |
#version 330 |
layout (location = 0) in vec3 Position; |
layout (location = 1) in vec2 TexCoord; |
out vec2 TexCoord0; |
void main() |
{ |
gl_Position = vec4(Position, 1.0); |
TexCoord0 = TexCoord; |
} |
(motion_blur.fs) |
#version 330 |
in vec2 TexCoord0; |
uniform sampler2D gColorTexture; |
uniform sampler2D gMotionTexture; |
out vec4 FragColor; |
void main() |
{ |
vec2 MotionVector = texture(gMotionTexture, TexCoord0).xy / 2.0; |
vec4 Color = vec4(0.0); |
vec2 TexCoord = TexCoord0; |
Color += texture(gColorTexture, TexCoord) * 0.4; |
TexCoord -= MotionVector; |
Color += texture(gColorTexture, TexCoord) * 0.3; |
TexCoord -= MotionVector; |
Color += texture(gColorTexture, TexCoord) * 0.2; |
TexCoord -= MotionVector; |
Color += texture(gColorTexture, TexCoord) * 0.1; |
FragColor = Color; |
} |