LayaAir2.0 自定义Mesh-画扇形

  2019.7.24 09:49更新

  之前计算绘制顶点数的时候,代码如下

// 封边顶点
let edgeVertex = sectorAngle < 360 ? 6 : 0
let edgeIndex = sectorAngle < 360 ? 4 : 0
// 需要画的顶点
let drawVertex = slices * (sectorAngle / 360).toFixed(1)

  使用toFixed(1),想将数量位数控制一下,我在后文还提到说不能出现小数。其实应该是要在这里将结果转为Number类型,保留整数就行。如果这里的顶点数不够,会导致绘制出来的度数不够。

// 需要画的顶点
let drawVertex = Number(slices * (sectorAngle / 360).toFixed(0))

  --------------------------------------------------  

  2019.7.17 18:12更新

  之前画封边的时候,共用了内径的上下顶点,后来在加入贴图的时候,UV坐标导入之后,会使得贴图在两个封边上成镜像效果,所以改成不共用顶点。

// 内径上顶点
vertices[vc++] = 0
vertices[vc++] = halfHeight
vertices[vc++] = 0
vertices[vc++] = 0
vertices[vc++] = 1
vertices[vc++] = 0
vertices[vc++] = 1
vertices[vc++] = 0
// 内径下顶点
vertices[vc++] = 0
vertices[vc++] = -halfHeight
vertices[vc++] = 0
vertices[vc++] = 0
vertices[vc++] = 1
vertices[vc++] = 0
vertices[vc++] = 1
vertices[vc++] = 1
// 外径上顶点 起点边
vertices[vc++] = radius
vertices[vc++] = halfHeight
vertices[vc++] = 0
vertices[vc++] = 0
vertices[vc++] = 1
vertices[vc++] = 0
vertices[vc++] = 0
vertices[vc++] = 0
// 外径下顶点 起点边
vertices[vc++] = radius
vertices[vc++] = -halfHeight
vertices[vc++] = 0
vertices[vc++] = 0
vertices[vc++] = 1 // 这个会影响光照
vertices[vc++] = 0
vertices[vc++] = 0
vertices[vc++] = 1
// 画两个三角形(内上、内下、外上, 外下、外上、内下) 起点封边
indices[ic++] = verticeCount + 0
indices[ic++] = verticeCount + 1
indices[ic++] = verticeCount + 2
indices[ic++] = verticeCount + 3
indices[ic++] = verticeCount + 2
indices[ic++] = verticeCount + 1

// 内径上顶点
vertices[vc++] = 0
vertices[vc++] = halfHeight
vertices[vc++] = 0
vertices[vc++] = 0
vertices[vc++] = 1
vertices[vc++] = 0
vertices[vc++] = 0
vertices[vc++] = 0
// 内径下顶点
vertices[vc++] = 0
vertices[vc++] = -halfHeight
vertices[vc++] = 0
vertices[vc++] = 0
vertices[vc++] = 1
vertices[vc++] = 0
vertices[vc++] = 0
vertices[vc++] = 1
// 外径上顶点 结束边
vertices[vc++] = posX
vertices[vc++] = halfHeight
vertices[vc++] = posZ
vertices[vc++] = 0
vertices[vc++] = 1
vertices[vc++] = 0
vertices[vc++] = 1
vertices[vc++] = 0
// 外径下顶点 结束边
vertices[vc++] = posX
vertices[vc++] = -halfHeight
vertices[vc++] = posZ
vertices[vc++] = 0
vertices[vc++] = 1 // 这个会影响光照
vertices[vc++] = 0
vertices[vc++] = 1
vertices[vc++] = 1
// 画两个三角形(内上、内下、外上, 外下、外上、内下) 结束封边
indices[ic++] = verticeCount + 4
indices[ic++] = verticeCount + 6
indices[ic++] = verticeCount + 5
indices[ic++] = verticeCount + 7
indices[ic++] = verticeCount + 5
indices[ic++] = verticeCount + 6
verticeCount += edgeVertex

  上述代码标红的地方,就是新增的加入UV坐标,这样封边就能正常的显示贴图了。另外,Laya中UV坐标系好像是以右上角为原点的,表现在UV的坐标原点,需要是右上角的顶点,比如上述代码中外径上顶点的UV坐标就是(0,0).

  --------------------------------------------------

  2019.7.17 14:34更新

