S04E13:正六面体网格生成

565 阅读2分钟

说明

正六面体,也就是立方体。

几何

其实完全可以通过其他方式来构造立方体,贴图也可以有更灵活的选择。这里我们为了保持与其他几何体的一致性,也采用和其实正多面体一样的做法:根据外接球半径,生成正六面体,然后将各个面细分,并采用对称贴图模式。

基本流程和前面的正四面基本一致:
第一步,找到顶点的几何关系;
第二步,计算顶点;
第三步,利用重复顶点,构造三角形或四边形;
第四步,计算初始法线;
第五步,可选步骤,细分每个平面;
第六步,根据顶点位置计算贴图坐标;

顶点位置计算比较简单:

let a: Float = 2 * radius / sqrtf(3)//棱长
let r = a / 2 //内切球半径
let points: [SIMD3<Float>] = [
    SIMD3<Float>(r, r, r),
    SIMD3<Float>(-r, r, r),
    SIMD3<Float>(-r, r, -r),
    SIMD3<Float>(r, r, -r),
    
    SIMD3<Float>(r, -r, r),
    SIMD3<Float>(-r, -r, r),
    SIMD3<Float>(-r, -r, -r),
    SIMD3<Float>(r, -r, -r),
]

其他做法也是和前文类似,不同的是,我们采用了 Quads 四边形来直接生成立方体。前面我们都是用三角形来组成网格,其实也可以直接用四边形来生成。不管是索引生成,还是每个面再细分网格,都可以用四边形直接生成。

不过,在很多 3D 引擎中,虽然我们输入的四边形顶点和索引,但最终生成的网格仍是经过优化的三角形,在 RealityKit 中也是如此。当然,这不会影响我们这里的逻辑,只会在最终显示时,RealityKit 会将我们的四边形再分成两个三角形。

代码

/// 正六面体(立方体),radius 为外接球半径,res 四边形平面剖分次数
public static func generateHexahedron(radius: Float, res: Int = 0) throws -> MeshResource {
    let pointCount = 8
    var quads = 6
    var vertices = pointCount * 3
    
    var descr = MeshDescriptor()
    var meshPositions: [SIMD3<Float>] = []
    var indices: [UInt32] = []
    var normals: [SIMD3<Float>] = Array(repeating: .zero, count: vertices)
    var textureMap: [SIMD2<Float>] = []
    
    let a: Float = 2 * radius / sqrtf(3)//棱长
    let r = a / 2 //内切球半径
    let points: [SIMD3<Float>] = [
        SIMD3<Float>(r, r, r),
        SIMD3<Float>(-r, r, r),
        SIMD3<Float>(-r, r, -r),
        SIMD3<Float>(r, r, -r),
        
        SIMD3<Float>(r, -r, r),
        SIMD3<Float>(-r, -r, r),
        SIMD3<Float>(-r, -r, -r),
        SIMD3<Float>(r, -r, -r),
    ]
    meshPositions.append(contentsOf: points + points + points)
    
    let index: [UInt32] = [
        3, 2, 1, 0,
        4, 5, 6, 7,
        
        3, 0, 4, 7,
        1, 2, 6, 5,
        
        0, 1, 5, 4,
        2, 3, 7, 6
    ]
    var countDict: [UInt32:Int] = [:]
    for ind in index {
        let count = countDict[ind] ?? 0
        indices.append(ind + UInt32(pointCount * count))
        countDict[ind] = count + 1
    }
    
    for i in 0..<quads {
        let ai = 4 * i
        let bi = 4 * i + 1
        let ci = 4 * i + 2
        let di = 4 * i + 3
        
        let i0 = indices[ai]
        let i1 = indices[bi]
        let i2 = indices[ci]
        let i3 = indices[di]
        
        let v0 = meshPositions[Int(i0)]
        let v1 = meshPositions[Int(i1)]
        let v2 = meshPositions[Int(i2)]
        let v3 = meshPositions[Int(i3)]
        
        let faceNormal = simd_normalize((v0 + v1 + v2 + v3) / 4)
        normals[Int(i0)] = faceNormal
        normals[Int(i1)] = faceNormal
        normals[Int(i2)] = faceNormal
        normals[Int(i3)] = faceNormal
    }
    
    for _ in 0..<res {
        let newQuads = quads * 4
        let newVertices = vertices + quads * 5
        
        var newIndices: [UInt32] = []
        var pos: SIMD3<Float>
        
        for i in 0..<quads {
            let ai = 4 * i
            let bi = 4 * i + 1
            let ci = 4 * i + 2
            let di = 4 * i + 3
            
            let i0 = indices[ai]
            let i1 = indices[bi]
            let i2 = indices[ci]
            let i3 = indices[di]
            
            let v0 = meshPositions[Int(i0)]
            let v1 = meshPositions[Int(i1)]
            let v2 = meshPositions[Int(i2)]
            let v3 = meshPositions[Int(i3)]
            
            let faceNormal = normals[Int(i0)]
            normals.append(contentsOf: [faceNormal, faceNormal, faceNormal, faceNormal, faceNormal])
            
            pos = (v0 + v1) / 2
            meshPositions.append(pos)
            
            pos = (v1 + v2) / 2
            meshPositions.append(pos)
            
            pos = (v2 + v3) / 2
            meshPositions.append(pos)
            
            pos = (v0 + v3) / 2
            meshPositions.append(pos)
            
            pos = (v0 + v1 + v2 + v3) / 4
            meshPositions.append(pos)

            
            let a = UInt32(5 * i + vertices)
            let b = UInt32(5 * i + 1 + vertices)
            let c = UInt32(5 * i + 2 + vertices)
            let d = UInt32(5 * i + 3 + vertices)
            let center = UInt32(5 * i + 4 + vertices)
            newIndices.append(contentsOf: [
                i0, a, center, d,
                a, i1, b, center,
                center, b, i2, c,
                d, center, c, i3
            ])
        }
        
        indices = newIndices
        quads = newQuads
        vertices = newVertices
    }
    
    for i in 0..<meshPositions.count {
        let p = meshPositions[i]
        let n = p
      
        textureMap.append(SIMD2<Float>(abs(atan2(n.x, n.z)) / .pi, 1 - acos(n.y/radius) / .pi))
    }
    descr.positions = MeshBuffers.Positions(meshPositions)
    descr.normals = MeshBuffers.Normals(normals)
    descr.textureCoordinates = MeshBuffers.TextureCoordinates(textureMap)
    descr.primitives = .trianglesAndQuads(triangles: [], quads: indices)
    return try MeshResource.generate(from: [descr])
}