cocos2d-x shader实现漩涡效果

前言

最开始是想实现一个黑洞扭曲空间效果的shader,后来发现可以简化成漩涡效果,于是在网上搜索各种资料。主要参考这篇文章 猛击我。但是这篇文章是基于顶点shader实现的,对于2d中的sprite只有4个顶点,所以在扭曲顶点uv坐标时并不能达到想要的效果,而对于3d则是mesh网格中的顶点数目越多实际效果越好,那如何能让2d中的sprite也能实现漩涡扭曲效果呢。

原理

可以引用unity文档中对于扭曲图像效果的说明:旋转扭曲图像特效是指在一个圆形区域内扭曲所渲染的图像。在圆形区域中心的像素被旋转一定的角度,而其他像素的旋转程度随着距中心距离的增大而减小,在圆形区域边界减小到0。所以有2种方式实现这一过程:
1.可以旋转顶点坐标本身。
2.可以旋转顶点对应的纹理坐标。
由于我们在顶点shader中没法拿到每个像素点的坐标值,所以我们用第二种方式实现。

实现

首先是顶点shader(Vortex.vsh)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#ifdef GL_ES
precision mediump float;
#endif
// Inputs
attribute vec4 a_position;
attribute vec2 a_texCoord;
// Varyings
//varying vec2 v_texCoord;
#ifdef GL_ES
varying mediump vec2 v_texCoord;
#else
varying vec2 v_texCoord;
#endif
void main()
{
gl_Position = CC_MVPMatrix * a_position;
//拿到未做扭曲变换的v_texCoord纹理坐标扔给像素shader
v_texCoord = a_texCoord;
}

再看核心实现,像素shader(Vortex.fsh)

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
#ifdef OPENGL_ES
precision mediump float;
#endif
uniform sampler2D CC_Texture0;
//uniform sampler2D u_texture;
// Varyings
//varying vec2 v_texCoord;//顶点shader扔过来的纹理坐标
#ifdef GL_ES
varying mediump vec2 v_texCoord;
#else
varying vec2 v_texCoord;
#endif
uniform float radius ;//旋转的半径
uniform float angle ;//旋转的角度
//旋涡的计算函数
vec2 vortex( vec2 uv )
{
//先减去贴图中心点的纹理坐标,这样是方便旋转计算
uv -= vec2(0.5, 0.5);
//计算当前坐标与中心点的距离。
float dist = length(uv);
//计算出旋转的百分比
float percent = (radius - dist) / radius;
if ( percent <= 1.0 && percent >= 0.0) //小于半径的区域才进行旋转
{
//通过sin,cos来计算出旋转后的位置。(这里我还不明白具体的原理,为什么通过这个计算可以得到旋转后的uv坐标,知道原理的同学可以联系我:-))
float theta = percent * percent * angle * 0.5;
float s = sin(theta);
float c = cos(theta);
uv = vec2(dot(uv, vec2(c, -s)), dot(uv, vec2(s, c)));
}
//再加上贴图中心点的纹理坐标,这样才正确。
uv += vec2(0.5, 0.5);
return uv;
}
void main()
{
//采样像素点时,通过变换后的uv值取。
gl_FragColor = texture2D( CC_Texture0, vortex( v_texCoord ) );
}

在看看c++代码中的核心实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
CCGLProgram * p = new CCGLProgram();
p->initWithVertexShaderFilename("Vortex.vsh", "Vortex.fsh");
p->addAttribute(kCCAttributeNamePosition, kCCVertexAttrib_Position);
p->addAttribute(kCCAttributeNameColor, kCCVertexAttrib_Color);
p->addAttribute(kCCAttributeNameTexCoord, kCCVertexAttrib_TexCoords);
p->link();
p->updateUniforms();
//m_pSprite为想要使用该shader的sprite
m_pSprite->setShaderProgram(p);
GLuint angle = glGetUniformLocation(m_pSprite->getShaderProgram()->getProgram(), "angle");
GLuint radius = glGetUniformLocation(m_pSprite->getShaderProgram()->getProgram(), "radius");
m_pSprite->getShaderProgram()->setUniformLocationWith1f(radius, 1.0f);//初始化时设置想要旋转的半径区域大小
m_pSprite->getShaderProgram()->setUniformLocationWith1f(angle, 0.0f);//初始化时设置开始的旋转角度

然后起个定时器,在定时器里面实现随时间旋转角度不断增大的效果:

1
2
3
GLuint angle = glGetUniformLocation(m_pSprite->getShaderProgram()->getProgram(), "angle");
m_pSprite->getShaderProgram()->use();
m_pSprite->getShaderProgram()->setUniformLocationWith1f(angle, m_angle);//这里的m_angle值随时间不断增大

最后效果如下

cmd-markdown-logo