// 索引数量
let indexCount = 3 * drawVertex + edgeIndex + 6 * drawVertex + 3 * drawVertex

  上述关于计算索引总数,需要 edgeIndex * 3,具体为什么要 * 3这个还需要再了解,但是不*3,会导致最后画底部圆面/扇面,缺少几个三角形。

  --------------------------------------------------

  2019.7.16 21:30更新

  再理解了一下这篇文章中提到的,三个点在一条直线上,画出来的三角形看不到,需要跳到下一行的第一个点,关键代码:

triangles[0] = 0;
triangles[1] = 1;
triangles[2] = xSize + 1;

  之前一直没理解triangles[2] = xSize + 1,为啥是这么写。再想一下,这里的顶点数据是在一个一维数组中,然后一行xSize个顶点,第一行的第一个数据下标是0,那么下一行第一个数据的下标就是xSize+1了。。。

  --------------------------------------------------

  最近想用Laya模仿微信小游戏《欢乐球球》,做为学习3D引擎的一个项目。做这个项目之前,先用2D做了另外一款简单的小游戏,简单熟悉了一下Laya的IDE,及引擎的一些基本知识。之前的3D知识基本为零,只照着网上的教程试着用过几天unity3d,但是都是皮毛的东西。

  《欢乐球球》这款游戏,核心玩法就是旋转圆柱,让小球通过圆柱上的扇状环,且不掉落在特定的标记为红色的环块上,通过一层环得分,最终根据得分进行排名。

  最开始接触这款游戏的时候,就大概知道核心点在于扇状环的形成。之前没有接触3D引擎的时候,以为引擎有现成的api来生成这样的3D物体,觉得做这款小游戏应该不复杂,后来发现没这么简单。Laya引擎里面提供的api能画Box,胶囊体,圆锥,圆柱,平面,四边形,球体,没有现成的画扇状体的api,而且在论坛搜索,谷歌/百度搜索除了在论坛找到一个说要修改底层,并且提到了一个关键词Mesh编程,就没有再找到有网友提供相关的教程,或许是我检索的关键词不对吧。

  但是检索“自定义扇形”这样的关键词,就找到了关于Unity3D自定义扇形相关的教程,而且还是针对《欢乐球球》这款游戏,而且还就是针对生成里面的扇形环3D物体的教程。大致看了一下,也是涉及到上面提到的一个关键词Mesh编程,并且分享了一篇专门讲Mesh编程的文章,里面有几个关键词:顶点,三角形,索引,vertices,triangles,uv。一开始觉得挺难的,unity3d的接口laya能用?相关的术语、api等相通?但是没办法,找了很久就是没有找到Laya相关的,只能硬着头皮上!

  先是再认真看一下Laya官方创建简单网格的api,Laya.PrimitiveMesh.createXXXX,注意到这里有个关键词Mesh,感觉有戏,继续追踪进去看(这里看的是laya.d3.js,这个是ide中集成的基础类库,需要手动导入),随便拿了一个api,createBox,看看源码:

var vertexCount=24;
var indexCount=36;
var vertexDeclaration=VertexMesh.getVertexDeclaration("POSITION,NORMAL,UV");

上面简单的摘了一段代码下来,主要是看到了几个关键词:vertexCount,indexCount,UV,感觉跟unity3d那篇讲Mesh编程里面涉及到的关键词相似,再一次感觉有戏!联想到在laya的论坛有看到说涉及到底层编程,自己写mesh代码之类的,然后看了官方提供的api,觉得应该是可以自己参考着写的,所以花了点时间来试试。

  首先扇形状3D物体,跟圆柱体类似,是圆柱体的一部分,所以我直接拿官方生成圆柱体的代码改一下。首先看一下官方的代码:

(radius === void 0) && (radius = 0.5);
(height === void 0) && (height = 2);
(slices === void 0) && (slices = 32);
var vertexCount = (slices + 1 + 1) + (slices + 1) * 2 + (slices + 1 + 1);
var indexCount = 3 * slices + 6 * slices + 3 * slices;
var vertexDeclaration = VertexMesh.getVertexDeclaration("POSITION,NORMAL,UV");
var vertexFloatStride = vertexDeclaration.vertexStride / 4;
var vertices = new Float32Array(vertexCount * vertexFloatStride);
var indices = new Uint16Array(indexCount);
var sliceAngle = (Math.PI * 2.0) / slices;
var halfHeight = height / 2;
var curAngle = 0;
var verticeCount = 0;
var posX = 0;
var posY = 0;
var posZ = 0;
var vc = 0;
var ic = 0;

// 部分一
for (var tv = 0; tv <= slices; tv++) {
    if (tv === 0) {
        vertices[vc++] = 0;
        vertices[vc++] = halfHeight;
        vertices[vc++] = 0;
        vertices[vc++] = 0;
        vertices[vc++] = 1;
        vertices[vc++] = 0;
        vertices[vc++] = 0.5;
        vertices[vc++] = 0.5;
    }
    curAngle = tv * sliceAngle;
    posX = Math.cos(curAngle) * radius;
    posY = halfHeight;
    posZ = Math.sin(curAngle) * radius;
    vertices[vc++] = posX;
    vertices[vc++] = posY;
    vertices[vc++] = posZ;
    vertices[vc++] = 0;
    vertices[vc++] = 1;
    vertices[vc++] = 0;
    vertices[vc++] = 0.5 + Math.cos(curAngle) * 0.5;
    vertices[vc++] = 0.5 + Math.sin(curAngle) * 0.5;
}
for (var ti = 0; ti < slices; ti++) {
    indices[ic++] = 0;
    indices[ic++] = ti + 1;
    indices[ic++] = ti + 2;
}
verticeCount += slices + 1 + 1;

// 部分二
for (var rv = 0; rv <= slices; rv++) {
    curAngle = rv * sliceAngle;
    posX = Math.cos(curAngle + Math.PI) * radius;
    posY = halfHeight;
    posZ = Math.sin(curAngle + Math.PI) * radius;
    vertices[vc++] = posX;
    vertices[vc + (slices + 1) * 8 - 1] = posX;
    vertices[vc++] = posY;
    vertices[vc + (slices + 1) * 8 - 1] = -posY;
    vertices[vc++] = posZ;
    vertices[vc + (slices + 1) * 8 - 1] = posZ;
    vertices[vc++] = posX;
    vertices[vc + (slices + 1) * 8 - 1] = posX;
    vertices[vc++] = 0;
    vertices[vc + (slices + 1) * 8 - 1] = 0;
    vertices[vc++] = posZ;
    vertices[vc + (slices + 1) * 8 - 1] = posZ;
    vertices[vc++] = 1 - rv * 1 / slices;
    vertices[vc + (slices + 1) * 8 - 1] = 1 - rv * 1 / slices;
    vertices[vc++] = 0;
    vertices[vc + (slices + 1) * 8 - 1] = 1;
}
vc += (slices + 1) * 8;
for (var ri = 0; ri < slices; ri++) {
    indices[ic++] = ri + verticeCount + (slices + 1);
    indices[ic++] = ri + verticeCount + 1;
    indices[ic++] = ri + verticeCount;
    indices[ic++] = ri + verticeCount + (slices + 1);
    indices[ic++] = ri + verticeCount + (slices + 1) + 1;
    indices[ic++] = ri + verticeCount + 1;
}
verticeCount += 2 * (slices + 1);

// 部分三
for (var bv = 0; bv <= slices; bv++) {
    if (bv === 0) {
        vertices[vc++] = 0;
        vertices[vc++] = -halfHeight;
        vertices[vc++] = 0;
        vertices[vc++] = 0;
        vertices[vc++] = -1;
        vertices[vc++] = 0;
        vertices[vc++] = 0.5;
        vertices[vc++] = 0.5;
    }
    curAngle = bv * sliceAngle;
    posX = Math.cos(curAngle + Math.PI) * radius;
    posY = -halfHeight;
    posZ = Math.sin(curAngle + Math.PI) * radius;
    vertices[vc++] = posX;
    vertices[vc++] = posY;
    vertices[vc++] = posZ;
    vertices[vc++] = 0;
    vertices[vc++] = -1;
    vertices[vc++] = 0;
    vertices[vc++] = 0.5 + Math.cos(curAngle) * 0.5;
    vertices[vc++] = 0.5 + Math.sin(curAngle) * 0.5;
}
for (var bi = 0; bi < slices; bi++) {
    indices[ic++] = 0 + verticeCount;
    indices[ic++] = bi + 2 + verticeCount;
    indices[ic++] = bi + 1 + verticeCount;
}
verticeCount += slices + 1 + 1;
return PrimitiveMesh._createMesh(vertexDeclaration, vertices, indices);

  这段代码有点长,我刚看的时候也是一脸懵逼,因为这段代码没有注释,对于没有3D知识的我来说内容全靠猜。我最初是一部分一部分注释,看看注释之后的效果是什么,才慢慢理解了这段代码。我将代码主要分为三个部分,两个for循环为一部分。根据变量的命名,我最终理解的是,每部分两个for循环,第一个for循环是生成顶点数据,第二个for循环是生成顶点序号。三个部分,第一个部分画出圆柱的上部圆面,第二个部分画出圆柱的柱包围,第三个部分画出圆柱的下部圆面,所以我要扣出扇状物体,就是要从这三个部分中去扣。

  首先,我试着将每个for循环的限制条件值slices改到原来的0.7,试着看看效果:

  嗯。。。有点样子了,不过旋转看下发现有缺陷,缺口部分没有封起来,空空的。然后开始了折腾的过程了。

  回看上面提到的Mesh编程教程里面的内容,3D世界中的物体都是通过无数个三角形组成的,所以这两个缺口应该是需要画三角形来填好。然后再想一下,缺口其实就是两个矩形,不就是画4个三角形就填好了么,挺简单的嘛。。。但是怎么画呢?之前可从来没玩过这个。

  依葫芦画瓢,我先画一个三角形看看:

// 内径上顶点
vertices[vc++] = 0;
vertices[vc++] = halfHeight;
vertices[vc++] = 0;
vertices[vc++] = 0;
vertices[vc++] = 1;
vertices[vc++] = 0;
vertices[vc++] = 0.5;
vertices[vc++] = 0.5;
// 内径下顶点
vertices[vc++] = 0;
vertices[vc++] = -halfHeight;
vertices[vc++] = 0;
vertices[vc++] = 0;
vertices[vc++] = 1;
vertices[vc++] = 0;
vertices[vc++] = 0.5;
vertices[vc++] = 0.5;
// 外径上顶点 起点边
vertices[vc++] = radius;
vertices[vc++] = halfHeight;
vertices[vc++] = 0;
vertices[vc++] = 0;
vertices[vc++] = 1;
vertices[vc++] = 0;
vertices[vc++] = 0.5;
vertices[vc++] = 0.5;
// 画三角形
indices[ic++] = verticeCount + 0;
indices[ic++] = verticeCount + 1;
indices[ic++] = verticeCount + 2;

  找到三个点是挺简单的。但是一开始我不知道在Laya中应该怎么描述出来,后来反反复复的看官方的例子,大概猜了一下,一个顶点由8位数据描述,前三个是坐标,最后两个影响贴图,中间三个不确定,依葫芦画瓢先找三个点再说。。。然后是顶点索引,前面的教程有提到顶点索引的顺时针、逆时针顺序,会影响最终显示出来的三角形,一直不理解是什么意思,一直改来改去的看效果,最终理解的是:0,1,2,表示先第一个顶点,再第二个,再第三个为顺时针方向,如果0,2,1,则表示先第一个,再第三个,再第二个为逆时针方向,会影响显示面(前面教程中提到0,1,2为直线,会导致看不见,第三个点需要到下一行的第一个。这句话还是没理解透)。最终摸索出上述代码,画出来一个三角形。然后再折腾好久,两个封边都画出来了,上最终代码:

radius = radius === undefined ? 0.5 : radius
height = height === undefined ? 2 : height
slices = slices === undefined ? 32 : slices
sectorAngle = sectorAngle === undefined ? 360 : sectorAngle

// 封边顶点
let edgeVertex = sectorAngle < 360 ? 6 : 0
let edgeIndex = sectorAngle < 360 ? 4 : 0
// 需要画的顶点
let drawVertex = slices * (sectorAngle / 360).toFixed(1)

