[MetalKit]26-The-Model-I-O-framework-ModelI-O框架

本系列文章是對 metalkit.org 上面MetalKit內容的全面翻譯和學習.c++

MetalKit系統文章目錄git


Model I/O2015年被引入到iOS 9OS X 10.11中的,這個框架幫助咱們建立更真實更有交互性的圖形.咱們能夠用它來導入/導出3D素材,來描述燈光,材料和環境,來烘焙燈光,來細分及體素化網格,來提供基於物理效果的渲染.Model I/O用一些3D API輕易地將咱們的資源融入到代碼裏:github

modelio_1.png

要導入一個資源,咱們只須要作: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/OMetal融合只須要四步:ide

modelio_2.png

Step 1: set up the render pipeline state建立渲染管線狀態

首先咱們建立一個頂點描述符來傳遞輸入項到頂點函數.頂點描述符是用來描述輸入到渲染狀態管線的頂點屬性.咱們須要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)
複製代碼

Step 2: set up the asset initialization創建素材初始化

咱們還須要建立一個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)
複製代碼

Step 3: set up 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)
複製代碼

Step 4: set up 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,你會看到這樣的輸出圖片:

modelio_3.png

這是個至關無趣的純白模型.讓咱們給它應用上環境光遮蔽,只要在片斷函數中用下面幾行替換最後一行就好了:

float4 occlusion = fragments.occlusion;
return half4(baseColor * occlusion);
複製代碼

若是你運行playground,你會看到這樣的輸出圖片:

modelio_4.png

環境光遮蔽看上去有點不成熟,這是由於咱們的模型是扁平的,沒有任何的曲線或表面不規則,因此環境光遮蔽不能讓它更真實.下一步,咱們用上紋理.用下面幾行替換片斷函數中的最後一行:

constexpr sampler samplers;
float4 texture = textures.sample(samplers, fragments.texCoords);
return half4(baseColor * texture);
複製代碼

若是你再運行playground,你會看到這樣的輸出圖片:

modelio_5.png

模型上的紋理看起來好多了,但若是咱們將環境光遮蔽也用上它會顯得更真實.用下面這行替換片斷函數中的最後一行:

return half4(baseColor * occlusion * texture);
複製代碼

若是你再運行playground,你會看到這樣的輸出圖片:

modelio_6.png

幾行代碼效果不錯,對吧?Model I/O對於3D圖形和遊戲開發者來講是個很棒的框架.網上也有不少關於Model I/OSceneKit協同使用的文章,可是,我認爲將其和Metal協同使用會更有意思! 源代碼 source code 已發佈在Github上.

下次見!

相關文章
相關標籤/搜索