Cocos Creator Shader实践 :手写一个搓扑克牌的效果(三)

今天继续使用上一节的js文件。

shader.card.vert.js顶点Shader

shader.card.frag.js片元Shader

用到的图片资源:
运行效果
运行效果

先将Shader的代码改为最简,不做任何处理。

shader.card.vert.js

1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports =
`
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main(){

v_texCoord = a_texCoord;
// 坐标
vec4 v = vec4(gl_Vertex);
v = CC_PMatrix * v;
gl_Position = v;
}
`;

shader.card.frag.js

1
2
3
4
5
6
7
8
9
10
module.exports = 
`
varying vec2 v_texCoord;

void main()
{
vec4 c = texture2D(CC_Texture0, v_texCoord);
gl_FragColor = c;
}
`

添加额外顶点

上一节我们看到,调节顶点时,图像会跟随发生改变。但是默认的六个顶点显然不够我们用于搓牌效果。现在我们就手动为一张纹理图添加多个额外的顶点。

创建GLNode
1
2
3
//必须创建glNode cc.Node没有Draw方法可以重写
this._glNode = new cc.GLNode();
this.node._sgNode.addChild(this._glNode);
加载扑克纹理
1
2
3
4
5
6
7
8
9
10
//纹理图,Assets中新建resources文件夹,即可动态加载 600x400
this._cardBack = "resources/CardBack.png";
this._cardFace = "resources/CardFace.png";
//加载为Sprite
var imgUrl = cc.url.raw(this._cardBack);
this._spCardBack = cc.textureCache.addImage(imgUrl);
this._spCardBack.retain();//引用计数+1,不会被资源池释放
imgUrl = cc.url.raw(this._cardFace);
this._spCardFace = cc.textureCache.addImage(imgUrl);
this._spCardFace.retain();
创建顶点数据

将图片等分为10x10的网格图元,图元越小效果越好,但相应的更消耗资源。代码如下:

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
initVertTexBuffer: function (isBack) {
//10x10的网格,计算顶点位置
var widthDiv = 10;
var heightDiv = 10;
//存放数据的数组
var verts = [];
var texs = [];
//顶点间隔,纹理为竖向,这里横向放置
var durH = this._cardSize.width / heightDiv;
var durW = this._cardSize.height / widthDiv;

for (var i = 1; i <= widthDiv; i++) {
for (var j = 1; j <= heightDiv; j++) {
//计算顶点
var x = (i - 1) * durW;
var y = (j - 1) * durH;
//此处设置六个顶点,作为一个图元
//注意:两个三角形的顶点都要以逆时针顺序设置,开启面剔除之后,逆时针顶点为正面
if (isBack) {
//逆时针
//第一个三角形
verts.push(x); verts.push(y);
verts.push(x + durW); verts.push(y);
verts.push(x + durW); verts.push(y + durH);
//第二个三角形
verts.push(x); verts.push(y);
verts.push(x + durW); verts.push(y + durH);
verts.push(x); verts.push(y + durH);
} else {
//顺时针
//第一个三角形
verts.push(x); verts.push(y);
verts.push(x + durW); verts.push(y + durH);
verts.push(x + durW); verts.push(y);
//第二个三角形
verts.push(x); verts.push(y);
verts.push(x); verts.push(y + durH);
verts.push(x + durW); verts.push(y + durH);
}
}
}
//顶点数量
this._vertsNum = verts.length / 2;

//计算纹理位置
for (var i = 0; i < this._vertsNum; i++) {
var texX = verts[i * 2];
var texY = verts[i * 2 + 1];
//X,Y交换,纹理横向
var numY = texX / this._cardSize.height;
var numX = texY / this._cardSize.width;
if (!isBack) numY = 1 - numY; numX = 1 - numX;
texs.push(numX);
texs.push(numY);
};

//顶点Buffer
var vertsBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
//纹理位置Buffer
var texsBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texs), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
//正反面区分
if (isBack) {
this._backVertsBuffer = vertsBuffer.buffer_id;
this._backTexsBuffer = texsBuffer.buffer_id;
}
else {
this._faceVertsBuffer = vertsBuffer.buffer_id;
this._faceTexsBuffer = texsBuffer.buffer_id;
}
},

创建glProgram,重写GLNode Draw方法

下面我们创建glProgram,并重写GlNode的Draw方法,手动控制顶点和纹理位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//创建glProgram
this.glProgram = new cc.GLProgram();
var vert = require("shader.card.vert");
var frag = require("shader.card.frag");
this.glProgram.initWithString(vert, frag);
this.glProgram.link();
this.glProgram.updateUniforms();

//重写Draw方法
this._glNode.draw = function () {
//启用面剔除
gl.enable(gl.CULL_FACE);
// gl.cullFace(gl.FRONT);//此函数可以使正反面颠倒
this.glProgram.use();
this.glProgram.setUniformsForBuiltins();
//绑定卡背纹理
gl.bindTexture(gl.TEXTURE_2D, this._spCardBack.name);
this.addVertsTexCoord(this._backVertsBuffer, this._backTexsBuffer);
//绑定卡面纹理
gl.bindTexture(gl.TEXTURE_2D, this._spCardFace.name);
this.addVertsTexCoord(this._faceVertsBuffer, this._faceTexsBuffer);
//停止面剔除
gl.disable(gl.CULL_FACE);
}.bind(this);

Function addVertsTexCoord

1
2
3
4
5
6
7
8
9
10
11
12
13
addVertsTexCoord: function (verts, texs) {
var VERTEX_ATTRIB_FLAG_POSITION = 1
var VERTEX_ATTRIB_FLAG_TEX_COORDS = 4
var VERTEX_ATTRIB_POSITION = 0;
var VERTEX_ATTRIB_TEX_COORDS = 2;
gl.bindBuffer(gl.ARRAY_BUFFER, verts)
gl.vertexAttribPointer(VERTEX_ATTRIB_POSITION, 2, gl.FLOAT, 0, 0, 0)
gl.bindBuffer(gl.ARRAY_BUFFER, texs)
gl.vertexAttribPointer(VERTEX_ATTRIB_TEX_COORDS, 2, gl.FLOAT, 0, 0, 0)
//GL_TRIANGLES是以每三个顶点绘制一个三角形。第一个三角形使用顶点v0,v1,v2,第二个使用v3,v4,v5,以此类推。如果顶点的个数n不是3的倍数,那么最后的1个或者2个顶点会被忽略。
gl.drawArrays(gl.TRIANGLES, 0, this._vertsNum)
gl.bindBuffer(gl.ARRAY_BUFFER, 0)
}

运行效果:

运行效果

下面我们来修改下Shader代码,看有何不同:

shader.card.vert.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module.exports =
`
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main(){
v_texCoord = a_texCoord;
// 坐标
vec4 v = vec4(gl_Vertex);
v = CC_PMatrix * v;
if(v.x >= 115 && v.y >= 165){
v.x+=100;
v.y+=100;
}
gl_Position = v;
}
`;

运行效果:

运行效果
因为顶点增多,修改一个位置的顶点数据,仅会影响单个图元。
注:我们也可以在v = CC_PMatrix * v;未与矩阵CC_PMatrix相乘前作出调整,顶点数据为(0,0)(600,400),与我们Buffer中的数据一致。

图片的位置靠左下角,下一节我们将会讨论如何控制图片位置。

流程回顾

  1. 手动创建GLNode
  2. 手动加载纹理到缓存,初始化网格顶点数据
  3. 创建GLProgram,使用Shader代码
  4. 重写GLNode Draw方法,绑定纹理,输入顶点数据

完整代码下载:
点击下载