一、 什么是高斯模糊
               首先,当然是先了解什么是高斯模糊。高斯模糊,在Photoshop中是一种滤镜特效,网上有详细的讲解, 笔者就不浪费口舌。

   具体原理,请看 云彩挂上的二叉树 高斯模糊算法的实现和优化,有关于高斯模糊高阶知识。在游戏开发中,可以作为一种很有趣的特效,比如游戏暂停时,将主游戏界面进行模糊,同时,有不至于太过于静态,比如,将特定的界面进行模糊,得到较好的用户体验。

 

  二、 在Libgdx中的实现
  那么,在Libgdx中是怎么实现? 先上效果图对比一下。

             未进行高斯模糊

             进行高斯模糊处理

    本基于libgdx1.5.4开发。

      因为没装好Eclipse Gradle 插件,为了方便,楼主只创建的Desktop工程:

          实际上笔者只是创建一个Demo,在stage绘制一张图片,当然,你也可以单独对一张图片进行渲染。对单个Stage整体进行渲染,并非完整游戏。在此,只需要了解BlurUils.java这个类。该类是笔者将高斯模糊封装为一个工具类。

1.创建GLSL着色器

         为什么使用GLSL?GLSL是OpenGL着色语言,笔者对GLSL语言不甚了解,其语法类似C语言。下面这两个着色器是笔者在官网wiki

https://github.com/mattdesl/lwjgl-basics/wiki/Shaders上找来再修改的。里面有对高斯模糊的详细描述,不过是英文版的

首先是顶点着色器 vertex.vert

//combined projection and view matrix
 uniform mat4 u_projTrans;
//"in" attributes from our SpriteBatch
 attribute vec2 a_position; attribute vec2 a_texCoord0;
attribute vec4 a_color;
//"out" varyings to our fragment shader
varying vec4 vColor;
varying vec2 vTexCoord;
void main() {
vColor = a_color; vTexCoord= a_texCoord0; gl_Position = u_projTrans * vec4(a_position,0.0, 1.0); }

 
u_projTrans  投影和视图矩阵联合,stage的矩阵

a_position 顶点数据

a_texCoord0 传入的纹理

这个着色器实际上是对纹理数据进行初始化

 

片段着色器 fragment.frag

//"in" attributes from our vertex shader
varying vec4 vColor;
varying vec2 vTexCoord;

//declare uniforms
uniform sampler2D u_texture;
uniform float resolution;
uniform float radius;
uniform vec2 dir;

void main() {
    //this will be our RGBA sum
    vec4 sum = vec4(0.0);

    //our original texcoord for this fragment
    vec2 tc = vTexCoord;

    //the amount to blur, i.e. how far off center to sample from 
    //1.0 -> blur by one pixel
    //2.0 -> blur by two pixels, etc.
    float blur = radius/resolution; 

    //the direction of our blur
    //(1.0, 0.0) -> x-axis blur
    //(0.0, 1.0) -> y-axis blur
    float hstep = dir.x;
    float vstep = dir.y;

    //apply blurring, using a 9-tap filter with predefined gaussian weights

    sum += texture2D(u_texture, vec2(tc.x - 4.0*blur*hstep, tc.y - 4.0*blur*vstep)) * 0.0162162162;
    sum += texture2D(u_texture, vec2(tc.x - 3.0*blur*hstep, tc.y - 3.0*blur*vstep)) * 0.0540540541;
    sum += texture2D(u_texture, vec2(tc.x - 2.0*blur*hstep, tc.y - 2.0*blur*vstep)) * 0.1216216216;
    sum += texture2D(u_texture, vec2(tc.x - 1.0*blur*hstep, tc.y - 1.0*blur*vstep)) * 0.1945945946;

    sum += texture2D(u_texture, vec2(tc.x, tc.y)) * 0.2270270270;

    sum += texture2D(u_texture, vec2(tc.x + 1.0*blur*hstep, tc.y + 1.0*blur*vstep)) * 0.1945945946;
    sum += texture2D(u_texture, vec2(tc.x + 2.0*blur*hstep, tc.y + 2.0*blur*vstep)) * 0.1216216216;
    sum += texture2D(u_texture, vec2(tc.x + 3.0*blur*hstep, tc.y + 3.0*blur*vstep)) * 0.0540540541;
    sum += texture2D(u_texture, vec2(tc.x + 4.0*blur*hstep, tc.y + 4.0*blur*vstep)) * 0.0162162162;

    //discard alpha for our simple demo, multiply by vertex color and return
    gl_FragColor = vColor * vec4(sum.rgb, 1.0);
}

      该着色器是以对顶点着色去初始化的纹理数据进行高斯模糊计算。

   实际上,Libgdx中还要对这两个文件读取字符串流,为了方便看代码,就嵌入类中。为什么使用OpenGL着色器?因为,高级渲染需要使用到OpenGL ES2.0进行绘制模糊,所以该渲染只支持Android 2.2及以上的机子。

