Cocos2d-js 实现弹窗背景虚化效果
美术组希望游戏中对话框的背景不是单调地叠一层半透明黑色,而是能有模糊的虚化效果。像是这个网页中第一个按钮按下去的样子。
显然,如果在要手游中实现这个效果,需要借助 OpenGL ES 2.0 Shader 来完成。
一个理想的方案是对当前场景设置一个 Shader 并在 Shader 中实现模糊效果,使得整个场景虚化。但是对话框也需要置于场景中,这会导致连对话框也虚化掉。
¶RenderTexture/FrameBuffer
不过好在现实也没有那么理想,在 Cocos2d 中直接对场景(CCScene)设置 Shader 是不起作用的。
一个变通的方法是将场景先绘制到缓冲区,然后再对缓冲区进行模糊。可以使用 cc.director.getRunningScene().visit(); 完成这一操作:
var bgTex = cc.RenderTexture.create(cc.visibleRect.width, cc.visibleRect.height);
bgTex.begin();
cc.director.getRunningScene().visit();
bgTex.end();
此时整个场景的‘截图’保存在了 bgTex 中。接下来只要对此截图进行模糊处理即可。
¶Software Blur
查阅了一些资料后,找到了一些备选的软模糊算法。
- StackBox Blur: Box Blur 的升级版,效果更好一些,而且速度是 Gaussian Blur 的 7 倍;
乍一看似乎 StackBox Blur 是较好的选择,但是从它源码上来看,它是最不容易实现的。所以我暂不考虑它了。我尝试了的 Box Blur 和 Gaussian Blur。
Box Blur 的效果过于生硬,而 Gaussian Blur 的效果好很多。
这里有一个简单的高斯模糊 Fragment Shader 的实现,不过只有9阶,在大半径的时候效果不够赞。所以我将其修改为15阶:
//apply blurring, using a 15-tap filter with predefined gaussian weights
sum += texture2D(CC_Texture0, vec2(tc.x - 7.0*blur*hstep, tc.y - 7.0*blur*vstep)) * 0.0044299121055113265;
sum += texture2D(CC_Texture0, vec2(tc.x - 6.0*blur*hstep, tc.y - 6.0*blur*vstep)) * 0.00895781211794;
sum += texture2D(CC_Texture0, vec2(tc.x - 5.0*blur*hstep, tc.y - 5.0*blur*vstep)) * 0.0215963866053;
sum += texture2D(CC_Texture0, vec2(tc.x - 4.0*blur*hstep, tc.y - 4.0*blur*vstep)) * 0.0443683338718;
sum += texture2D(CC_Texture0, vec2(tc.x - 3.0*blur*hstep, tc.y - 3.0*blur*vstep)) * 0.0776744219933;
sum += texture2D(CC_Texture0, vec2(tc.x - 2.0*blur*hstep, tc.y - 2.0*blur*vstep)) * 0.115876621105;
sum += texture2D(CC_Texture0, vec2(tc.x - 1.0*blur*hstep, tc.y - 1.0*blur*vstep)) * 0.147308056121;
sum += texture2D(CC_Texture0, vec2(tc.x, tc.y)) * 0.159576912161;
sum += texture2D(CC_Texture0, vec2(tc.x + 1.0*blur*hstep, tc.y + 1.0*blur*vstep)) * 0.147308056121;
sum += texture2D(CC_Texture0, vec2(tc.x + 2.0*blur*hstep, tc.y + 2.0*blur*vstep)) * 0.115876621105;
sum += texture2D(CC_Texture0, vec2(tc.x + 3.0*blur*hstep, tc.y + 3.0*blur*vstep)) * 0.0776744219933;
sum += texture2D(CC_Texture0, vec2(tc.x + 4.0*blur*hstep, tc.y + 4.0*blur*vstep)) * 0.0443683338718;
sum += texture2D(CC_Texture0, vec2(tc.x + 5.0*blur*hstep, tc.y + 5.0*blur*vstep)) * 0.0215963866053;
sum += texture2D(CC_Texture0, vec2(tc.x + 6.0*blur*hstep, tc.y + 6.0*blur*vstep)) * 0.00895781211794;
sum += texture2D(CC_Texture0, vec2(tc.x + 7.0*blur*hstep, tc.y + 7.0*blur*vstep)) * 0.0044299121055113265;
这是一组预算好的权重,节省计算量,这样就能省时省力达到满意的效果了。
需要注意的是,高斯模糊算法需要两次作用于图像。第一次对原图进行水平模糊,第二次对水平模糊后的图像进行垂直模糊。所以前面提到的缓冲区截图是非常必要的。
¶LayerBlur
由于绘制整个场景的过程是耗时的,且该效果只是用于对话框背景,所以缓冲区中的图像并不需要实时更新,创建后直接当作静态背景纹理使用即可。
为了便于使用,我将其封装为一个 Layer:
var modalBg = LayerBlur.create(LayerBlur.defaultBlurRadius, cc.color(0, 0, 0, 255 * 0.6));
this.addChild(modalBg);

本文例子在此开发环境中创建并测试通过:cocos2d-html5 3.0 alpha 2
源码已上传至 Gist
UPDATED 20140528
在 iOS 模拟器中测试没有通过:
- cocos2d-x 中须使用独立 shader,且需要使用两次 bgTex 缓冲区;
- cocos2d-x 在 visit() 渲染包含 ccui 的组件时,会产生奇怪的 bug 导致场景被色块覆盖。
UPDATED 20141031:
优化:An investigation of fast real-time GPU-based image blur algorithms
文章指出由于高斯模糊是一种低通滤波器,所以可以把图像缩小后使用低阶的高斯模糊滤镜后再以图像原来尺寸绘制到屏幕来达到优化的目的。
11 条留言
问一下这个的浏览器兼容性怎么样 ?
可以参看这个 http://caniuse.com/#search=...
您好,我参照您的实现方式在 cocos2d-js 3.0 final 版本中实验,调试的时候提示 setUniformLocationWith2f 函数未定义 找了好久没找到相关资料,请问是怎么回事? 【modules 引用了 shaders extensions external 模块】
我看了下 3.0 final 版,setUniformLocationWith2f 在 CCGLProgram.js 里有的,这个函数用来设置 shader 类似为 vec2 的参数。
Blur 内容里 tc是??
https://github.com/mattdesl...
//our original texcoord for this fragment
vec2 tc = vTexCoord;
您好,我截图没问题,但在重新渲染时好像没有成功,不知道是调用的函数不对还是顺序不对,试了一上午,基本都是不报错,但截的图重新放上去后是未经过模糊处理的,我想看看您处理的源代码,但gist地址好像不对,打不开
gist已经被墙了,所以打不开。请自备梯子。
Uncaught cc.GLProgram.getUniformLocationForName(): Invalid operation. Cannot get uniform location when program is not initialized
这个报错 联系一下我,谢谢 QQ 3162379
请问 你解决了么 我遇到问题和你一样QQ592116366
我的报错同上Invalid operation. Cannot get uniform location when program is not initialized
引擎版本cocos2d-js3.3
说这个program 没有初始化 但是....怎么改?真心不会了
Disqus 留言存档