【Cocos2d-x】图片描边的一种比较好的shader实现方法

【Cocos2d-x】图片描边的一种比较好的shader实现方法,第1张

概述图片描边需求如下: 1. 可指定描边宽度 2. 可指定描边颜色3. 可用于字体 图片描边我所知道的方式有以下几种: 1. Cocos2d-x 3.x中,字体用FreeType库,字体描边可以用FreeType自带的描边功能,实际效果没测过,但只能用于字体。 2. 用RenderTexture,方法大概是把原图做一圈的偏移,渲染到同一张纹理上,他们相隔中心点的距离都是r,最后再把自己渲染到中间,核心

图片描边需求如下:

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();

这种方法可以作为一个比较好的解决方案,但是我认为这种方式在生成描边图片时,需要绘制多个图片,效率不是很好。


3. Cocos2d-x 3.x的ShaderTest有个描边的例子,是用shader实现的,不过那个程序有些不友好:描边宽度不是传像素进去,而是一个0~1的数字,是一个比例,就是说大的图片描边大,小的图片描边小,而且同一个图片不同位置的描边也宽窄不一(这在长比宽大很多的图片尤其明显),而且描边的颜色也深浅不一。



以下我分享一种我认为比较好的描边算法:

在片段着色器里面,对于每个像素: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~1uniform 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

总结

以上是内存溢出为你收集整理的【Cocos2d-x】图片描边的一种比较好的shader实现方法全部内容,希望文章能够帮你解决【Cocos2d-x】图片描边的一种比较好的shader实现方法所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/1054167.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-25
下一篇2022-05-25

发表评论

登录后才能评论

评论列表(0条)

    保存