这篇文章获得了NetEase公司的技术分享三等奖哦
2D游戏的描边实现
对场景物体的进行描边是游戏常用的功能。基本上有三种作用:
突出显示主体。比如点选人物,NPC,建筑等时显示发光的外轮廓。
隐藏锯齿。
与卡通渲染配合使用实现卡通风格。
后面两种作用,对于3D游戏,可以通过Silhouette edge相关算法实现。或者通过延法线拉伸mesh顶点与原始mesh做clip也能得到外轮廓。对于2d游戏,由于没有法线数据可以利用,只能通过离线后处理的方式进行。但是对于图像处理常用的边缘检测算法(sobel、canny),由于很难得到闭合的正确轮廓线,首先排除。参照3d游戏的做法,思路可以是这样的:
通过后处理向外围扩大原始图片。
用原始大小的图片做蒙版裁剪处理后的图片。
得到轮廓线。
第一步不能简单地缩放图片实现,例如下面所示:
中心小正方形代表原始贴图,如果单纯缩放成外围大矩形,然后对外围大矩形进行裁剪,可以看到并不能把黑色区块正确裁剪掉。必须对原图进行膨胀处理。下面是hx 采用的做法:
渲染成单色
只要有alpha值的像素点都保留下来,渲染到一张(原始宽+描边*2)X(原始高+描边*2)的渲染对象。
// lmy 2014/3/26 #ifdef GL_ES precision highp float; #endif varying vec2 v_texCoord; uniform sampler2D CC_Texture0; uniform vec4 v_color; void main(void) { gl_FragColor = texture2D(CC_Texture0, v_texCoord); if (gl_FragColor.a>0.0001) { gl_FragColor.rgb = v_color.rgb; } else { gl_FragColor.rgb = vec3(0.0,0.0,0.0); } }
渲染描边
逐像素渲染,采样每个像素相邻的上下左右4个像素,相加后作为当前像素的值。这样一遍渲染下来可以把原始图像膨胀1个像素,描边大小n需要n遍渲染。
void main(void) { float dx = 1.0/rt_size.x; float dy = 1.0/rt_size.y; vec4 c0 = texture2D(CC_Texture0, v_texCoord+vec2(dx,0)); vec4 c1 = texture2D(CC_Texture0, v_texCoord+vec2(-dx,0)); vec4 c2 = texture2D(CC_Texture0, v_texCoord+vec2(0,dy)); vec4 c3 = texture2D(CC_Texture0, v_texCoord+vec2(0,-dy)); vec4 c4 = texture2D(CC_Texture0, v_texCoord); gl_FragColor = c4 + c0+c1+c2+c3; }
裁剪
用膨胀后的原图为底图,原始图像做蒙版进行裁剪,镂空区域即为描边。但此时的描边很毛,需要进一步处理。
过滤
为了解决毛边,需要对图像做过滤处理,使用box filter对周围16个像素做均值采样即可达到较好的打磨效果。
void main(void) { vec4 color = vec4(0.0,0.0,0.0,0.0); steps[0] = -2.0; steps[1] = -1.0; steps[2] = 0.0; steps[3] = 1.0; steps[4] = 2.0; vec2 dxdy = 1.0 / rt_size; for (int x = -2; x < 2; x++) { for (int y = -2; y < 2; y++) { color += texture2D(CC_Texture0, vec2(v_texCoord.x + steps[x+2] * dxdy.x, v_texCoord.y + steps[y+2] * dxdy.y)); } } color /= 16.0; gl_FragColor = color; }
5. 换色
这一步是为了hx需求做的,绿色描边是选中状态,红色描边属于不能摆放的状态,只需要把描边换一下色,不需要再执行一遍上述1-4步。
void main(void) { gl_FragColor = texture2D(CC_Texture0, v_texCoord); if (gl_FragColor.a>0.0001) { gl_FragColor.rgb = v_color.rgb; } }
整个流程的渲染对象如下,分别是原图->单色->膨胀->裁剪->过滤&换色
总结
由于渲染对象与全屏相比并不是很大,对于渲染速度还是有保证的,虽然膨胀阶段需要多次渲染,但是基本能保证即点即响应的渲染速度,对于数以百千计的场景资源,节省了很多美术工作量。
改进
对于玻璃这类包含半透明条纹的物体,会把条纹的描边也计算进去,而不能准确表达出外轮廓,暂时没有想到比较好的解决办法,欢迎大家拍砖。
HX项目组|小奕