说明
前一篇说过,当圆角立方体的半径等于边长一半时,就会变成一个球体。这个球体由 6 个面组成,每个面都是弯曲变形的,中间部分突出明显。我们希望对顶点分布进行调整,让点分布更均匀一些。
几何
如何调整这些顶点?这里我参考了知名 Unity 教程网站:CatLikeCoding 的两篇文章:
- Cube Sphere Better Roundness :对应中文版为 Unity Mesh基础系列(三)立方体球(更好更圆)
- Cube Sphere Going from Cube to Sphere
其中基本原理如下图所示,最早直接将立方体上的点向球心移动,会导致圆角处的点比较集中,而靠近 xyz 轴方向则比较稀疏。我们可以找到一个公式,让这些点在圆上分布更均匀一些。
文中的推导公式如下:
也就是说,我们只需要对最终得到的顶点进行如下操作即可:
var roundPositions: [SIMD3<Float>] = []
for p in meshPositions {
let n = simd_normalize(p)
let n2 = n * n // 计算各分量的平方
let x = n.x * sqrtf(1 - (n2.y + n2.z) / 2 + n2.y * n2.z / 3)
let y = n.y * sqrtf(1 - (n2.x + n2.z) / 2 + n2.x * n2.z / 3)
let z = n.z * sqrtf(1 - (n2.x + n2.y) / 2 + n2.x * n2.y / 3)
let newN = simd_normalize(SIMD3<Float>(x, y, z))
normals.append(newN)
roundPositions.append(newN * radius)
}
最终结果,贴图的变形程度更小
代码
public static func generateCubeSphere(radius: Float, resolution: Int = 10, splitFaces: Bool = false) throws -> MeshResource {
var descr = MeshDescriptor()
var meshPositions: [SIMD3<Float>] = []
var indices: [UInt32] = []
var normals: [SIMD3<Float>] = []
var textureMap: [SIMD2<Float>] = []
var materials: [UInt32] = []
let edge = resolution > 2 ? resolution : 3
let edgeMinusOne = edge - 1
let edgeMinusOnef = Float(edgeMinusOne)
let edgeMinusOneSqr = edgeMinusOne * edgeMinusOne
let edgeInc = 2 * radius / edgeMinusOnef
let facePointCount = edge * edge
// +X
for j in 0..<edge {
let startY = radius
let startZ = radius
let jf = Float(j)
let uvy = jf / edgeMinusOnef
for i in 0..<edge {
let p = SIMD3<Float>(radius, startY - edgeInc * jf, startZ - edgeInc * Float(i))
meshPositions.append(p)
let uv = SIMD2<Float>(Float(i) / edgeMinusOnef, 1 - uvy)
textureMap.append(uv)
if j != edgeMinusOne && i != edgeMinusOne {
let index = UInt32(i + j * edge)
let tl = index
let tr = tl + 1
let bl = index + UInt32(edge)
let br = bl + 1
indices.append(contentsOf: [tl,bl,tr,
tr,bl,br])
}
}
}
// -X
for j in 0..<edge {
let startY = radius
let startZ = -radius
let jf = Float(j)
let uvy = jf / edgeMinusOnef
for i in 0..<edge {
let p = SIMD3<Float>(-radius, startY - edgeInc * jf, startZ + edgeInc * Float(i))
meshPositions.append(p)
let uv = SIMD2<Float>(Float(i) / edgeMinusOnef, 1 - uvy)
textureMap.append(uv)
if j != edgeMinusOne && i != edgeMinusOne {
let index = UInt32(i + j * edge + facePointCount)
let tl = index
let tr = tl + 1
let bl = index + UInt32(edge)
let br = bl + 1
indices.append(contentsOf: [tl,bl,tr,
tr,bl,br])
}
}
}
if splitFaces {
materials.append(contentsOf: Array(repeating: 0, count: edgeMinusOneSqr * 4))
}
// +Y
for j in 0..<edge {
let startX = -radius
let startZ = -radius
let jf = Float(j)
let uvy = jf / edgeMinusOnef
for i in 0..<edge {
let p = SIMD3<Float>(startX + edgeInc * Float(i), radius, startZ + edgeInc * jf)
meshPositions.append(p)
let uv = SIMD2<Float>(Float(i) / edgeMinusOnef, 1 - uvy)
textureMap.append(uv)
if j != edgeMinusOne && i != edgeMinusOne {
let index = UInt32(i + j * edge + facePointCount * 2)
let tl = index
let tr = tl + 1
let bl = index + UInt32(edge)
let br = bl + 1
indices.append(contentsOf: [tl,bl,tr,
tr,bl,br])
}
}
}
// -Y
for j in 0..<edge {
let startX = radius
let startZ = -radius
let jf = Float(j)
let uvy = jf / edgeMinusOnef
for i in 0..<edge {
let p = SIMD3<Float>(startX - edgeInc * Float(i), -radius, startZ + edgeInc * jf)
meshPositions.append(p)
let uv = SIMD2<Float>(Float(i) / edgeMinusOnef, 1 - uvy)
textureMap.append(uv)
if j != edgeMinusOne && i != edgeMinusOne {
let index = UInt32(i + j * edge + facePointCount * 3)
let tl = index
let tr = tl + 1
let bl = index + UInt32(edge)
let br = bl + 1
indices.append(contentsOf: [tl,bl,tr,
tr,bl,br])
}
}
}
if splitFaces {
materials.append(contentsOf: Array(repeating: 1, count: edgeMinusOneSqr * 4))
}
// +Z
for j in 0..<edge {
let startY = radius
let startX = -radius
let jf = Float(j)
let uvy = jf / edgeMinusOnef
for i in 0..<edge {
let p = SIMD3<Float>(startX + edgeInc * Float(i), startY - edgeInc * jf, radius)
meshPositions.append(p)
let uv = SIMD2<Float>(Float(i) / edgeMinusOnef, 1 - uvy)
textureMap.append(uv)
if j != edgeMinusOne && i != edgeMinusOne {
let index = UInt32(i + j * edge + facePointCount * 4)
let tl = index
let tr = tl + 1
let bl = index + UInt32(edge)
let br = bl + 1
indices.append(contentsOf: [tl,bl,tr,
tr,bl,br])
}
}
}
// -Z
for j in 0..<edge {
let startY = radius
let startX = radius
let jf = Float(j)
let uvy = jf / edgeMinusOnef
for i in 0..<edge {
let p = SIMD3<Float>(startX - edgeInc * Float(i), startY - edgeInc * jf, -radius)
meshPositions.append(p)
let uv = SIMD2<Float>(Float(i) / edgeMinusOnef, 1 - uvy)
textureMap.append(uv)
if j != edgeMinusOne && i != edgeMinusOne {
let index = UInt32(i + j * edge + facePointCount * 5)
let tl = index
let tr = tl + 1
let bl = index + UInt32(edge)
let br = bl + 1
indices.append(contentsOf: [tl,bl,tr,
tr,bl,br])
}
}
}
if splitFaces {
materials.append(contentsOf: Array(repeating: 2, count: edgeMinusOneSqr * 4))
}
var roundPositions: [SIMD3<Float>] = []
for p in meshPositions {
let n = simd_normalize(p)
let n2 = n * n
let x = n.x * sqrtf(1 - (n2.y + n2.z) / 2 + n2.y * n2.z / 3)
let y = n.y * sqrtf(1 - (n2.x + n2.z) / 2 + n2.x * n2.z / 3)
let z = n.z * sqrtf(1 - (n2.x + n2.y) / 2 + n2.x * n2.y / 3)
let newN = simd_normalize(SIMD3<Float>(x, y, z))
normals.append(newN)
roundPositions.append(newN * radius)
}
meshPositions = roundPositions
descr.positions = MeshBuffers.Positions(meshPositions)
descr.normals = MeshBuffers.Normals(normals)
descr.textureCoordinates = MeshBuffers.TextureCoordinates(textureMap)
descr.primitives = .triangles(indices)
if !materials.isEmpty {
descr.materials = MeshDescriptor.Materials.perFace(materials)
}
return try .generate(from: [descr])
}