文章目录
  1. 1. 3.x的渲染机制
  2. 2. 绘制遮罩
  3. 3. 使用自定义Shader达到渐变的效果
  4. 4. PS

这篇文章可能比较长,我写这篇博客的目的是做一个记录,方便以后自己查阅。
所以逻辑性可能较差,如果你无意间看到了我的博文并且有什么疑问或者建议,请联我

linyn0612@126.com

cocos2dx 版本为3.x,具体为3.3+
为外部提供的接口:

static RectShade* create(Rect rect,Color4B color,float lenght);
rect 中间显示的区域,Rect
color 遮罩的颜色值,Color4B
length 遮罩的渐变区域宽度,float(命名有问题..懒得改了)

完成之后大致的效果如下,一般可以用来做高亮,新手引导,迷雾等功能。

shadeReck

在Cocos以上效果常规的实现方法是首先定义一个继承LayerColor的类,重写其绘制,使用自定义Shader进行绘制,这也是我认为比较好的方法。不过我为了熟悉OpenGL以及Cocos2dx的绘制系统,选择了一个扩展性较强的实现方式。

3.x的渲染机制

cocos2dx中所有的节点(Node)在内存中以树的形式存在,并且以中序进行遍历(左-中-右)。这一点在2.x到3.x的过程中没有改变,改变的3.x是将绘制的部分单独抽出来并封装成为对像。3.x以前是一边遍历一边进行绘制,3.x以后遍历树的时的任务是生成绘制命令,并排序存放起来。最后统一绘制。

也就是说在2.x中重写一个节点的绘制一般只需要override节点的draw函数就可以了。在3.x中则需要再引入一个绘制命令对象。引擎提供了Customcommand这个自定义绘制的类,我们可以直接使用或者派生出来自己的类,怎么做取决于自己。

一般可以这样做:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//	RectShadeNode.h

