cocos圖片描邊

圖片描邊需求如下:

  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. 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~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, -0.5, -0.866, -1, -0.866, -0.5, 0, 0.5, 0.866};
const float sinArray[12] = {0, 0.5, 0.866, 1, 0.866, 0.5, 0, -0.5, -0.866, -1, -0.866, -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, vec2(v_texCoord.x, 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;
}

————————————————
版權聲明:本文爲CSDN博主「shjy2011」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/u011281572/article/details/44999609

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章