Cocos Creator 3.0 实现折纸效果,仅占用一个 DrawCall !
效果
实现
整体思路
初始化一个多边形。 折叠后分割成两个多边形。 如果需要继续分割,对场上的所有多边形进行折叠,折叠出新的多边形的层级正好与原来的相反。
所以,所有的计算和渲染都可以转换成对一个多边形的操作。
为了简化计算,我们约定初始化的多边形为凸多边形。这么做有几个好处。
折叠后生成的仍是凸多边形,并且对于每个多边形只会折叠出两个凸多边形
渲染时,分割凸多边形为三角形特别方便,即能快速计算出顶点索引
计算
主要分为三块
多边形分割 线线交点 轴对称
分割
当夹角大于90度时,该顶点正好是折叠多边形的顶点。
当夹角等于90度时,该顶点是两个多边形的顶点。
当夹角小于90度时,该顶点是底部多边形的顶点。
const dotValue = temp_v2_vector.dot(temp_v2_vector_3)
if (Math.abs(dotValue) === 0) {
// 刚好在点上
} else if (dotValue > 0) {
// 在前面
} else {
// 在后面
}
交点
直线上的一点可以用点和向量表示。
Cramer's Rule
)求出交点。function linelinePoint(p1: Vec2, p1Dir: Vec2, p2: Vec2, p2Dir: Vec2) {
const a1 = p1Dir.x, b1 = -p2Dir.x, c1 = p2.x - p1.x
const a2 = p1Dir.y, b2 = -p2Dir.y, c2 = p2.y - p1.y
const d = a1 * b2 - a2 * b1,
d1 = c1 * b2 - c2 * b1,
d2 = a1 * c2 - c1 * a2
const t1 = d1 / d, t2 = d2 / d
return [t1, t2]
}
t1
,这个t1
不仅可以用来求出顶点坐标,也可以求出相交的纹理坐标。const posSpilt = Vec2(pos.x + dir.x * t1, pos.y + dir.y * t1)
const uvSpilt = Vec2(uv.x + uvdir.x * t1, uv.y + uvdir.y * t1)
对称点
求出该顶点与中点的向量 求出该点在触摸方向的单位向量的投影(点乘),这正好是距离的一半 求出对称点坐标(距离乘方向向量+起始点坐标)
Vec2.subtract(temp_v2_vector_4, temp_v2_pos, pos)
const dotLength = temp_v2_vector_4.dot(temp_v2_vector) * 2
temp_v2_pos_2.set((pos.x + temp_v2_vector.x * dotLength), pos.y + temp_v2_vector.y * dotLength)
渲染
Assembler
的方式组装顶点数据。将引擎中的 Sprite-simple
组装器拷贝出来,作为自己的组装器模板。新建一个类继承 Sprite
,并设置它的组装器到自己的组装器创建变量顶点数组,纹理数组。 编写组装器逻辑
// 仅限凸多边形
@ccclass('PolygonSprite')
export class PolygonSprite extends Sprite {
@property({ type: [Vec2] })
protected _vertices: Vec2[] = [new Vec2(-100, -100), new Vec2(100, -100), new Vec2(100, 100), new Vec2(-100, 100)];
// 省略部分代码
@property({ type: [Vec2] })
protected _uvs: Vec2[] = [new Vec2(0, 0), new Vec2(1, 0), new Vec2(1, 1), new Vec2(0, 1)];
// 省略部分代码
protected _flushAssembler() {
//指向自定义的组装器
let assembler = polygonAssembler;
if (this._assembler !== assembler) {
this.destroyRenderData();
this._assembler = assembler;
}
// 省略部分代码
}
}
// 保存顶点数据
updateVertexData(sprite: PolygonSprite) {
//中间变量
const renderData = sprite.renderData;
if (!renderData) {
return;
}
renderData.vertexCount = renderData.dataLength = sprite.vertices.length
// 三角形数量 = 顶点数 - 2
// 索引数量 = 三角形数量X3
renderData.indicesCount = (renderData.vertexCount - 2) * 3
renderData.vertDirty = false;
for (let i = 0; i < sprite.vertices.length; ++i) {
const xy = sprite.vertices[i];
renderData.data[i].x = xy.x
renderData.data[i].y = xy.y
}
},
updateUvs(sprite: PolygonSprite) {
const renderData = sprite.renderData!;
//实际uv
const uv = sprite.spriteFrame!.uv;
// 左 下 上 右
const l = uv[0], b = uv[1], t = uv[7], r = uv[6]
for (let i = 0; i < sprite.uvs.length; ++i) {
const uvs = sprite.uvs[i];
renderData.data[i].u = l + (r - l) * uvs.x
renderData.data[i].v = b + (t - b) * uvs.y
}
renderData.uvDirty = false;
},
fillBuffers(sprite: PolygonSprite, renderer: any) {
//省略代码
// 填充顶点
for (let i = 0; i < renderData.vertexCount; ++i) {
const vert = renderData.data[i];
// 计算世界坐标
vBuf![vertexOffset++] = a * vert.x + c * vert.y + tx;
vBuf![vertexOffset++] = b * vert.x + d * vert.y + ty;
vBuf![vertexOffset++] = vert.z;
// 填充uv
vBuf![vertexOffset++] = vert.u;
vBuf![vertexOffset++] = vert.v;
Color.toArray(vBuf!, sprite.color, vertexOffset);
vertexOffset += 4;
}
// 填充索引
for (let i = 0; i < sprite.vertices.length - 2; ++i) {
const start = i;
iBuf![indicesOffset++] = vertexId;
iBuf![indicesOffset++] = start + 1 + vertexId;
iBuf![indicesOffset++] = start + 2 + vertexId;
}
},
小结
assembler
实现合批渲染。以上为白玉无冰使用 Cocos Creator 3.0.0
实现 “折纸效果”
的技术分享,更多精彩请关注微信公众号!也欢迎小伙伴们移步“Cocos 中文社区”一起交流讨论。帖子链接:https://forum.cocos.org/t/topic/112045
征集令
📢📢📢!!Cocos 现向全体开发者征集 Creator 3.x 官方文档内容修改建议,登录“Cocos 中文社区”说出你的想法吧!地址:
https://forum.cocos.org/t/topic/114798
Cocos 开发者沙龙来了!6 月 19 日,我们将来到上海,与各位开发者齐聚一堂!
引擎体重 & 技术担当 Panda、 CTO & 周边收集狂林顺、首席客服王哲及技术团队将一起空降上海,为大家详解 Cocos Creator 3.x 最新功能及宝藏技术。
更有华为海思麒麟团队图形渲染工程师杜鹏程以及华为工程师夏敏言、《江南百景图》小游戏版负责人大城小胖、乐府资深 Cocos 开发专家夏凯强、TopOn 广告产品专家余国强等一众大佬强势加盟,带来超多干货分享!
此外,我们为大家带来了硬核现场一对一技术支持,限额 5 个团队。
是的,你没有看错,我们引擎团队核心成员将全程驻场,你可以携带自己的作品和问题同他们进行一对一技术交流,名额有限,赶快扫描下方二维码报名吧!
点击【阅读原文】跳转至报名入口,期待你的加入!