cocos2d 游戏的描边实现

小明 2014-09-4 2,694 views

这篇文章获得了NetEase公司的技术分享三等奖哦

2D游戏的描边实现

对场景物体的进行描边是游戏常用的功能。基本上有三种作用:

  1. 突出显示主体。比如点选人物,NPC,建筑等时显示发光的外轮廓。

  2. 隐藏锯齿。

  3. 与卡通渲染配合使用实现卡通风格。

后面两种作用,对于3D游戏,可以通过Silhouette edge相关算法实现。或者通过延法线拉伸mesh顶点与原始meshclip也能得到外轮廓。对于2d游戏,由于没有法线数据可以利用,只能通过离线后处理的方式进行。但是对于图像处理常用的边缘检测算法(sobelcanny),由于很难得到闭合的正确轮廓线,首先排除。参照3d游戏的做法,思路可以是这样的:

  1. 通过后处理向外围扩大原始图片。

  2. 用原始大小的图片做蒙版裁剪处理后的图片。

  3. 得到轮廓线。

第一步不能简单地缩放图片实现,例如下面所示:

 

中心小正方形代表原始贴图,如果单纯缩放成外围大矩形,然后对外围大矩形进行裁剪,可以看到并不能把黑色区块正确裁剪掉。必须对原图进行膨胀处理。下面是hx 采用的做法:

 

  1. 渲染成单色

    只要有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);
        }
    }
  2. 渲染描边

    逐像素渲染,采样每个像素相邻的上下左右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;
    
    }
  3. 裁剪

    用膨胀后的原图为底图,原始图像做蒙版进行裁剪,镂空区域即为描边。但此时的描边很毛,需要进一步处理。

  4. 过滤

为了解决毛边,需要对图像做过滤处理,使用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项目组|小奕


    欢迎拍砖!