class RectShadeNode : public Node {

public:

...

void onDraw(const Mat4 &transform, uint32_t flags);
virtual void draw(Renderer *renderer, const Mat4 &transform, uint32_t flags) override;

...

protect:

...

V2F_C4B_T2F* _buffer;
GLuint _vbo;
CustomCommand _customCommand;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//	RectShadeNode.cpp
void RectShadeNode::draw(cocos2d::Renderer *renderer, const cocos2d::Mat4 &transform, uint32_t flags)
{
_customCommand.init(_globalZOrder);
_customCommand.func = CC_CALLBACK_0(RectShadeNode::onDraw,this ,transform, flags);
renderer->addCommand(&_customCommand);
}
void RectShadeNode::onDraw(const cocos2d::Mat4 &transform, uint32_t flags)
{
/* a.
setup GLProgram & use
set blendFun
*/

/* b.
if _dirty:

bind vbo
_dirty = false
*/

// c.set Attribs & Uniforms

// d.draw
}

绘制的数据使用cocos2dx提供的V2F_C4B_T2F结构体,当然这个效果不需要Textrood,完全可以自己定义一个定义只有Vertics与color的结构体,我比较懒就直接用了现成的。

绘制遮罩

具体过程是找到遮罩的8个顶点,之后使用8个顶点建立索引,绘制8个三角形。然后就完成了。
绘图版的笔坏了,还没有鼠标,就原谅我用触摸板画的图吧=。=||

shadeReck1

如以上图,我们要绘制由a-h八个顶点组成的8个三角形
在初始化(一般init函数中)对绘制的数据进行赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
bool RectShadeNode::init(Rect rect,Color4B color,float length)
{

//glprogram & vbo & attribs & uniforms
...

drawReckNode(rect,color,length);
return true;
}

void RectShadeNode::drawReckNode(Rect rect,Color4B color,float length)
{
auto size = Director::getInstance()->getWinSize();
unsigned int vertex_count = 8*3;
ensureCapacity(vertex_count);

_positions = (Vec2*)malloc(4*sizeof(Vec2*));
Vec2 position1 = rect.origin;
Vec2 position2 = Vec2(rect.origin.x,rect.origin.y+rect.size.height);
Vec2 position3 = Vec2(rect.origin.x+rect.size.width,rect.origin.y+rect.size.height);
Vec2 position4 = Vec2(rect.origin.x+rect.size.width,rect.origin.y);
_positions[0] = position1;
_positions[1] = position2;
_positions[2] = position3;
_positions[3] = position4;

V2F_C4B_T2F a = {Vec2(0,0), Color4B(color), Tex2F(0.0, 0.0) };
V2F_C4B_T2F b = {Vec2(size.width,0), Color4B(color), Tex2F(0.0, 0.0) };
V2F_C4B_T2F c = {position1, Color4B(color), Tex2F( 0.0, 0.0) };
V2F_C4B_T2F d = {position4, Color4B(color), Tex2F( 0.0, 0.0) };
V2F_C4B_T2F e = {Vec2(size.width,size.height), Color4B(color), Tex2F(0.0, 0.0) };
V2F_C4B_T2F f = {position3, Color4B(color), Tex2F(0.0, 0.0) };
V2F_C4B_T2F g = {position2, Color4B(color), Tex2F( 0.0, 0.0) };
V2F_C4B_T2F h = {Vec2(0,size.height), Color4B(color), Tex2F( 0.0, 0.0) };

V2F_C4B_T2F_Triangle *triangles = (V2F_C4B_T2F_Triangle *)(_buffer + _bufferCount);
V2F_C4B_T2F_Triangle triangle0 = {a, b, c};
V2F_C4B_T2F_Triangle triangle1 = {b, d, c};
V2F_C4B_T2F_Triangle triangle2 = {e, d, b};
V2F_C4B_T2F_Triangle triangle3 = {e, f, d};
V2F_C4B_T2F_Triangle triangle4 = {e, g, f};
V2F_C4B_T2F_Triangle triangle5 = {e, h, g};
V2F_C4B_T2F_Triangle triangle6 = {h, c, g};
V2F_C4B_T2F_Triangle triangle7 = {h, a, c};

triangles[0] = triangle0;
triangles[1] = triangle1;
triangles[2] = triangle2;
triangles[3] = triangle3;
triangles[4] = triangle4;
triangles[5] = triangle5;
triangles[6] = triangle6;
triangles[7] = triangle7;
}

初步的任务已尽完成了

shadeReck2

使用自定义Shader达到渐变的效果

效果实现需要传入一个渐变宽度作为参数,令绘制点到高亮的矩形4个边中距离最短的距离为dis。
如果dis < length,则改变其透明度,并且dis在[0,length]中,对应透明度w[color.w,0],且两者成线性。
只需要在fragment shader中判断,并给出w即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

// fsh部分代码
float aspe = resolution.x / resolution.y;

vec2 transv2(vec2 point)
{
vec2 result = point.xy / resolution.xy;
result.x *= aspe;
return result;
}

float transf(float r)
{
float re = r / resolution.y;
return re;
}

float lenght = transf(f_lenght);
bool isInRect(vec2 pos)
{
bool pRe = true;

if ((pos.x < position1.x - lenght || pos.x > position4.x + lenght)||
(pos.y < position1.y - lenght || pos.y > position2.y + lenght)) {
pRe = false;
}
return pRe;
}

float getMask(vec2 pos,float lenght)
{

if (isInRect(pos)) {

float dis = abs(pos.x-position1.x+lenght);
if (dis > abs(pos.x-position4.x-lenght))
dis = abs(pos.x-position4.x-lenght);
if (dis > abs(pos.y-position1.y+lenght))
dis = abs(pos.y-position1.y+lenght);
if (dis > abs(pos.y-position2.y-lenght))
dis = abs(pos.y-position2.y-lenght);

return smoothstep(lenght,0.0,dis)*v_fragmentColor.w;
}
return v_fragmentColor.w;
}



void main()
{
vec2 pos = transv2(v_position.xy);
float mask = getMask(pos,lenght);

vec4 color = v_fragmentColor;
color.w = mask;
gl_FragColor = color;
}

shadeReck1

为了方便计算与判断,我将坐标信息做了初步的处理,还用到了一个内置函数smoothstep函数,这里不赘述其用法。
最后为了实现一个呼吸的效果,只需length*cos(time)就可以了。

1
2
3
4
5
6
7
8
float transf(float r)
{
float re = r / resolution.y;
re *= (1.2+0.8*cos(CC_Time[1]));
return re;
}

float lenght = transf(f_lenght);

整个效果就完成了。

Github:https://github.com/JackLN/ShaderDemo.git
这个项目可能比较乱,什么都有。。。

PS

虽说做出了效果,但也仅仅是为了做出效果…rect,color,length等属性都应该留出更改的接口。
更可以在此基础上扩展出多边形的遮罩,多个不同区域同时高亮等效果。
整个过程是我学习与探索cocos2dx与opengl绘制的过程,最后也完成了一个初步的效果。

文章目录
  1. 1. 3.x的渲染机制
  2. 2. 绘制遮罩
  3. 3. 使用自定义Shader达到渐变的效果
  4. 4. PS