Skip to content

Instantly share code, notes, and snippets.

@meshula
Created September 10, 2022 20:56
Show Gist options
  • Save meshula/f9b8522ec9e9a73a731db8729ed7484d to your computer and use it in GitHub Desktop.
Save meshula/f9b8522ec9e9a73a731db8729ed7484d to your computer and use it in GitHub Desktop.
normalize an MDLMesh by using MetalKit
class PBRMesh {
var mtkMesh : MTKMesh?
var submeshes = [PBRSubmesh?]()
// create an array with 3 entries initialized to nil
// swift doesn't have fixed size object arrays
var pipeline = [PBRModel_Pipeline?](repeating: nil, count: 3)
init?(modelIOMesh: MDLMesh!,
vertexDescriptor: MDLVertexDescriptor!,
textureLoader: MTKTextureLoader!,
device: MTLDevice) {
guard let mdlSubmeshes = modelIOMesh.submeshes else {
return nil
}
/// @TODO only add if not supplied
modelIOMesh.addNormals(withAttributeNamed: MDLVertexAttributeNormal, creaseThreshold: 0.2)
/// @TODO only add if not supplied
modelIOMesh.addTangentBasis(forTextureCoordinateAttributeNamed: MDLVertexAttributeTextureCoordinate, normalAttributeNamed: MDLVertexAttributeNormal, tangentAttributeNamed: MDLVertexAttributeTangent)
/// @TODO only add if not supplied
modelIOMesh.addTangentBasis(forTextureCoordinateAttributeNamed: MDLVertexAttributeTextureCoordinate, tangentAttributeNamed: MDLVertexAttributeTangent, bitangentAttributeNamed: MDLVertexAttributeBitangent)
// Impose the supplied vertex descriptor, which will cause
// Model I/O to re-layout the vertex data.
// This is done after generating tangents, because Model I/O
// needs those to be generated as f32's, but the supplied
// vertex descriptor might decimate the values to e.g. f16
modelIOMesh.vertexDescriptor = vertexDescriptor
do {
mtkMesh = try MTKMesh(mesh: modelIOMesh, device: device)
}
catch {
print("Could not create a metal kit mesh")
return nil
}
guard let metalKitMesh = mtkMesh else {
return nil
}
guard metalKitMesh.submeshes.count == mdlSubmeshes.count else {
return nil
}
for index in 0 ..< metalKitMesh.submeshes.count {
let mdlSubmesh = mdlSubmeshes[index] as! MDLSubmesh
let pbrSubmesh = PBRSubmesh(
mdlSubmesh: mdlSubmesh,
mtkSubmesh: metalKitMesh.submeshes[index],
textureLoader: textureLoader)
submeshes.append(pbrSubmesh)
}
}
} // PBRMesh
// all the unchanging data consumbed by encodable
class PBRModel {
// models may recursively contain many meshes, these arrays
// have flattened from the originating models, and correspond one to one
var _meshes = [PBRMesh?]()
var mtlVertexDesc = MTLVertexDescriptor()
var mdlVertexDesc = MDLVertexDescriptor()
init() {
self.createVertexDescriptor()
}
init(url: URL!,
view: MTKView!,
renderController: RenderController!,
device:MTLDevice) {
createVertexDescriptor()
// Create a MetalKit mesh buffer allocator so that Model I/O
// will load mesh data directly into Metal buffers accessible by the GPU
let bufferAllocator = MTKMeshBufferAllocator(device: device)
// Use Model I/O to load the model file at the URL. This returns a Model I/O asset object, which
// contains a hierarchy of Model I/O objects composing a "scene" described by the model file.
// This hierarchy may include lights, cameras, but, most importantly, mesh and submesh data
// that we'll render with Metal
let asset = MDLAsset(url: url, vertexDescriptor: nil,
bufferAllocator: bufferAllocator)
// Create a MetalKit texture loader to load material textures from files or the asset catalog
// into Metal textures
let textureLoader = MTKTextureLoader(device: device)
// Traverse the Model I/O asset hierarchy to find Model I/O meshes and create app-specific
// PBRMesh objects from those Model I/O meshes
for object in asset {
guard let assetMeshes = PBRModel.buildMeshes(
object: (object as! MDLObject),
mdlVertexDesc: mdlVertexDesc,
textureLoader: textureLoader,
device: device) else { continue }
_meshes.append(contentsOf: assetMeshes)
}
let mtlVertDesc = self.mtlVertexDesc
for mesh in self._meshes {
guard let mesh = mesh else { continue }
for submesh in mesh.submeshes {
guard let pbrsubmesh = submesh else { continue }
guard let mtkSm = submesh?.mtkSubmesh else { continue }
pbrsubmesh.mtkSubmesh = mtkSm
for i : Int in 0...2 {
pbrsubmesh.pipeline[i] = PBRModel_Pipeline(
view: view,
renderController: renderController,
mtlVertexDescriptor: mtlVertDesc,
quality: i)
}
}
}
}
init(mesh: MDLMesh?, device: MTLDevice?) {
if let mesh = mesh,
let device = device {
createVertexDescriptor()
// Create a MetalKit texture loader to load material textures from files or the asset catalog
// into Metal textures
let textureLoader = MTKTextureLoader(device: device)
// Traverse the Model I/O asset hierarchy to find Model I/O meshes and create app-specific
// PBRMesh objects from those Model I/O meshes
if let assetMeshes = PBRModel.buildMeshes(
object: mesh,
mdlVertexDesc: mdlVertexDesc,
textureLoader: textureLoader,
device: device) {
_meshes.append(contentsOf: assetMeshes)
}
}
}
// Traverse the Model I/O object hierarchy picking out Model I/O mesh objects to creating Metal
// vertex buffers, index buffers, and textures
class func buildMeshes(object: MDLObject?,
mdlVertexDesc:MDLVertexDescriptor?,
textureLoader: MTKTextureLoader!,
device: MTLDevice) -> [PBRMesh?]? {
guard let object = object else { return nil }
// ignoring MDLCamera, MDLLight, etc.
if !(object is MDLMesh) {
return nil
}
let mesh = object as! MDLMesh
let newMesh = PBRMesh(modelIOMesh: mesh,
vertexDescriptor: mdlVertexDesc,
textureLoader: textureLoader,
device: device)
var newMeshes = [PBRMesh?]()
newMeshes.append(newMesh)
// Traverse the Model I/O asset hierarchy to find further Model I/O meshes
if mdlObjectChildCount(mesh) == 0 {
return newMeshes
}
for index in 0 ..< mesh.children.count {
let child = mesh.children[index]
do {
let childMeshes = PBRModel.buildMeshes(
object: child, mdlVertexDesc: mdlVertexDesc,
textureLoader: textureLoader,
device: device)
guard let childMeshes = childMeshes else { continue }
newMeshes.append(contentsOf: childMeshes)
}
}
return newMeshes;
}
func createVertexDescriptor() {
func set(attr: PBRShader_VertexAttributeIndex,
format: MTLVertexFormat, offset: Int,
index: PBRShader_BufferIndex) {
mtlVertexDesc.attributes[Int(attr.rawValue)].format = format
mtlVertexDesc.attributes[Int(attr.rawValue)].offset = offset
mtlVertexDesc.attributes[Int(attr.rawValue)].bufferIndex = Int(index.rawValue)
}
set(attr:PBRShader_VertexAttributeIndex_Position,
format: .float3, offset: 0,
index: PBRShader_BufferIndex_MeshPositions)
set(attr:PBRShader_VertexAttributeIndex_Texcoord,
format: .float2, offset: 0,
index: PBRShader_BufferIndex_MeshGenerics)
set(attr:PBRShader_VertexAttributeIndex_Normal,
format: .half4, offset: 8,
index: PBRShader_BufferIndex_MeshGenerics)
set(attr:PBRShader_VertexAttributeIndex_Tangent,
format: .half4, offset: 16,
index: PBRShader_BufferIndex_MeshGenerics)
set(attr:PBRShader_VertexAttributeIndex_Bitangent,
format: .half4, offset: 24,
index: PBRShader_BufferIndex_MeshGenerics)
// Position Buffer Layout
mtlVertexDesc.layouts[Int(PBRShader_BufferIndex_MeshPositions.rawValue)].stride = 12
mtlVertexDesc.layouts[Int(PBRShader_BufferIndex_MeshPositions.rawValue)].stepRate = 1
mtlVertexDesc.layouts[Int(PBRShader_BufferIndex_MeshPositions.rawValue)].stepFunction = .perVertex
// Generic Attribute Buffer Layout
mtlVertexDesc.layouts[Int(PBRShader_BufferIndex_MeshGenerics.rawValue)].stride = 32
mtlVertexDesc.layouts[Int(PBRShader_BufferIndex_MeshGenerics.rawValue)].stepRate = 1
mtlVertexDesc.layouts[Int(PBRShader_BufferIndex_MeshGenerics.rawValue)].stepFunction = .perVertex
mdlVertexDesc = MTKModelIOVertexDescriptorFromMetal(mtlVertexDesc)
// create the semantic mappings for the mdl vertex attributes
func set(attr: PBRShader_VertexAttributeIndex, name: String) {
(mdlVertexDesc.attributes[Int(attr.rawValue)] as! MDLVertexAttribute).name = name
}
set(attr: PBRShader_VertexAttributeIndex_Position, name: MDLVertexAttributePosition)
set(attr: PBRShader_VertexAttributeIndex_Texcoord, name: MDLVertexAttributeTextureCoordinate)
set(attr: PBRShader_VertexAttributeIndex_Normal, name: MDLVertexAttributeNormal)
set(attr: PBRShader_VertexAttributeIndex_Tangent, name: MDLVertexAttributeTangent)
set(attr: PBRShader_VertexAttributeIndex_Bitangent, name: MDLVertexAttributeBitangent)
}
} // PBRModel
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment