更多笔记,请关注公众号:

感谢群内大佬 honmono 的分享,也欢迎同学们入群交流

QQ群:521643513

Cocos引擎源码位于CocosCreator.app/Contents/Resources/engine/cocos2d/(Mac版本), 以下使用CocosEngine代替路径

效果展示

1, 自定义渲染组件—TexturePlus

2, 实现自定义多边形渲染范围

===========

3, 图片切割效果

===========

4, 图片破碎效果

===========

5, 碎片掉落效果

===========

浅析Assember

源码路径位于 CocosEngine/core/renderer/assembler.js

对于Assembler的个人理解, Assembler中的核心是顶点数据, 每个顶点都有位置,uv信息, 通过改变顶点的位置和uv信息, 就可以实现一些例如 只显示图片的一部分区域,图片部分区域拉伸(九宫格也是基于这个实现的). 且修改顶点数据并不会打断合批,性能有保障.而在Cocos中每一个渲染组件,例如cc.Sprite,cc.Label,cc.Graphics等, 它们都继承于RenderComponent, 且都有一个对应的Assembler从而实现不同的渲染效果.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8QSLuvXr-1609931515309)(./cocos-dir-1.png)];

Assembler提供两个静态方法, register和init. register方法将渲染组件和Assembler绑定,init方法用于初始化Assembler.

自定义Assembler

自定义Assembler的核心就是将顶点数据填充到renderdata中

Assembler的实现可以参考 Assembler2D, 源码位于CocosEngine/core/renderer/assembler-2d.js

