学习OpenGL过程中

发现运动模糊这种画面加强特效十分有趣

运动模糊可以从几个方面来理解

1.人眼看到快速移动的东西看不清,就是运动模糊

2.相机拍照快速移动的东西,画面虚化,是更加直观的运动模糊

3.在游戏中,移动物体周围,用算法加入加强的模糊效果,提高画面表现力,这是我们下面要讨论的内容

 转载注明http://www.cnblogs.com/billyrun/articles/5945653.html

 源码地址https://github.com/novemrain/Cocos-OpenGL

运动模糊原理

1.在一帧里面多次绘制运动物体

在当前帧取得运动物体此前若干帧的顶点变换信息,

一并进行渲染(渲染多次)

需要控制每一帧的强度,需要适当虚化(减弱颜色和透明度)才能还原运动模糊效果

这种方式会出现明显的拖影

 

2.纹理采样加权模糊

这种模糊效果在FragmentShader中实现

获取运动速度,并把速度作为一个向量传入shader

纹理采样时,在速度方向上采样多次并取颜色(加权)平均值

这种方式会出现明显的模糊

 

3.通过glReadPixels(PBO)

OpenGL超级宝典上的实例方法

大概思路是读取/保存像素

然后把前几帧的像素绘制在屏幕上

与方法1比较相似但也有差别

方法1保存了之前的变换信息进行再次绘制

本方法在绘制本帧时直接保存了像素信息

很直观的这样可以做全屏幕的模糊

不知道能不能做单一物体的模糊(对背景等不需要模糊的部分不做重复绘制)

 

运动模糊实现

以下我们结合cocos,使用方法1实现Sprite/Armature的运动模糊

运动模糊开启后,元素会在Move,Jump等动作时表现明显的拖影效果

 

1.Shader

const char* motionBlur_frag = STRINGIFY(
varying vec4 v_fragmentColor;
varying vec2 v_texCoord;
uniform float u_vOpacity;
void main()
{
    vec4 color0 = texture2D(CC_Texture0, v_texCoord);
    gl_FragColor = v_fragmentColor * color0 * u_vOpacity;
}
);

与Sprite所使用的shader相比,多乘了一个u_vOpacity

避免多次混合导致图像颜色过强

 

2.渲染次数的控制

首先定义一个最大的模糊渲染次数

比如说10次,那么我们需要最多保存10个过去的变换

其次还需要知道当前的模糊渲染次数

物体运动的不同阶段,身后的模糊是不同的

刚运动时拖影数是1,2,3,4,5..这样递增的

// motion blur
    // blur effect on/off
    bool bMotionBlur;
    // current blurs
    int currentMotions;
    // blurs to be set
    unsigned short uFrame;
    // transforms for blurs
    cocos2d::Mat4 motions[MOTION_FRAME_MAX];

注意这里uFrame才是本次设置的最大模糊渲染次数

cocos2d::Mat4 motions[MOTION_FRAME_MAX]并不一定是满的!

 

3.运动模糊开启与关闭

void MotionBlurSprite::setMotionBlur(bool bBlur, unsigned short frame)
{
    // 目前的做法,直接切换了shader不会保存最后几帧
    if (bMotionBlur != bBlur)
    {
        bMotionBlur = bBlur;
        currentMotions = 0;
    }
    uFrame = frame;

    if (bMotionBlur)
    {
        setGLProgramState(GLProgramState::getOrCreateWithGLProgramName(GLProgram::SHADER_NAME_MOTION_BLUR));
        auto program = getGLProgram();
        _uvOpacityLoc = glGetUniformLocation(program->getProgram(), "u_vOpacity");
        setIntensity(1.5f);
    }
    else
    {
        setGLProgramState(GLProgramState::getOrCreateWithGLProgramName(GLProgram::SHADER_NAME_POSITION_TEXTURE_COLOR_NO_MVP));
    }
}

注意切换时,尤其是从关到开,一定要把currentMotions置0

防止以前的过期拖影重复显示在运动初始阶段

确保运动的初始阶段是"干净"的

 

4.模糊特效的绘制顺序

先绘制快要过期的拖影,再绘制最近的拖影,最后绘制本帧

这样的顺序保证了正确的显示层级

在本帧绘制完成后,还要更新运动模糊的transform数组

void MotionBlurSprite::onDraw(const Mat4 &transform, uint32_t flags)
{
    //1.渲染顺序反向
    //2.混合颜色透明度调整

    auto glProgramState = getGLProgramState();
    glProgramState->apply(transform);
    GL::blendFunc(_blendFunc.src, _blendFunc.dst);

    GL::bindTexture2D(_texture->getName());
    GL::enableVertexAttribs(GL::VERTEX_ATTRIB_FLAG_POS_COLOR_TEX);

#define kQuadSize sizeof(_quad.bl)
    size_t offset = (size_t)&_quad;

    // 对于vertex绑定我们变换过的顶点信息
    // 对于texCoods和color绑定_quad中的信息
    // vertex
    int diff = offsetof(V3F_C4B_T2F, vertices);
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_POSITION, 3, GL_FLOAT, GL_FALSE, kQuadSize, (void*)(offset + diff));
    // texCoods
    diff = offsetof(V3F_C4B_T2F, texCoords);
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_TEX_COORD, 2, GL_FLOAT, GL_FALSE, kQuadSize, (void*)(offset + diff));
    // color
    diff = offsetof(V3F_C4B_T2F, colors);
    glVertexAttribPointer(GLProgram::VERTEX_ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, kQuadSize, (void*)(offset + diff));

    

    Mat4 currentTransform;
    bool bLast = false;
    for (int i = currentMotions - 1; i >= 0 || bLast == false; i--)
    {
        bLast = i < 0;
        currentTransform = bLast ? transform : motions[i];
        float fOpacity = 1.0f / (currentMotions + 1);
        if (bMotionBlur)fOpacity = MIN(fOpacity * _intensity, 1.0f);
        glUniform1f(_uvOpacityLoc, fOpacity);
        //绘制三角形
        glProgramState->apply(currentTransform);
        glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
        CHECK_GL_ERROR_DEBUG();
        CC_INCREMENT_GL_DRAWN_BATCHES_AND_VERTICES(1, 4);
    }

    // 处理运动模糊帧
    if (bMotionBlur) currentMotions = MIN(currentMotions + 1, uFrame);
    else currentMotions = MAX(currentMotions - 1, 0);

    for (int i = currentMotions - 1; i >= 0; i--)
    {
        motions[i] = i == 0 ? transform : motions[i - 1];
    }
}

 

5.Armature处理办法

Armature由一个个Bone组成

Bone包含了Skin,Skin的处理方式和Sprite十分相似

处理Skin之后,Armture会在各种Action(Move/Jump)发生时出现模糊效果

而在动画播放的本身不会产生模糊(动画播放的过程中transform不改变)

 

我们为Armature添加一个运动模糊的开关

CC_SYNTHESIZE(bool, bMotionBlur, MotionBlur);

然后在Armature::draw方法中,将bMotionBlur值传入每一个Skin

这样来控制Skin是否表现模糊效果

 

6.Sprite/Armature运动模糊效果图

 

 转载注明http://www.cnblogs.com/billyrun/articles/5945653.html

 源码地址https://github.com/novemrain/Cocos-OpenGL