S04E17: 地理均分球体网格生成

322 阅读1分钟

说明

地理均分球体,即 GeoSphere,也是一种球体,只是三角网格划分方式与普通经纬度球体不同。

几何

从本质上说,GeoSphere 就是正二十面体经过细分后得到的球体。与前面的正二十面体不同的是:每个点的法线都是独特的,而且细分后新生成的点,会沿法线进行移动,移动到球面上。

从代码角度来讲,更简单了。无需考虑顶点法线不同的问题,也就无需再重复 3 次顶点。法线计算也简单了,直接根据每个点的坐标,进行归一化就可以了。

代码

public static func generateGeoSphere(radius: Float, res: Int = 0) throws -> MeshResource {
    let pointCount = 12
    var triangles = 20
    var vertices = pointCount
    
    var descr = MeshDescriptor()
    var meshPositions: [SIMD3<Float>] = []
    var indices: [UInt32] = []
    var normals: [SIMD3<Float>] = []
    var textureMap: [SIMD2<Float>] = []
    
    let phi = (1.0 + sqrtf(5)) * 0.5
    let r2 = radius * radius
    let den = (1.0 + (1.0 / pow(phi, 2.0)))
    let h = sqrt(r2 / (den))
    let w = h / phi

    let points = [
        SIMD3<Float>(0.0, h, w),
        SIMD3<Float>(0.0, h, -w),
        SIMD3<Float>(0.0, -h, w),
        SIMD3<Float>(0.0, -h, -w),

        SIMD3<Float>(h, -w, 0.0),
        SIMD3<Float>(h, w, 0.0),
        SIMD3<Float>(-h, -w, 0.0),
        SIMD3<Float>(-h, w, 0.0),

        SIMD3<Float>(-w, 0.0, -h),
        SIMD3<Float>(w, 0.0, -h),
        SIMD3<Float>(-w, 0.0, h),
        SIMD3<Float>(w, 0.0, h)
    ]
    meshPositions.append(contentsOf: points)
    
    let index: [UInt32] = [
        0, 11, 5,
        0, 5, 1,
        0, 1, 7,
        0, 7, 10,
        0, 10, 11,

        1, 5, 9,
        5, 11, 4,
        11, 10, 2,
        10, 7, 6,
        7, 1, 8,

        3, 9, 4,
        3, 4, 2,
        3, 2, 6,
        3, 6, 8,
        3, 8, 9,

        4, 9, 5,
        2, 4, 11,
        6, 2, 10,
        8, 6, 7,
        9, 8, 1
    ]
    
    indices.append(contentsOf: index)
    
    for _ in 0..<res {
        let newTriangles = triangles * 4
        let newVertices = vertices + triangles * 3
        
        var newIndices: [UInt32] = []
        var pos: SIMD3<Float>
        
        for i in 0..<triangles {
            let ai = 3 * i
            let bi = 3 * i + 1
            let ci = 3 * i + 2
            
            let i0 = indices[ai]
            let i1 = indices[bi]
            let i2 = indices[ci]
            
            let v0 = meshPositions[Int(i0)]
            let v1 = meshPositions[Int(i1)]
            let v2 = meshPositions[Int(i2)]
            
            // a
            pos = (v0 + v1) * 0.5
            pos = simd_normalize(pos) * radius
            meshPositions.append(pos)

            // b
            pos = (v1 + v2) * 0.5
            pos = simd_normalize(pos) * radius
            meshPositions.append(pos)
            
            // c
            pos = (v2 + v0) * 0.5
            pos = simd_normalize(pos) * radius
            meshPositions.append(pos)
            
            
            let a = UInt32(ai + vertices)
            let b = UInt32(bi + vertices)
            let c = UInt32(ci + vertices)
            newIndices.append(contentsOf: [
                i0, a, c,
                a, i1, b,
                a, b, c,
                c, b, i2
            ])
        }
        
        indices = newIndices
        triangles = newTriangles
        vertices = newVertices
    }
    
    for i in 0..<meshPositions.count {
        let p = meshPositions[i]
        let n = simd_normalize(p)
        normals.append(n)
        textureMap.append(SIMD2<Float>(abs(atan2(n.x, n.z)) / .pi, 1 - acos(n.y) / .pi))
    }
    
    descr.positions = MeshBuffers.Positions(meshPositions)
    descr.normals = MeshBuffers.Normals(normals)
    descr.textureCoordinates = MeshBuffers.TextureCoordinates(textureMap)
    descr.primitives = .triangles(indices)
    return try MeshResource.generate(from: [descr])
}