Post

Replies

Boosts

Views

Activity

Reply to Creating a voxel mesh and render it using metal within a RealityKit ImmersiveView
@deeje thanks for your reply. I did a bit of research and it seems this approach cannot be really scaled for millions of voxels, right? I need to update the opacity of each individual voxel, and it seems your suggested approach requires a regeneration of the mesh each time. I tried nevertheless, but I didn't manage to go far since, again, I can't really find proper documentation on how to do this. This is what I got so far: // VoxelMesh.swift // arcade // // Created by Alejandro Marcos Aragón on 06/06/2025. // import Foundation import RealityKit import SwiftUI // Define Voxel struct struct Voxel { var position: SIMD3<Float> var color: SIMD4<Float> } // Function to create cube geometry func makeCube(at position: SIMD3<Float>, size: Float) -> (vertices: [SIMD3<Float>], normals: [SIMD3<Float>], indices: [UInt32]) { let halfSize = size / 2 let vertices: [SIMD3<Float>] = [ // Front face [-halfSize, -halfSize, halfSize], // 0 [ halfSize, -halfSize, halfSize], // 1 [ halfSize, halfSize, halfSize], // 2 [-halfSize, halfSize, halfSize], // 3 // Back face [-halfSize, -halfSize, -halfSize], // 4 [ halfSize, -halfSize, -halfSize], // 5 [ halfSize, halfSize, -halfSize], // 6 [-halfSize, halfSize, -halfSize] // 7 ].map { $0 + position } // Offset by voxel position let normals: [SIMD3<Float>] = [ [ 0, 0, 1], // Front [ 0, 0, 1], [ 0, 0, 1], [ 0, 0, 1], [ 0, 0, -1], // Back [ 0, 0, -1], [ 0, 0, -1], [ 0, 0, -1] ] let indices: [UInt32] = [ // Front 0, 1, 2, 2, 3, 0, // Right 1, 5, 6, 6, 2, 1, // Back 5, 4, 7, 7, 6, 5, // Left 4, 0, 3, 3, 7, 4, // Top 3, 2, 6, 6, 7, 3, // Bottom 0, 4, 5, 5, 1, 0 ] return (vertices, normals, indices) } // Function to create voxel mesh contents func makeVoxelMeshContents(voxels: [Voxel], voxelSize: Float = 1.0) throws -> MeshResource.Contents { var positions: [SIMD3<Float>] = [] var normals: [SIMD3<Float>] = [] var colors: [SIMD4<Float>] = [] var indices: [UInt32] = [] var uvs: [SIMD2<Float>] = [] // Reserve capacity for performance let vertexCountPerCube = 8 let indexCountPerCube = 36 positions.reserveCapacity(voxels.count * vertexCountPerCube) normals.reserveCapacity(voxels.count * vertexCountPerCube) colors.reserveCapacity(voxels.count * vertexCountPerCube) indices.reserveCapacity(voxels.count * indexCountPerCube) var vertexOffset: UInt32 = 0 // Generate geometry for each voxel for voxel in voxels { let (verts, norms, inds) = makeCube(at: voxel.position, size: voxelSize) positions.append(contentsOf: verts) normals.append(contentsOf: norms) // Repeat or generate UVs per vertex of the cube let cubeUVs: [SIMD2<Float>] = [ [0, 0], [1, 0], [1, 1], [0, 1], // front face [0, 0], [1, 0], [1, 1], [0, 1] // back face ] uvs.append(contentsOf: cubeUVs) colors.append(contentsOf: Array(repeating: voxel.color, count: verts.count)) indices.append(contentsOf: inds.map { $0 + vertexOffset }) vertexOffset += UInt32(verts.count) } // Create MeshDescriptor var descriptor = MeshDescriptor(name: "voxels") descriptor.positions = MeshBuffer(positions) descriptor.normals = MeshBuffer(normals) descriptor.textureCoordinates = MeshBuffer(uvs) // descriptor.colors = MeshBuffer(colors) descriptor.primitives = .triangles(indices) descriptor.materials = .allFaces(0) // Generate MeshResource and return its contents let mesh = try MeshResource.generate(from: [descriptor]) return mesh.contents } class VoxelMesh: ObservableObject { @Published var voxels: [Voxel] = [] var entity: ModelEntity? var elSize: Float = 1.0 var parentEntity: Entity private var isInitialized = false init(parent: Entity, voxels: [Voxel], voxelSize: Float = 1.0) { self.parentEntity = parent self.voxels = voxels self.elSize = voxelSize Task { await self.generateVoxelEntity() await MainActor.run { self.isInitialized = true // Mark as initialized } } } func waitForInitialization() async { while !isInitialized { try? await Task.sleep(nanoseconds: 100_000_000) // Wait 0.1 seconds } } func generateVoxelEntity() async { do { let contents = try makeVoxelMeshContents(voxels: voxels, voxelSize: elSize) let mesh = try await MeshResource(from: contents) guard let device = MTLCreateSystemDefaultDevice(), let library = try? device.makeDefaultLibrary(bundle: .main) else { fatalError("Failed to load default Metal library.") } let entity = await MainActor.run { return ModelEntity( mesh: mesh, materials: [SimpleMaterial(color: .white, isMetallic: false)] ) } await entity.generateCollisionShapes(recursive: false) self.entity = entity parentEntity.addChild(entity) await MainActor.run { self.isInitialized = true } } catch { print("Failed to generate voxel mesh: \(error)") } } func updateVoxelOpacities(newOpacities: [Double]) async { print("3. lalalalal") guard newOpacities.count == voxels.count else { fatalError("Number of opacity values is not the same as the number of mesh voxels") } await MainActor.run { for i in voxels.indices { var voxel = voxels[i] voxel.color.w = Float(newOpacities[i]) voxels[i] = voxel } } do { let updatedContents = try makeVoxelMeshContents(voxels: voxels, voxelSize: elSize) let newMesh = try await MeshResource(from: updatedContents) self.entity?.model?.mesh = newMesh } catch { print("Failed to update voxel mesh: \(error)") } } } It seems there are many issues behind this code, including the fact hat I can't use a SimpleMaterial to assign different parity values to all voxels.
Topic: Spatial Computing SubTopic: General Tags:
Jun ’25