// 顶点数量(上圆面,前后封边,中间厚度,下圆面)
let vertexCount = (drawVertex + 1 + 1) + edgeVertex + (drawVertex + 1) * 2 + (drawVertex + 1 + 1)
// 索引数量
let indexCount = 3 * drawVertex + edgeIndex * 3 + 6 * drawVertex + 3 * drawVertex
// 顶点声明
let vertexDeclaration = Laya.VertexMesh.getVertexDeclaration("POSITION,NORMAL,UV")
let vertexFloatStride = vertexDeclaration.vertexStride / 4
// 申请数据
let vertices = new Float32Array(vertexCount * vertexFloatStride)
let indices = new Uint16Array(indexCount)
// 切片角度
let sliceAngle = (Math.PI * 2.0) / slices
// 半高(模型处于坐标中心点)
let halfHeight = height * 0.5
// 
let curAngle = 0
// 当前顶点数量
let verticeCount = 0
// 坐标位置
let posX = 0
let posY = 0
let posZ = 0
// 数组索引
let vc = 0
let ic = 0

// 调整切片总数
//slices *= (sectorAngle / 360).toFixed(1)
// 这个值如果是个小数,会出现问题
slices = drawVertex

// 每8个数据,描述一个点信息:前三个为坐标,中间三个好像是影响光照效果,后两个不确定
// 上圆面顶点数据
for (let tv = 0; tv <= slices; tv++) {
    if (tv === 0) {
        vertices[vc++] = 0
        vertices[vc++] = halfHeight
        vertices[vc++] = 0
        vertices[vc++] = 0
        vertices[vc++] = 1
        vertices[vc++] = 0
        vertices[vc++] = 0.5
        vertices[vc++] = 0.5
    }
    curAngle = tv * sliceAngle
    posX = Math.cos(curAngle) * radius
    posY = halfHeight
    posZ = Math.sin(curAngle) * radius
    vertices[vc++] = posX
    vertices[vc++] = posY
    vertices[vc++] = posZ
    vertices[vc++] = 0
    vertices[vc++] = 1
    vertices[vc++] = 0
    vertices[vc++] = 0.5 + Math.cos(curAngle) * 0.5
    vertices[vc++] = 0.5 + Math.sin(curAngle) * 0.5
}
// 上部圆面三角索引数据
for (var ti = 0; ti < slices; ti++) {
    indices[ic++] = 0
    indices[ic++] = ti + 1
    indices[ic++] = ti + 2
}
verticeCount += slices + 1 + 1

// 画封边 起点封边和结束封边,总共6个点,要画4个三角形
if (edgeVertex > 0) {
    // 内径上顶点
    vertices[vc++] = 0
    vertices[vc++] = halfHeight
    vertices[vc++] = 0
    vertices[vc++] = 0
    vertices[vc++] = 1
    vertices[vc++] = 0
    vertices[vc++] = 0.5
    vertices[vc++] = 0.5
    // 内径下顶点
    vertices[vc++] = 0
    vertices[vc++] = -halfHeight
    vertices[vc++] = 0
    vertices[vc++] = 0
    vertices[vc++] = 1
    vertices[vc++] = 0
    vertices[vc++] = 0.5
    vertices[vc++] = 0.5
    // 外径上顶点 起点边
    vertices[vc++] = radius
    vertices[vc++] = halfHeight
    vertices[vc++] = 0
    vertices[vc++] = 0
    vertices[vc++] = 1
    vertices[vc++] = 0
    vertices[vc++] = 0.5
    vertices[vc++] = 0.5
    // 外径下顶点 起点边
    vertices[vc++] = radius
    vertices[vc++] = -halfHeight
    vertices[vc++] = 0
    vertices[vc++] = 0
    vertices[vc++] = 1 // 这个会影响光照
    vertices[vc++] = 0
    vertices[vc++] = 0.5
    vertices[vc++] = 0.5
    // 外径上顶点 结束边
    vertices[vc++] = posX
    vertices[vc++] = halfHeight
    vertices[vc++] = posZ
    vertices[vc++] = 0
    vertices[vc++] = 1
    vertices[vc++] = 0
    vertices[vc++] = 0.5
    vertices[vc++] = 0.5
    // 外径下顶点 结束边
    vertices[vc++] = posX
    vertices[vc++] = -halfHeight
    vertices[vc++] = posZ
    vertices[vc++] = 0
    vertices[vc++] = 1 // 这个会影响光照
    vertices[vc++] = 0
    vertices[vc++] = 0.5
    vertices[vc++] = 0.5
    // 画两个三角形(内上、内下、外上, 外下、外上、内下) 起点封边
    indices[ic++] = verticeCount + 0
    indices[ic++] = verticeCount + 1
    indices[ic++] = verticeCount + 2
    indices[ic++] = verticeCount + 3
    indices[ic++] = verticeCount + 2
    indices[ic++] = verticeCount + 1
    // 画两个三角形(内上、内下、外上, 外下、外上、内下) 结束封边
    indices[ic++] = verticeCount + 0
    indices[ic++] = verticeCount + 4
    indices[ic++] = verticeCount + 1
    indices[ic++] = verticeCount + 5
    indices[ic++] = verticeCount + 1
    indices[ic++] = verticeCount + 4
    verticeCount += edgeVertex
}

