Libgdx 高级渲染之 高斯模糊
一、 什么是高斯模糊
首先,当然是先了解什么是高斯模糊。高斯模糊,在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
版权声明:本文为博主原创文章,转载请附上博文链接!