说明
一般情况下,在 3D 中创建一个平面几何体,都是由两个三角形组成一个四边形。对于普通的显示来说,这已经足够了,但有时候我们希望可以有更多的顶点,以便能完成一些变形动画。这时就需要创建细分的平面网格。
计算
首先,我们要知道水平和竖直的点的个数,计算这些点的坐标,并加入顶点数组中。其实顶点坐标的计算和 UV 是类似的,都是计算水平竖直的百分比,只是顶点需要再将百分比转为 -0.5~+0.5 范围并乘以对应宽高,而 UV 并不需要。
// x_v 为水平方向第几个点,vertices.0 为水平总点数,width 为物理宽度
meshPositions.append([
(Float(x_v) / Float(vertices.0 - 1) - 0.5) * width,
0,
(0.5 - Float(y_v) / Float(vertices.1 - 1)) * depth
])
接下来,是 UV 的计算。UV 坐标的范围是 0~1,这一点很好计算。
需要说明的是,Mesh 中 UV 的原点在左下角,x 轴向右为正, y 轴向上为正。而当我们使用
ModelDebugOptionsComponent(visualizationMode: .textureCoordinates)对 UV 可视化时,会发现左上角为原点,y 轴向下为正。这可能是因为 RealityKit 为了兼容 UIKit 坐标做了 y 轴翻转,以便更好和 UIImage 的贴图对应起来。
最后,还有三角形索引 indices 的计算,它是用来说明,哪三个点构成一个三角形。我们可以一次添加 6 个点,也就是两个三角形,刚好构成一个四边形网格。注意三角形顶点的顺序,它们决定三角形的正面朝向哪里。
代码
extension MeshResource {
/// Creates a new plane mesh with the specified values.
/// - Parameters:
/// - width: Width of the output plane
/// - depth: Depth of the output plane
/// - vertices: Vertex count in the x and z axis
/// - Returns: A plane mesh
public static func generateDetailedPlane(
width: Float, depth: Float, vertices: (Int, Int)
) throws -> MeshResource {
var descr = MeshDescriptor()
var meshPositions: [SIMD3<Float>] = []
var indices: [UInt32] = []
var textureMap: [SIMD2<Float>] = []
for x_v in 0..<(vertices.0) {
let vertexCounts = meshPositions.count
for y_v in 0..<(vertices.1) {
meshPositions.append([
(Float(x_v) / Float(vertices.0 - 1) - 0.5) * width,
0,
(0.5 - Float(y_v) / Float(vertices.1 - 1)) * depth
])
textureMap.append([Float(x_v) / Float(vertices.0 - 1), Float(y_v) / Float(vertices.1 - 1)])
if x_v > 0 && y_v > 0 {
indices.append(
contentsOf: [
vertexCounts - vertices.1, vertexCounts, vertexCounts - vertices.1 + 1,
vertexCounts - vertices.1 + 1, vertexCounts, vertexCounts + 1
].map { UInt32($0 + y_v - 1) })
}
}
}
descr.primitives = .triangles(indices)
descr.positions = MeshBuffer(meshPositions)
descr.textureCoordinates = MeshBuffers.TextureCoordinates(textureMap)
return try .generate(from: [descr])
}
}