// 画出厚度外圈
for (let rv = 0; rv <= slices; rv++) {
    curAngle = rv * sliceAngle
    posX = Math.cos(curAngle) * radius
    posY = halfHeight
    posZ = Math.sin(curAngle) * radius

    vertices[vc++] = posX
    vertices[vc + (slices + 1) * 8 - 1] = posX

    vertices[vc++] = posY
    vertices[vc + (slices + 1) * 8 - 1] = -posY

    vertices[vc++] = posZ
    vertices[vc + (slices + 1) * 8 - 1] = posZ

    vertices[vc++] = posX
    vertices[vc + (slices + 1) * 8 - 1] = posX

    vertices[vc++] = 0
    vertices[vc + (slices + 1) * 8 - 1] = 0

    vertices[vc++] = posZ
    vertices[vc + (slices + 1) * 8 - 1] = posZ

    vertices[vc++] = 1 - rv * 1 / slices
    vertices[vc + (slices + 1) * 8 - 1] = 1 - rv * 1 / slices

    vertices[vc++] = 0
    vertices[vc + (slices + 1) * 8 - 1] = 1
}
vc += (slices + 1) * 8
// z轴三角
for (let ri = 0; ri < slices; ri++) {
    indices[ic++] = ri + verticeCount + (slices + 1)
    indices[ic++] = ri + verticeCount + 1
    indices[ic++] = ri + verticeCount
    indices[ic++] = ri + verticeCount + (slices + 1)
    indices[ic++] = ri + verticeCount + (slices + 1) + 1
    indices[ic++] = ri + verticeCount + 1
}
verticeCount += 2 * (slices + 1)

// 画出下圆面
for (let bv = 0; bv <= slices; bv++) {
    if (bv === 0) {
        vertices[vc++] = 0
        vertices[vc++] = -halfHeight
        vertices[vc++] = 0
        vertices[vc++] = 0
        vertices[vc++] = -1
        vertices[vc++] = 0
        vertices[vc++] = 0.5
        vertices[vc++] = 0.5
    }
    curAngle = bv * sliceAngle
    posX = Math.cos(curAngle) * radius
    posY = -halfHeight
    posZ = Math.sin(curAngle) * radius
    vertices[vc++] = posX
    vertices[vc++] = posY
    vertices[vc++] = posZ
    vertices[vc++] = 0
    vertices[vc++] = -1
    vertices[vc++] = 0
    vertices[vc++] = 0.5 + Math.cos(curAngle) * 0.5
    vertices[vc++] = 0.5 + Math.sin(curAngle) * 0.5
}
for (let bi = 0; bi < slices; bi++) {
    indices[ic++] = 0 + verticeCount
    indices[ic++] = bi + 2 + verticeCount
    indices[ic++] = bi + 1 + verticeCount
}
verticeCount += slices + 1 + 1

return Laya.PrimitiveMesh._createMesh(vertexDeclaration, vertices, indices)

  上述代码是在官方生成圆柱体的基础上改的,加了一部分我理解的注释,可能有误。核心的地方在第一部分for循环后,加入的封边操作,最终画出来的基本上达到了效果。

原文地址:https://www.cnblogs.com/zhong-dev/p/11197516.html