定义好着色器,就要知道该怎么去使用。首先,我们得了解Libgdx是怎么进行图形渲染的。我们都知道,Libgdx常使用SpriteBatch这个类进行绘制,如果我们查看该类源码

@Override
    public void begin () {
        if (drawing) throw new IllegalStateException("SpriteBatch.end must be called before begin.");
        renderCalls = 0;

        Gdx.gl.glDepthMask(false);
        if (customShader != null)
            customShader.begin();
        else
            shader.begin();
        setupMatrices();

        drawing = true;
    }

    @Override
    public void end () {
        if (!drawing) throw new IllegalStateException("SpriteBatch.begin must be called before end.");
        if (idx > 0) flush();
        lastTexture = null;
        drawing = false;

        GL20 gl = Gdx.gl;
        gl.glDepthMask(true);
        if (isBlendingEnabled()) gl.glDisable(GL20.GL_BLEND);

        if (customShader != null)
            customShader.end();
        else
            shader.end();
    }

我们发现,实际上是customShader或shader这两个在运行,

private final ShaderProgram shader;
private ShaderProgram customShader = null;
这是这两个属性的定义

public SpriteBatch (int size, ShaderProgram defaultShader) {
        // 32767 is max index, so 32767 / 6 - (32767 / 6 % 3) = 5460.
        if (size > 5460) throw new IllegalArgumentException("Can't have more than 5460 sprites per batch: " + size);

        mesh = new Mesh(VertexDataType.VertexArray, false, size * 4, size * 6, new VertexAttribute(Usage.Position, 2,
            ShaderProgram.POSITION_ATTRIBUTE), new VertexAttribute(Usage.ColorPacked, 4, ShaderProgram.COLOR_ATTRIBUTE),
            new VertexAttribute(Usage.TextureCoordinates, 2, ShaderProgram.TEXCOORD_ATTRIBUTE + "0"));

        projectionMatrix.setToOrtho2D(0, 0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());

        vertices = new float[size * Sprite.SPRITE_SIZE];

        int len = size * 6;
        short[] indices = new short[len];
        short j = 0;
        for (int i = 0; i < len; i += 6, j += 4) {
            indices[i] = j;
            indices[i + 1] = (short)(j + 1);
            indices[i + 2] = (short)(j + 2);
            indices[i + 3] = (short)(j + 2);
            indices[i + 4] = (short)(j + 3);
            indices[i + 5] = j;
        }
        mesh.setIndices(indices);

        if (defaultShader == null) {
            shader = createDefaultShader();
            ownsShader = true;
        } else
            shader = defaultShader;
    }
…
@Override
public void setShader (ShaderProgram shader) {
    if (drawing) {
        flush();
        if (customShader != null)
            customShader.end();
        else
            this.shader.end();
    }
    customShader = shader;
    if (drawing) {
        if (customShader != null)
            customShader.begin();
        else
            this.shader.begin();
        setupMatrices();
    }
}
 

在该构造函数中对shader进行初始化,在setShader (ShaderProgram shader)中对customShader 进行初始化,我们分析可以发现,如果我们不进行setShader那么将是使用shader,而shader则是使用createDefaultShader();进行初始化,下面是createDefaultShader代码

static public ShaderProgram createDefaultShader () {
        String vertexShader = "attribute vec4 " + ShaderProgram.POSITION_ATTRIBUTE + ";\n" //
            + "attribute vec4 " + ShaderProgram.COLOR_ATTRIBUTE + ";\n" //
            + "attribute vec2 " + ShaderProgram.TEXCOORD_ATTRIBUTE + "0;\n" //
            + "uniform mat4 u_projTrans;\n" //
            + "varying vec4 v_color;\n" //
            + "varying vec2 v_texCoords;\n" //
            + "\n" //
            + "void main()\n" //
            + "{\n" //
            + "   v_color = " + ShaderProgram.COLOR_ATTRIBUTE + ";\n" //
            + "   v_color.a = v_color.a * (255.0/254.0);\n" //
            + "   v_texCoords = " + ShaderProgram.TEXCOORD_ATTRIBUTE + "0;\n" //
            + "   gl_Position =  u_projTrans * " + ShaderProgram.POSITION_ATTRIBUTE + ";\n" //
            + "}\n";
        String fragmentShader = "#ifdef GL_ES\n" //
            + "#define LOWP lowp\n" //
            + "precision mediump float;\n" //
            + "#else\n" //
            + "#define LOWP \n" //
            + "#endif\n" //
            + "varying LOWP vec4 v_color;\n" //
            + "varying vec2 v_texCoords;\n" //
            + "uniform sampler2D u_texture;\n" //
            + "void main()\n"//
            + "{\n" //
            + "  gl_FragColor = v_color * texture2D(u_texture, v_texCoords);\n" //
            + "}";

        ShaderProgram shader = new ShaderProgram(vertexShader, fragmentShader);
        if (shader.isCompiled() == false) throw new IllegalArgumentException("Error compiling shader: " + shader.getLog());
        return shader;
    }