// 将这5个属性注入Assembler2D.prototype内
cc.js.addon(Assembler2D.prototype, {floatsPerVert: 5,       // 一个顶点所需的空间 xy两个,uv两个,color一个verticesCount: 4,       // 顶点个数indicesCount: 6,        // 三角形顶点个数uvOffset: 2,            // uv在buffer中的偏移量, colorOffset: 4,         // color在buffer中的偏移量// 格式如 x|y|u|v|color|x|y|u|v|color|x|y|u|v|color|......// 当然也可以自定义格式
});
export default class Assembler2D extends Assembler {constructor () {super();this._renderData = new RenderData();this._renderData.init(this);this.initData();this.initLocal();}// 计算总共所需的空间大小get verticesFloats () {return this.verticesCount * this.floatsPerVert;}initData () {let data = this._renderData;data.createQuadData(0, this.verticesFloats, this.indicesCount);}// 更新顶点颜色信息updateColor (comp, color) {}// 更新顶点坐标信息updateWorldVerts (comp) {}// 将renderdata中的数据填充到buffer中, 也计算填充了三角形顶点索引fillBuffers (comp, renderer) {}}cc.Assembler2D = Assembler2D;

简单概况一下就是: 计算每个顶点的position,uv,color(可以不要),以及三角形顶点索引,然后赋值到buffer内就行了,提供三角形顶点索引 是因为gpu绘制图像都是绘制了一个个三角形而成, 换而言之三角形是最小绘制单元而顶点内的信息只需要在顶点发生变化时才需要更新

编辑器内可编辑的多边形区域的实现, 可以看之前那篇MaskPlus, 里面实现了如何实现一个自定义多边形遮罩以及自定义Gizmo.

计算顶点的世界坐标

用上图显示图片的自定义多边形区域为例, 首先计算好多边形顶点数组polygon, polygon是基于结点坐标的, 且按逆时针排序.
计算过程可以直接参考我之前写的maskplus

protected updateWorldVerts(comp: TexturePlus) {if (CC_NATIVERENDERER) {this.updateWorldVertsNative(comp);} else {this.updateWorldVertsWebGL(comp);}
}protected updateWorldVertsWebGL(comp: TexturePlus) {let verts = this._renderData.vDatas[0];let matrix: cc.Mat4 = comp.node['_worldMatrix'];let matrixm = matrix.m,a = matrixm[0], b = matrixm[1], c = matrixm[4], d = matrixm[5], tx = matrixm[12], ty = matrixm[13];let justTranslate = a === 1 && b === 0 && c === 0 && d === 1;let floatsPerVert = this.floatsPerVert;if (justTranslate) {let polygon = comp.polygon;for(let i=0; i<polygon.length; i++) {verts[i * floatsPerVert] = polygon[i].x + tx;verts[i * floatsPerVert+1] = polygon[i].y + ty;}} else {let polygon  = comp.polygon;for(let i=0; i<polygon.length; i++) {verts[i * floatsPerVert] = a * polygon[i].x + c * polygon[i].y + tx;verts[i * floatsPerVert+1] = b * polygon[i].x + d * polygon[i].y + ty;}}
}

代码很简单, 其中tx, ty是结点对应世界坐标的偏移量, 代码中polygon是更具结点坐标得到的, 这里进行了一次计算
a,b,c,d是cocos为Node计算的旋转值

计算顶点的uv坐标

uv坐标的计算可以有几种方式, 可以做成局部拉伸的效果, 也可以做成裁剪效果, 这里就以裁剪效果为例.

uv坐标取值区间是0~1, 对应的是texture的宽和高, 按比例取的.
取texture的高是反着取的, 因为cocos的世界坐标原点在左下角.

/** 计算uv, 锚点都是中心 */
public static computeUv(points: cc.Vec2[], width: number, height: number) {let uvs: cc.Vec2[] = [];for(const p of points) {let x = MathUtils.clamp(0, 1, (p.x + width/2) / width);let y = MathUtils.clamp(0, 1, 1. - (p.y + height/2) / height);uvs.push(cc.v2(x, y));}return uvs;
}

将uv填充到renderdata内

/** 更新uv */
protected updateUVs(comp: TexturePlus) {let uvOffset = this.uvOffset;let floatsPerVert = this.floatsPerVert;let verts = this._renderData.vDatas[0];let uvs = [];if(comp.texture) {uvs = CommonUtils.computeUv(comp.polygon, comp.texture.width, comp.texture.height)        }let polygon = comp.polygon;for(let i=0; i<polygon.length; i++) {let dstOffset = floatsPerVert * i + uvOffset;verts[dstOffset] = uvs[i].x;verts[dstOffset + 1] = uvs[i].y;}
}

计算顶点color

/** 填充顶点的color */
public updateColor(comp: TexturePlus, color: number) {let uintVerts = this._renderData.uintVDatas[0];if(!uintVerts) return ;color = color != null ? color : comp.node.color['_val'];let floatsPerVert = this.floatsPerVert;let colorOffset = this.colorOffset;let polygon = comp.polygon;for(let i=0; i<polygon.length; i++) {uintVerts[colorOffset + i * floatsPerVert] = color;}
}

这里可能会造成疑惑的是color填充进的是uintVDatas, 而之前的uv和position都是填充进的vDatas
阅读render-data源码可以知道, uintVerts和vDatas是共享的同一段buffer

/** render-data.js */
updateMesh (index, vertices, indices) {this.vDatas[index] = vertices;// 将vertices.buffer当成参数传入, 他们共享同一段bufferthis.uintVDatas[index] = new Uint32Array(vertices.buffer, 0, vertices.length);this.iDatas[index] = indices;this.meshCount = this.vDatas.length;
},createData (index, verticesFloats, indicesCount) {let vertices = new Float32Array(verticesFloats);let indices = new Uint16Array(indicesCount);this.updateMesh(index, vertices, indices);
},

计算三角形顶点索引

因为三角形是最小的绘制单元, 所以需要将多边形转换为一个个三角形让gpu渲染.
计算三角形我这里选择的方式是耳切法, 针对耳切法的实现网上已经有有很多了,我这里也不再赘叙.

ps: 我也是看了白玉无冰大佬的帖子才了解的

链接地址: https://forum.cocos.org/t/mask-mesh-gizmo/88288

代码也不复杂, 需要注意的是points是有序的, 且是逆时针方向排列, 所以只需要循环判断是不是耳朵且三角形内没有包含其他点就行, 找到后切掉在继续判断即可

// 将多边形分解为多个三角形
public static splitPolygonByTriangle(points: cc.Vec2[]): number[] {if(points.length <= 3) return [0, 1, 2];let pointMap: {[key: string]: number} = {};     // point与idx的映射for(let i=0; i<points.length; i++) {let p = points[i];pointMap[`${p.x}-${p.y}`] = i;}const getIdx = (p: cc.Vec2) => {return pointMap[`${p.x}-${p.y}`]}points = points.concat([]);let idxs: number[] = [];let index = 0;while(points.length > 3) {let p1 = points[(index) % points.length], p2 = points[(index+1) % points.length], p3 = points[(index+2) % points.length];let splitPoint = (index+1) % points.length;let v1 = p2.sub(p1);let v2 = p3.sub(p2);if(v1.cross(v2) < 0) {      // 是一个凹角, 寻找下一个index = (index + 1) % points.length;continue;}let hasPoint = false;       for(const p of points) {if(p != p1 && p != p2 && p != p3 && this.isInTriangle(p, p1, p2 ,p3)) {hasPoint = true;break;}}if(hasPoint) {      // 当前三角形包含其他点, 寻找下一个index = (index + 1) % points.length;continue;}// 找到了耳朵, 切掉idxs.push(getIdx(p1), getIdx(p2), getIdx(p3));points.splice(splitPoint, 1);}for(const p of points) {idxs.push(getIdx(p));}return idxs;
}
// 判断一个点是否在三角形内
public static isInTriangle(point: cc.Vec2, triA: cc.Vec2, triB: cc.Vec2, triC: cc.Vec2) {let AB = triB.sub(triA), AC = triC.sub(triA), BC = triC.sub(triB), AD = point.sub(triA), BD = point.sub(triB);//@ts-ignorereturn (AB.cross(AC) >= 0 ^ AB.cross(AD) < 0)  && (AB.cross(AC) >= 0 ^ AC.cross(AD) >= 0) && (BC.cross(AB) > 0 ^ BC.cross(BD) >= 0);
}

在assembler中的使用

将这一步的计算不要放到fillBuffer内, 因为并不需要每帧计算, 只需要在修改顶点时计算即可
this.indicesArr = CommonUtils.splitPolygonByTriangle(comp.polygon);
----------------------------------------------------------------------------let ins = this.indicesArr;
for(let i=0; i<iData.length; i++) {ibuf[indiceOffset++] = vertexId + ins[i];
}

修改顶点后重新分配render-data

public initData() {let data = this._renderData;data.createQuadData(0, this.verticesFloats, this.indicesCount);
}public resetData(comp: TexturePlus) {let points = comp.polygon;if(!points || points.length < 3) return ;this.verticesCount = points.length;this.indicesCount = this.verticesCount + (this.verticesCount - 3) * 2;this._renderData.clear();this.initData();
}

填充buffer

//每帧都会被调用
fillBuffers(comp: TexturePlus, renderer) {if (renderer.worldMatDirty) {this.updateWorldVerts(comp);}let renderData = this._renderData;// vData里包含 pos, uv, color数据, iData中包含三角形顶点索引let vData = renderData.vDatas[0];let iData = renderData.iDatas[0];let buffer = this.getBuffer();let offsetInfo = buffer.request(this.verticesCount, this.indicesCount);// buffer data may be realloc, need get reference after request.// fill verticeslet vertexOffset = offsetInfo.byteOffset >> 2,vbuf = buffer._vData;if (vData.length + vertexOffset > vbuf.length) {vbuf.set(vData.subarray(0, vbuf.length - vertexOffset), vertexOffset);} else {vbuf.set(vData, vertexOffset);}// fill indiceslet ibuf = buffer._iData,indiceOffset = offsetInfo.indiceOffset,vertexId = offsetInfo.vertexOffset;             // vertexId是已经在buffer里的顶点数,也是当前顶点序号的基数let ins = this.indicesArr;for(let i=0; i<iData.length; i++) {ibuf[indiceOffset++] = vertexId + ins[i]; }
}

图片切割实现

图片切割其实就是做了线段和多边形的切割计算, 原理就不多说了, 直接上代码.
https://github.com/kirikayakazuto/CocosCreator_UIFrameWork/blob/SplitTexture/assets/Script/test/UISplitTexture.ts

源码地址:

TexturePlus: https://github.com/kirikayakazuto/CocosCreator_UIFrameWork/blob/SplitTexture/assets/Script/Common/Components/TexturePlus.ts
TextureAssembler: https://github.com/kirikayakazuto/CocosCreator_UIFrameWork/blob/SplitTexture/assets/Script/Common/Assemblers/TextureAssembler.ts

CocosCreator 基于Assembler实现的图片切割破碎效果及自定义遮罩相关推荐

  1. 基于html5鼠标悬停图片动画展示效果

    分享一款基于html5鼠标悬停图片动画展示效果.里面包含两款不同效果的html5图片展示效果.效果图如下: 在线预览   源码下载 实现的代码. html代码: <div class=" ...

  2. ae破碎效果在哪_怎么用AE做切割破碎效果的影视动画

    1.打开软件AE,新建一个合成,命名为[切割破碎效果]. 2.用[椭圆工具]绘制一个正圆,调整大小为388,并命名为圆. 3.选择[多边形工具]绘制一个三角形,显示标尺去精确调整大小和角度. 4.在圆 ...

  3. html5jqueryl轮播图,基于JQuery的实现图片轮播效果(焦点图)

    完整的演示代码: JQuery实现图片轮播效果 #banner {position:relative; width:478px; height:286px; border:1px solid #666 ...

  4. php中实现图片自动轮播,基于vue.js实现图片轮播效果

    轮播图效果: html {{sd.title}} js export default { components: { }, ready: function() { var _this=this; va ...

  5. 【Python+图片切割+图片合并】基于Python的图片批量切割与合并(保持原像素不变,不会出现像素大小不匹配、填充黑边的问题)

    基于Python的图片批量切割与合并(保持原像素不变,不会出现像素大小不匹配.填充黑边的问题) 前言 效果图 1.批量读取文件 2.清空目标目录方法(配合切割图片方法使用) 3.批量切割图片(像素不足 ...

  6. QLabel实现图片轮播效果

    QLabel实现图片轮播效果 QLabel实现图片轮播效果 功能 效果图 代码 表白词 功能 继承QLabel控件实现了图片轮播效果 1.自定义添加图片与图片描述: 2.支持2S自动轮播 3.支持鼠标 ...

  7. python3 opencv 基于二值化图像素投影的图片切割方法

    对于一些背景纯色,结构相对简单的图,可以利用传统的opencv图像处理进行分割.先来记录一下基于二值化图像素投影的图片切割方法的实现.比如下面这张图,可以利用这个算法进行切割.(源代码在最后面) 切割 ...

  8. 图像处理小技巧-图片切割

    深度学习 图像处理 ... 图像处理小技巧-图片切割 深度学习 前言 一.介绍 二.使用python实现 2.1 导入库 2.2 全部程序 总结 前言 基于olivettifaces数据集 githu ...

  9. thinkcmf5调用指定分类的二级_Tengine快速上手系列教程amp;视频:基于Python API的图片分类应用入门丨附彩蛋...

    前言:近期,Tengine团队加班加点,好消息接踵而来,OpenCV 4.3.0发布,OPEN AI LAB AIoT智能开发平台Tengine与OpenCV合作共同加速边缘智能,Tengine再获业 ...

  10. WPF纯手工两步打造图片切割工具(一)

    一.功能说明 1.四种图片切割方式:缩放:指定宽高(可能变形).缩放:指定宽(高按比例).缩放:指定高(宽按比例).裁减:指定宽高. 2.批量图片切割. 3.目标存储区同名文件处理:直接覆盖.重新命名 ...

最新文章

  1. h5自定义相机界面_MIUI 12全新相机发布,多款相机图标进行重绘,可升级机型一览...
  2. MultiBoot设计参考链接
  3. 面试官:你说一下Redis使用场景都有哪些
  4. CSS魔法堂:你一定误解过的Normal flow
  5. 四参数拟合曲线_每周放送|曲线拟合
  6. Java面试 - List和Set比较,各自的子类比较
  7. mysql 启动报错Can't connect to local MySQL server through socket '/data/mysql/mysql/mysql.sock'(111)...
  8. 33 计算机维修,33.计算机硬件检测维修与数据恢复竞赛规程(修改)全解.doc
  9. 构建基于流程的多维度企业管理体系
  10. windows下封一些危险的端口
  11. 解决微信调整字体大小导致Html5页面混乱
  12. repo sync代码断点续传
  13. 自学编程、玩 vlog,90 后程序员们的冠军之路
  14. teamviewer超时后连接被阻断。您的许可证对您与伙伴的最大话时间有所限制
  15. Python入门教程三:显示'Welcome to Python'五次
  16. Linux_29_Linux-Vsftpd
  17. [图像几何变换]——图像的仿射变换、透射变换及图像金字塔
  18. Java编程题-买苹果
  19. 论文笔记-Integration of AI activities in software development processes
  20. android studio高德地图的基本使用

热门文章

  1. Excel怎么合并单元格
  2. oracle取得组内行号,行号
  3. VC++两万字总结Windows系统中的Layered分层窗口技术(附源码)
  4. 项目#npm install #cnpm install #yarn安装包报错
  5. linux删除回收站提示权限错误
  6. ul li文字不对齐
  7. 摩托车竞速游戏:Road Redemption公路救赎mac中文版
  8. 明尼苏达计算机科学硕士录取案例,专业42-明尼苏达大学双城分校研究生录取-W同学...
  9. 免费报表软件有哪些?5款热门工具
  10. 如何通俗易懂地理解递归