Cocos2d-lua 初识shader之四:描边

原理:对于所有透明的像素点,遍历该像素点周围的所有像素点,当有任意一个像素点非透明时,就将该像素点置为描边颜色。

PS.在网上读到一位前辈写的方法是“遍历所有不透明的像素点四周,当有透明像素点时,将该像素点设置为描边颜色”(思路相反),这样的做法会有些缺憾,结尾会放出比较图。

    local vert = [[
        attribute vec4 a_position; 
        attribute vec2 a_texCoord; 
        attribute vec4 a_color; 
        #ifdef GL_ES  
        varying lowp vec4 v_fragmentColor;
        varying mediump vec2 v_texCoord;
        #else                      
        varying vec4 v_fragmentColor; 
        varying vec2 v_texCoord;  
        #endif    
        void main() 
        {
            gl_Position = CC_PMatrix * a_position; 
            v_fragmentColor = a_color;
            v_texCoord = a_texCoord;
        }
    ]]

    local frag = [[
        #ifdef GL_ES 
        precision mediump float; 
        #endif 
        varying vec4 v_fragmentColor; 
        varying vec2 v_texCoord; 
        uniform vec2 my_size;                   // 纹理大小,纹理坐标范围为0-1
        void main(void) 
        { 
            vec2 unit = 1.0 / my_size.xy;       // 单位坐标
            float step = 30.0;                  // 30度
            float width = 2.5;                  // 描边宽度
            float a_limit = 0.4;                // 透明度限制,低于该值即视为透明
            vec4 border = vec4(1.0,1.0,1.0,1.0);// 边框颜色

            vec4 color1 = texture2D(CC_Texture0, v_texCoord);
            gl_FragColor = color1;
            if(color1.a >= a_limit)
            {
                return;                         // 非透明像素点不做处理
            }
            // 遍历四周所有像素点
            for(float i = 0.0; i < 360.0; i += step)
            {
                // 当前角度的偏移坐标
                vec2 offset = vec2(cos(i) * unit.x, sin(i) * unit.y) * width;
                // 当前像素点偏移坐标下的像素点
                vec4 color2 = texture2D(CC_Texture0, v_texCoord + offset); 
                if(color2.a >= a_limit)
                {
                    gl_FragColor = border;      // 不透明则将当前像素点设为边框
                    return;
                }
            }
        }
    ]]
    -- 1.创建glProgram
    local glProgram = cc.GLProgram:createWithByteArrays(vert, frag)
    -- 2.获取glProgramState
    local glProgramState = cc.GLProgramState:getOrCreateWithGLProgram(glProgram)
    -- 3.设置属性值
    local size = self.blur:getTexture():getContentSizeInPixels()
    glProgramState:setUniformVec2("my_size", cc.p(size.width, size.height))
    self.blur:setGLProgram(glProgram)
    self.blur:setGLProgramState(glProgramState)

当前效果:

 

相反效果:

添加渐变效果

原理很简单,对于当前像素点,一层一层进行遍历即可
        void main(void) 
        { 
            vec2 unit = 1.0 / my_size.xy;       // 单位坐标
            float step = 30.0;                  // 30度
            float width = 5.5;                  // 描边宽度
            float width_step = 0.5;             // 描边宽度_步长
            float a_limit = 0.4;                // 透明度限制,低于该值即视为透明
            vec4 border = vec4(1.0,1.0,1.0,1.0);// 边框颜色

            vec4 color1 = texture2D(CC_Texture0, v_texCoord);
            gl_FragColor = color1;
            if(color1.a >= a_limit)
            {
                return;                         // bu透明像素点不做处理
            }
            // 一层一层遍历四周所有像素点
            for(float w = 0.0; w < width; w += width_step)
            {
                for(float i = 0.0; i < 360.0; i += step)
                {
                    // 当前角度的偏移坐标
                    vec2 offset = vec2(cos(i) * unit.x, sin(i) * unit.y) * w;
                    // 当前像素点偏移坐标下的像素点
                    vec4 color2 = texture2D(CC_Texture0, v_texCoord + offset); 
                    if(color2.a >= a_limit)
                    {
                        gl_FragColor = border * w / width;      // 不透明则将当前像素点设为边框
                        return;
                    }
                }
            }
        }

