本文适合于Cocos2d-X等使用OpenGL API的渲染框架
一般实现遮罩效果的方法有以下几种:
使用OpenGL的是以下几个接口:
glEnable(GL_STENCIL_TEST);
glStencilMask(mask_layer);
glStencilFunc(GL_NEVER, mask_layer, mask_layer);
glStencilOp(!_inverted ? GL_ZERO : GL_REPLACE, GL_KEEP, GL_KEEP);
这个代码是Cocos2d-X中ClippingNode的实现方式,具体源码可以参考CCClippingNode.cpp。这种方法是一种”old school”的技术。缺点是不能抗锯齿,例如,在使用圆形的时候,锯齿非常严重。
这种方法只适合于矩形的裁剪区域。Cocos2d-X中的ClippingRectangleNode就是基于这种方法。使用的OpenGL代码如下(截取自Cocos2d-X):
glEnable(GL_SCISSOR_TEST);
glScissor((GLint)(x * _scaleX + _viewPortRect.origin.x),
(GLint)(y * _scaleY + _viewPortRect.origin.y),
(GLsizei)(w * _scaleX),
(GLsizei)(h * _scaleY));
在Fragment Shader中实现遮罩可以使用算法来实现,例如利用圆形的特质,可以通过x^2 + y^2 = radius
的公式来决定当前Fragment的颜色取值。
本人写了一个Fragment Shader实现了这种做法,不过效果依旧不够理想,代码如下:
#ifdef GL_ES
precision lowp float;
#endif
varying vec2 v_texCoord;
uniform vec2 v_centerCoord;
//uniform float f_scaleX;
//uniform float f_scaleY;
//uniform float f_radius;
uniform vec3 v_params;//for f_scaleX,f_scaleY,f_radius
varying vec4 v_fragmentColor;
void main()
{
vec4 pixColor = texture2D(CC_Texture0, v_texCoord);
float dist = length((v_texCoord - v_centerCoord) * v_params.xy);
float alpha = pixColor.a;
if(dist > v_params.z){
float diff = dist - v_params.z;
if(diff < 0.02){
alpha = 1.0 - diff/0.02;
}else{
alpha = 0.0;
}
pixColor.rgb = pixColor.rgb * alpha;
}
gl_FragColor = v_fragmentColor * vec4(pixColor.rgb, alpha);
}
由于设置了ALPHA_PREMULTIPLIED,因此Shader中需要重新计算rgb。
由于是基于纹理的坐标来计算长度,而纹理在OpenGL中会被normalize,因此需要根据实际情况去调整x轴和y轴的比例。(如果原来的图是200 * 300,在Fragment Shader中v_texCoord的x单位和y单位的实际长度是2:3)
这个Shader只是比较实验性的写法,效果比Stencil Buffer好,但在圆心的上下左右四个中心区域不能算出很好的效果。效果如下,可以仔细观察上下两个角。
blendfunc实现的遮罩效果是最简单的,首先绘制遮罩图的,遮罩图的blendfunc需要设置为:
mask:setBlendFunc(gl.ONE_MINUS_SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
然后绘制被遮罩对象,其blendfunc需要设置为:
sprite:setBlendFunc(gl.ONE_MINUS_DST_ALPHA, gl.DST_ALPHA)
原理其实很简单,遮罩图绘制到framebuffer的时候只保留alpha值,而sprite绘制的时候使用遮罩的apha值。不过需要注意的是,如果使用该方法,需要保证opengl的Config中有配置alpha通道,例如在使用OpenGL ES的安卓环境中,需要设置
mGLSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
否则,遮罩和图都无法被绘制出来。
这种方法其实也有缺点,如果被遮罩对象是透明的话,是没办法和底下的混合的。
LibGDX 在文中总结了好几种方法,可惜都没有很好地介绍每种方法。读者可以自行做进一步研究。