Created
September 10, 2022 20:56
-
-
Save meshula/f9b8522ec9e9a73a731db8729ed7484d to your computer and use it in GitHub Desktop.
normalize an MDLMesh by using MetalKit
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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