一层一层的遍历像素点明显效率极低,时间复杂度为(12*n)^m = n^m(n为透明像素点,m为遍历层数),一种解决方法:是将两个循环的颠倒过来,先找到非透明像素,然后在逐步判断两个像素点间的下一个非透明像素的位置。时间复杂度为12*n*m = n*m,提高了一些效率。

        void main(void) 
        { 
            float a_limit = 0.1;                // 透明度限制,低于该值即视为透明
            vec4 color1 = texture2D(CC_Texture0, v_texCoord);
            gl_FragColor = color1;
            if(color1.a >= a_limit)
            {
                return;                         // bu透明像素点不做处理
            }
            vec2 unit = 1.0 / my_size.xy;       // 单位坐标
            float step = 30.0;                  // 30度
            float width = 5.0;                  // 描边宽度
            float width_step = 0.5;             // 描边宽度_步长
            vec4 border = vec4(1.0,1.0,1.0,1.0);// 边框颜色
            // 遍历四周所有像素点
            for(float i = 0.0; i < 360.0; i += step)
            {
                // 当前角度的偏移坐标
                vec2 offset = vec2(cos(i) * unit.x, sin(i) * unit.y);
                // 当前像素点偏移坐标下的像素点
                vec4 color2 = texture2D(CC_Texture0, v_texCoord + offset * width); 
                if(color2.a >= a_limit)
                {
                    for(float w = 0.0; w <= width; w += width_step)
                    {   
                        vec4 color3 = texture2D(CC_Texture0, v_texCoord + offset * w); 
                        if (color3.a >= a_limit)
                        {
                            gl_FragColor = border * w / width;      // 不透明则将当前像素点设为边框
                            return;
                        }    
                    }
                }
            }
        }


效果稍有些差异

由于这种方式并没有选择最优点来计算描边宽度,因此描边效果并不好,我们应该找到透明像素点周围距离最近的非透明像素点。

        void main(void) 
        { 
            float a_limit = 0.1;                // 透明度限制,低于该值即视为透明
            vec4 color1 = texture2D(CC_Texture0, v_texCoord);
            gl_FragColor = color1;
            if(color1.a >= a_limit)
            {
                return;                         // bu透明像素点不做处理
            }
            vec2 unit = 1.0 / my_size.xy;       // 单位坐标
            float step = 30.0;                  // 30度
            float width = 5.0;                  // 描边宽度
            float width_step = 0.5;             // 描边宽度_步长
            vec4 border = vec4(1.0,1.0,1.0,1.0);// 边框颜色
            // 遍历四周所有像素点
            float min_dis = 5.0;                // 最小距离
            float sum = 0.0;                    // 周围满足条件的非透明像素点个数
            for(float i = 0.0; i < 360.0; i += step)
            {
                // 当前角度的偏移坐标
                vec2 offset = vec2(cos(i) * unit.x, sin(i) * unit.y);
                // 当前像素点偏移坐标下的像素点
                vec4 color2 = texture2D(CC_Texture0, v_texCoord + offset * width); 
                if(color2.a >= a_limit)
                {
                    for(float w = 0.0; w <= width; w += width_step)
                    {   
                        vec4 color3 = texture2D(CC_Texture0, v_texCoord + offset * w); 
                        if (color3.a >= a_limit && w <= min_dis)
                        {
                            min_dis = w;
                            sum += 1.0;
                        }    
                    }
                }
            }
            if(sum > 0.0)
            {
                gl_FragColor = border * min_dis / width;      // 不透明则将当前像素点设为边框
            }
        }


效果好了一些


--------------------- 
作者:Emperor_Dandy 
来源:CSDN 
原文:https://blog.csdn.net/Register_man/article/details/78033003 
版权声明:本文为博主原创文章,转载请附上博文链接!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值