我们会发现,libgdx 就定义了一个默认的着色器,我们可以看到ShaderProgram这个类,所以,因此笔者也据此进行创建自己的shader。SpriteBatch的setShader(ShaderProgram)就是为我们提供使用自己的渲染方式。

frameBufferA = new FrameBuffer(Pixmap.Format.RGBA8888, 800, 480, false);
        frameBufferB = new FrameBuffer(Pixmap.Format.RGBA8888, 800, 480, false);
        shader = new ShaderProgram(vertexShader, fragmentShader);    
        blurBatch = new SpriteBatch();
        blurBatch.setProjectionMatrix(GaussianBlur.game.camera.combined);

这是笔者初始化的ShaderProgram和纹理缓存A、B,定义纹理缓存,是因为我们处理的是OpenGL 的X轴和Y纹理缓存。

以下是完整代码

package com.whs.blur;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.glutils.FrameBuffer;
import com.badlogic.gdx.graphics.glutils.ShaderProgram;
import com.badlogic.gdx.scenes.scene2d.Stage;

/**
 * 高级渲染 - 高斯模糊
 * @author WHS
 *  Date 2015/02/09 16:07
 */
public class BlurUtils  {

    public  final static float MAX_BLUR = 5.0F;     // 最大的模糊系数
    private static SpriteBatch blurBatch;        
    private static String vertexShader;            // 定点
    private static String fragmentShader;            // 片段
    private static ShaderProgram shader;            // 自定义着色器
    private static FrameBuffer frameBufferA;        // 纹理缓存A,实际上就是用来存放上一次纹理缓存的拷贝
    private static FrameBuffer frameBufferB;        // 纹理缓存B
    private static float radius = 0.0F;                // 初始的模糊系数
    private static int fbo_size = 1024;    // 纹理缓存大小
    private static float  blur = 0.0F;        // 模糊系数
    private static  float time = 0;            // 偏移的时间
    private static float xOffset = 0.8F;    // x轴偏移,水平渲染
    private static float yOffset = 0.8F;    // y轴偏移,垂直渲染

    static {
        //定点
        vertexShader = "uniform mat4  u_projTrans;\n " 
                + "attribute vec2 a_position;\n "
                + "attribute vec2 a_texCoord0;\n"
                + "attribute vec4 a_color;\n"
                + "varying vec4 vColor;\n"
                + "varying vec2 vTexCoord;\n"
                + "void main() {\n"
                + "    vColor = a_color;\n"
                + "    vTexCoord = a_texCoord0;\n"
                + "    gl_Position = u_projTrans * vec4(a_position, 0.0, 1.0);\n"
                + "}";
        //片段
        fragmentShader = "varying vec4 vColor;\n"
                + "varying vec2 vTexCoord;\n"
                +"uniform sampler2D u_texture;\n"
                + "uniform float resolution;\n"
                + "uniform float radius;\n"
                + "uniform vec2 dir;\n"
                + "void main() {\n"
                + "    vec4 sum = vec4(0.0);\n"
                + "    vec2 tc = vTexCoord;\n"
                + "    float blur = radius/resolution; \n"
                + "    float hstep = dir.x;\n"
                + "    float vstep = dir.y;\n"
                + "    sum += texture2D(u_texture, vec2(tc.x - 4.0*blur*hstep, tc.y - 4.0*blur*vstep)) * 0.0162162162;\n"
                + "    sum += texture2D(u_texture, vec2(tc.x - 3.0*blur*hstep, tc.y - 3.0*blur*vstep)) * 0.0540540541;\n"
                + "    sum += texture2D(u_texture, vec2(tc.x - 2.0*blur*hstep, tc.y - 2.0*blur*vstep)) * 0.1216216216;\n"
                + "    sum += texture2D(u_texture, vec2(tc.x - 1.0*blur*hstep, tc.y - 1.0*blur*vstep)) * 0.1945945946;\n"
                + "    sum += texture2D(u_texture, vec2(tc.x, tc.y)) * 0.2270270270;\n"
                + "    sum += texture2D(u_texture, vec2(tc.x + 1.0*blur*hstep, tc.y + 1.0*blur*vstep)) * 0.1945945946;\n"
                + "    sum += texture2D(u_texture, vec2(tc.x + 2.0*blur*hstep, tc.y + 2.0*blur*vstep)) * 0.1216216216;\n"
                + "    sum += texture2D(u_texture, vec2(tc.x + 3.0*blur*hstep, tc.y + 3.0*blur*vstep)) * 0.0540540541;\n"
                + "    sum += texture2D(u_texture, vec2(tc.x + 4.0*blur*hstep, tc.y + 4.0*blur*vstep)) * 0.0162162162;\n"
                + "    gl_FragColor = vColor * vec4(sum.rgb, 1.0);\n" + "}";

//        vertexShader = Gdx.files.internal("blur/vertex.vert").readString(); // 读取定点着色
//        fragmentShader = Gdx.files.internal("blur/fragment.frag").readString(); // 读取片段着色
        frameBufferA = new FrameBuffer(Pixmap.Format.RGBA8888, 800, 480, false);
        frameBufferB = new FrameBuffer(Pixmap.Format.RGBA8888, 800, 480, false);
        shader = new ShaderProgram(vertexShader, fragmentShader);    
        blurBatch = new SpriteBatch();
        blurBatch.setProjectionMatrix(GaussianBlur.game.camera.combined);
     }

    /**
     *     对指定舞台进行模糊处理
     *  绘制主体,注意方法处理的顺序,以及begin()跟 end()配对
     *  有些手机不支持导致shader.isCompiled() == false 无法进行着色
     *  @param stage
     */
    public static void blur(Stage stage) {

        if (shader != null && shader.isCompiled()) {
            time += Gdx.graphics.getDeltaTime();
            Gdx.gl.glClearColor(0.0F, 0.0F, 0.0F, 0.0F);
            Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
            // 时间控制,显示动态变化
            blur = time < MAX_BLUR ? time : MAX_BLUR; 
            blurRander(stage);
            horizontalBlur();
            verticalBlur();
        }

    }

    /**
     *  模糊主体渲染
     */
    private static void blurRander(Stage stage){

        //先执行frameBufferA捕获默认渲染之下的纹理缓存
        frameBufferA.begin();
        blurBatch.begin();    
        shader.begin();
        /** 因为是循环执行,必须对参数进行重置,重新设置shader绘制需要模糊的stage部分**/
        shader.setUniformf("dir", 0f, 0f);      
        shader.setUniformf("radius", radius );  
        shader.setUniformf("resolution", fbo_size);  
        blurBatch.setShader(shader);
        //精灵重置后绘制stage
        stage.draw();                    
        stage.getRoot().draw(blurBatch, 1);
        //进行刷新
        blurBatch.flush();
        //获得第一次纹理缓存
        frameBufferA.end();
    }

    /**
     *  垂直模糊渲染
     */
    private static void horizontalBlur()   {

        /**设置为垂直模糊的shader**/
        blurBatch.setShader(shader);
        shader.setUniformf("dir", xOffset, 0f);
        shader.setUniformf("radius",blur );

        frameBufferB.begin();

        blurBatch.draw(frameBufferA.getColorBufferTexture(), 0, 0);//绘制纹理缓存A
        blurBatch.flush();
        frameBufferB.end();
    }

    /**
     *  水平模糊渲染
     */
    private static void verticalBlur()   {
        /*
         * 设置为 水平模糊shader再进行绘制
         */
        shader.setUniformf("dir", 0f, yOffset);
        shader.setUniformf("radius",blur );

        blurBatch.draw(frameBufferB.getColorBufferTexture(), 0, 0);// 绘制纹理缓存B
        blurBatch.flush();
        blurBatch.end();
        shader.end();

    }

    public static void dispose() {
        blurBatch.dispose();
        shader.dispose();    
    }

}

作者:吴海生—
来源:CSDN
原文:https://blog.csdn.net/wuhaishengxxx/article/details/45715755
版权声明:本文为博主原创文章,转载请附上博文链接!

发表评论

邮箱地址不会被公开。 必填项已用*标注