本系列文章是對 metalkit.org 上面MetalKit內容的全面翻譯和學習.c++
Model I/O是2015
年被引入到iOS 9
和OS X 10.11
中的,這個框架幫助咱們建立更真實更有交互性的圖形.咱們能夠用它來導入/導出3D
素材,來描述燈光,材料和環境,來烘焙燈光,來細分及體素化網格,來提供基於物理效果的渲染.Model I/O用一些3D API
輕易地將咱們的資源融入到代碼裏:github
要導入一個資源,咱們只須要作:swift
var url = URL(string: "/Users/YourUsername/Desktop/imported.obj")
let asset = MDLAsset(url: url!)
複製代碼
要導出一個素材咱們只要作:bash
url = URL(string: "/Users/YourUsername/Desktop/exported.obj")
try! asset.export(to: url!)
複製代碼
Model I/O會保存 .obj文件和一個額外的 .mtl文件,其中包含了物體材質的信息,好比這個例子:框架
# Apple ModelI/O MTL File: exported.mtl
newmtl material_1
Kd 0.8 0.8 0.8
Ka 0 0 0
Ks 0 0 0
ao 0 0 0
subsurface 0 0 0
metallic 0 0 0
specularTint 0 0 0
roughness 0.9 0 0
anisotropicRotation 0 0 0
sheen 0.05 0 0
sheenTint 0 0 0
clearCoat 0 0 0
clearCoatGloss 0 0 0
複製代碼
將Model I/O
和Metal
融合只須要四步:ide
首先咱們建立一個頂點描述符來傳遞輸入項到頂點函數.頂點描述符是用來描述輸入到渲染狀態管線的頂點屬性.咱們須要3 x 4
字節給頂點位置,4 x 1
字節給顏色,2 x 2
字節給紋理座標,4 x 1
字節給AO環境光遮蔽.最後咱們告訴描述符,總的stride步幅
是多長(24):函數
let vertexDescriptor = MTLVertexDescriptor()
vertexDescriptor.attributes[0].offset = 0
vertexDescriptor.attributes[0].format = MTLVertexFormat.float3 // position
vertexDescriptor.attributes[1].offset = 12
vertexDescriptor.attributes[1].format = MTLVertexFormat.uChar4 // color
vertexDescriptor.attributes[2].offset = 16
vertexDescriptor.attributes[2].format = MTLVertexFormat.half2 // texture
vertexDescriptor.attributes[3].offset = 20
vertexDescriptor.attributes[3].format = MTLVertexFormat.float // occlusion
vertexDescriptor.layouts[0].stride = 24
let renderPipelineDescriptor = MTLRenderPipelineDescriptor()
renderPipelineDescriptor.vertexDescriptor = vertexDescriptor
let rps = device.newRenderPipelineStateWithDescriptor(renderPipelineDescriptor)
複製代碼
咱們還須要建立一個Model I/O
的頂點描述符來描述頂點屬性在網格中的佈局.咱們使用一個名爲Farmhouse.obj的模型,它有一個Farmhouse.png紋理(都已經添加到項目中了):佈局
let desc = MTKModelIOVertexDescriptorFromMetal(vertexDescriptor)
var attribute = desc.attributes[0] as! MDLVertexAttribute
attribute.name = MDLVertexAttributePosition
attribute = desc.attributes[1] as! MDLVertexAttribute
attribute.name = MDLVertexAttributeColor
attribute = desc.attributes[2] as! MDLVertexAttribute
attribute.name = MDLVertexAttributeTextureCoordinate
attribute = desc.attributes[3] as! MDLVertexAttribute
attribute.name = MDLVertexAttributeOcclusionValue
let mtkBufferAllocator = MTKMeshBufferAllocator(device: device!)
let url = Bundle.main.url(forResource: "Farmhouse", withExtension: "obj")
let asset = MDLAsset(url: url!, vertexDescriptor: desc, bufferAllocator: mtkBufferAllocator)
複製代碼
下一步,爲素材加載紋理:post
let loader = MTKTextureLoader(device: device)
let file = Bundle.main.path(forResource: "Farmhouse", ofType: "png")
let data = try Data(contentsOf: URL(fileURLWithPath: file))
let texture = try loader.newTexture(with: data, options: nil)
複製代碼
MetalKit
mesh and submesh objects創建MetalKit
網格和子網格對象咱們如今正在建立在最後一步,第四步中用到的網格和子網格.咱們還要計算Ambient Occlusion環境光遮蔽,它是對幾何體遮斷的度量,它告訴咱們環境光有多少到達了咱們物體的各個像素或點,以及光線被周圍的網格阻礙了多少.Model I/O
提供了一個UV
製圖器來建立2D
紋理並將其包裹在物體的3D
網格上.咱們爲紋理中的每一個像素計算其環境光遮蔽數值,這個值是添加一每一個頂點上的額外的浮點數:
let mesh = asset.object(at: 0) as? MDLMesh
mesh.generateAmbientOcclusionVertexColors(withQuality: 1, attenuationFactor: 0.98, objectsToConsider: [mesh], vertexAttributeNamed: MDLVertexAttributeOcclusionValue)
let meshes = try MTKMesh.newMeshes(from: asset, device: device!, sourceMeshes: nil)
複製代碼
Metal
rendering and drawing of meshes創建Metal
渲染和繪圖網格最後,咱們用網格數據來配置繪圖所需的命令編碼器:
let mesh = (meshes?.first)!
let vertexBuffer = mesh.vertexBuffers[0]
commandEncoder.setVertexBuffer(vertexBuffer.buffer, offset: vertexBuffer.offset, at: 0)
let submesh = mesh.submeshes.first!
commandEncoder.drawIndexedPrimitives(submesh.primitiveType, indexCount: submesh.indexCount, indexType: submesh.indexType, indexBuffer: submesh.indexBuffer.buffer, indexBufferOffset: submesh.indexBuffer.offset)
複製代碼
下一步,咱們將致力於咱們的着色器函數.首先咱們爲頂點和uniforms創建本身的結構體:
struct VertexIn {
float4 position [[attribute(0)]];
float4 color [[attribute(1)]];
float2 texCoords [[attribute(2)]];
float occlusion [[attribute(3)]];
};
struct VertexOut {
float4 position [[position]];
float4 color;
float2 texCoords;
float occlusion;
};
struct Uniforms {
float4x4 modelViewProjectionMatrix;
};
複製代碼
注意,我讓頂點描述符中的信息和VertexIn
結構體相匹配.對於頂點函數,咱們使用了一個** [[stage_in]]**屬性,由於咱們將把每一個頂點的輸入值做爲一個參數傳遞到該函數:
vertex VertexOut vertex_func(const VertexIn vertices [[stage_in]],
constant Uniforms &uniforms [[buffer(1)]],
uint vertexId [[vertex_id]])
{
float4x4 mvpMatrix = uniforms.modelViewProjectionMatrix;
float4 position = vertices.position;
VertexOut out;
out.position = mvpMatrix * position;
out.color = float4(1);
out.texCoords = vertices.texCoords;
out.occlusion = vertices.occlusion;
return out;
}
複製代碼
片斷函數讀取從頂點函數中傳遞過來的每一個片斷做爲輸入值,並經過命令編碼器處理咱們傳遞過去的紋理:
fragment half4 fragment_func(VertexOut fragments [[stage_in]],
texture2d<float> textures [[texture(0)]])
{
float4 baseColor = fragments.color;
return half4(baseColor);
}
複製代碼
若是你運行playground,你會看到這樣的輸出圖片:
這是個至關無趣的純白模型.讓咱們給它應用上環境光遮蔽,只要在片斷函數中用下面幾行替換最後一行就好了:
float4 occlusion = fragments.occlusion;
return half4(baseColor * occlusion);
複製代碼
若是你運行playground,你會看到這樣的輸出圖片:
環境光遮蔽看上去有點不成熟,這是由於咱們的模型是扁平的,沒有任何的曲線或表面不規則,因此環境光遮蔽不能讓它更真實.下一步,咱們用上紋理.用下面幾行替換片斷函數中的最後一行:
constexpr sampler samplers;
float4 texture = textures.sample(samplers, fragments.texCoords);
return half4(baseColor * texture);
複製代碼
若是你再運行playground,你會看到這樣的輸出圖片:
模型上的紋理看起來好多了,但若是咱們將環境光遮蔽也用上它會顯得更真實.用下面這行替換片斷函數中的最後一行:
return half4(baseColor * occlusion * texture);
複製代碼
若是你再運行playground,你會看到這樣的輸出圖片:
幾行代碼效果不錯,對吧?Model I/O
對於3D
圖形和遊戲開發者來講是個很棒的框架.網上也有不少關於Model I/O
與SceneKit
協同使用的文章,可是,我認爲將其和Metal
協同使用會更有意思! 源代碼 source code 已發佈在Github上.
下次見!