【Cocos2d-x】图片描边的一种比较好的shader实现方法
图片描边需求如下: 1. 可指定描边宽度 2. 可指定描边颜色3. 可用于字体
图片描边我所知道的方式有以下几种: 1. Cocos2d-x 3.x中,字体用FreeType库,字体描边可以用FreeType自带的描边功能,实际效果没测过,但只能用于字体。 2. 用RenderTexture,方法大概是把原图做一圈的偏移,渲染到同一张纹理上,他们相隔中心点的距离都是r,最后再把自己渲染到中间,核心代码大概这样: rt->begin(); for(int i = 0; i < 360; i += 15) { float rad = CC_DEGREES_TO_RADIANS(i); m_label->setPosition(ccp( textureSize.width * 0.5f + sin(rad) * r,textureSize.height * 0.5f + cos(rad) * r)); m_label->visit(); } m_label->setColor(col); m_label->setBlendFunc(originalBlend); m_label->setPosition(ccp(textureSize.width * 0.5f,textureSize.height * 0.5f)); m_label->visit(); rt->end(); 这种方法可以作为一个比较好的解决方案,但是我认为这种方式在生成描边图片时,需要绘制多个图片,效率不是很好。
以下我分享一种我认为比较好的描边算法: 在片段着色器里面,对于每个像素:1. 如果它是不透明的像素,则不管,维持原本颜色;2. 如果透明,是360度判断它四周有没有不透明的像素,如果有,则把它设成描边颜色,否则保持透明。 我为代码加了比较详细的注释,希望大家能理解
stroke.fsh:描边片段着色器 varying vec4 v_fragmentColor; // vertex shader传入,setColor设置的颜色 varying vec2 v_texCoord; // 纹理坐标 uniform float outlineSize; // 描边宽度,以像素为单位 uniform vec3 outlineColor; // 描边颜色 uniform vec2 textureSize; // 纹理大小(宽和高),为了计算周围各点的纹理坐标,必须传入它,因为纹理坐标范围是0~1 uniform vec3 foregroundColor; // 主要用于字体,可传可不传,不传默认为白色 // 判断在这个角度上距离为outlineSize那一点是不是透明 int getIsStrokeWithAngel(float angel) { int stroke = 0; float rad = angel * 0.01745329252; // 这个浮点数是 pi / 180,角度转弧度 float a = texture2D(CC_Texture0,vec2(v_texCoord.x + outlineSize * cos(rad) / textureSize.x,v_texCoord.y + outlineSize * sin(rad) / textureSize.y)).a; // 这句比较难懂,outlineSize * cos(rad)可以理解为在x轴上投影,除以textureSize.x是因为texture2D接收的是一个0~1的纹理坐标,而不是像素坐标 if (a >= 0.5)// 我把alpha值大于0.5都视为不透明,小于0.5都视为透明 { stroke = 1; } return stroke; } void main() { vec4 myC = texture2D(CC_Texture0,vec2(v_texCoord.x,v_texCoord.y)); // 正在处理的这个像素点的颜色 myC.rgb *= foregroundColor; if (myC.a >= 0.5) // 不透明,不管,直接返回 { gl_FragColor = v_fragmentColor * myC; return; } // 这里肯定有朋友会问,一个for循环就搞定啦,怎么这么麻烦!其实我一开始也是用for的,但后来在安卓某些机型(如小米4)会直接崩溃,查找资料发现OpenGL es并不是很支持循环,while和for都不要用 int strokeCount = 0; strokeCount += getIsStrokeWithAngel(0.0); strokeCount += getIsStrokeWithAngel(30.0); strokeCount += getIsStrokeWithAngel(60.0); strokeCount += getIsStrokeWithAngel(90.0); strokeCount += getIsStrokeWithAngel(120.0); strokeCount += getIsStrokeWithAngel(150.0); strokeCount += getIsStrokeWithAngel(180.0); strokeCount += getIsStrokeWithAngel(210.0); strokeCount += getIsStrokeWithAngel(240.0); strokeCount += getIsStrokeWithAngel(270.0); strokeCount += getIsStrokeWithAngel(300.0); strokeCount += getIsStrokeWithAngel(330.0); if (strokeCount > 0) // 四周围至少有一个点是不透明的,这个点要设成描边颜色 { myC.rgb = outlineColor; myC.a = 1.0; } gl_FragColor = v_fragmentColor * myC; } 我的utilShader.cpp: const char* shaderNameStroke = "ShjyShader_Stroke"; namespace utilShader { // 传入描边宽度(像素为单位),描边颜色,图片大小,获得GLProgramState cocos2d::GLProgramState* getStrokeProgramState( float outlineSize,cocos2d::Color3B outlineColor,cocos2d::Size textureSize,cocos2d::Color3B foregroundColor/* = cocos2d::Color3B::WHITE*/ ) { auto glprogram = GLProgramCache::getInstance()->getGLProgram(shaderNameStroke); if (!glprogram) { std::string fragmentSource = FileUtils::getInstance()->getStringFromFile(FileUtils::getInstance()->fullPathForFilename("shaders/stroke.fsh")); glprogram = GLProgram::createWithByteArrays(ccPositionTextureColor_noMVP_vert,fragmentSource.c_str()); GLProgramCache::getInstance()->addGLProgram(glprogram,shaderNameStroke); } auto glprogramState = GLProgramState::create(glprogram); glprogramState->setUniformFloat("outlineSize",outlineSize); glprogramState->setUniformVec3("outlineColor",Vec3(outlineColor.r / 255.0f,outlineColor.g / 255.0f,outlineColor.b / 255.0f)); glprogramState->setUniformVec2("textureSize",Vec2(textureSize.width,textureSize.height)); glprogramState->setUniformVec3("foregroundColor",Vec3(foregroundColor.r / 255.0f,foregroundColor.g / 255.0f,foregroundColor.b / 255.0f)); return glprogramState; } } 调用的地方: Sprite* spr = Sprite::create("elephant1_Diffuse.png"); spr->setPosition(200,200); spr->setGLProgramState(utilShader::getStrokeProgramState(5,Color3B::GREEN,spr->getContentSize())); this->addChild(spr,1); 效果:
效果还算是比较好的,经测试,此算法在安卓多个机型上也表现良好。
这样一套完整的描边算法就完成了,如果描述有不当之处,或有更优方法,欢迎吐槽。
------------------------------------------------------------------------------------------------------------------------------------------------------------------- 谢谢complex_ok的吐槽,应该预先计算好sin和cos值,无需每次计算。优化后的 stroke.fsh 如下: varying vec4 v_fragmentColor; varying vec2 v_texCoord; uniform float outlineSize; uniform vec3 outlineColor; uniform vec2 textureSize; uniform vec3 foregroundColor; const float cosArray[12] = {1,0.866,0.5,-0.5,-0.866,-0.1,0.866}; const float sinArray[12] = {0,1,-1,-0.5}; int getIsStrokeWithAngelIndex(int index) { int stroke = 0; float a = texture2D(CC_Texture0,vec2(v_texCoord.x + outlineSize * cosArray[index] / textureSize.x,v_texCoord.y + outlineSize * sinArray[index] / textureSize.y)).a; if (a >= 0.5) { stroke = 1; } return stroke; } void main() { vec4 myC = texture2D(CC_Texture0,v_texCoord.y)); myC.rgb *= foregroundColor; if (myC.a >= 0.5) { gl_FragColor = v_fragmentColor * myC; return; } int strokeCount = 0; strokeCount += getIsStrokeWithAngelIndex(0); strokeCount += getIsStrokeWithAngelIndex(1); strokeCount += getIsStrokeWithAngelIndex(2); strokeCount += getIsStrokeWithAngelIndex(3); strokeCount += getIsStrokeWithAngelIndex(4); strokeCount += getIsStrokeWithAngelIndex(5); strokeCount += getIsStrokeWithAngelIndex(6); strokeCount += getIsStrokeWithAngelIndex(7); strokeCount += getIsStrokeWithAngelIndex(8); strokeCount += getIsStrokeWithAngelIndex(9); strokeCount += getIsStrokeWithAngelIndex(10); strokeCount += getIsStrokeWithAngelIndex(11); bool stroke = false; if (strokeCount > 0) { stroke = true; } if (stroke) { myC.rgb = outlineColor; myC.a = 1.0; } gl_FragColor = v_fragmentColor * myC; } 本帖地址:http://blog.csdn.net/u011281572/article/details/44999609 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |