Metal 練習:第五篇-MetalKit


Metal 練習:第五篇-MetalKit

此篇練習是基於前一篇 Metal 練習:第四篇-Lighting 的拓展

此篇練習完成后,將會學到如何利用MetalKit框架,同時也要使用3D數學計算相關的smid框架

第一步:MetalKit

打開前一篇練習的工程Metal 練習:第四篇-Lighting,此篇練習還要另一個文件float4x4-Extension.swift

在WWDC2015上Apple提供了MetalKit作為使用Metal一個通道。這個框架提供工具減少了在Metal上運行應用程序編寫大量的模板代碼,主要提供3個功能

  1. 紋理加載:使用MTKTextureLoader輕松加載圖片資源到Metal紋理
  2. 視圖管理:通過MTKview減少大量代碼使你的Metal渲染到屏幕上
  3. 模型I/O集成:高效加載模型資源進Metal緩存和用內置的容器管理大量數據

SIMD

SMID框架提供很多公共數據類型和函數來幫助處理向量和矩陣數學運算。前面我們用的是Objective-C的數據類型,本篇練習用Swift寫,因此轉到用本框架。現在刪掉Matrix4.mMatrix4.hHelloMetalStart-Bridging-Header.h文件以及修改Build Settings中頭文件的設置。
然后工程中全局搜索Matrix4float4x4代替。

Cmd + B一下,有很多錯誤,首先將import smid加入以下幾個文件中BufferProvider.swiftviewController.swiftNode.swift

//  BufferProvider中nextUniformsBuffer(_:modelViewMatrix:light:)方法找到下面代碼
memcpy(bufferPointer, modelViewMatrix.raw(), MemoryLayout<Float>.size * float4x4.numberOfElements())
memcpy(bufferPointer + MemoryLayout<Float>.size*float4x4.numberOfElements(), projectionMatrix.raw(), MemoryLayout<Float>.size * float4x4.numberOfElements())
memcpy(bufferPointer + 2 * MemoryLayout<Float>.size * float4x4.numberOfElements(), light.raw(), Light.size())
// 替換為
// 1. 現在矩陣是Swift的結構體,需要將它們改為可變的,然后通過引用傳遞
var projectionMatrix = projectionMatrix
var modelViewMatrix = modelViewMatrix  
// 2. 通達引用傳遞
memcpy(bufferPointer, &modelViewMatrix, MemoryLayout<Float>.size*float4x4.numberOfElements())
memcpy(bufferPointer + MemoryLayout<Float>.size*float4x4.numberOfElements(), &projectionMatrix, MemoryLayout<Float>.size*float4x4.numberOfElements())
memcpy(bufferPointer + 2*MemoryLayout<Float>.size*float4x4.numberOfElements(), light.raw(), Light.size())
// 在Node.swift中的render方法,將
let nodeModelMatrix = self.modelMatrix()
// 替換為
var nodeModelMatrix = self.modelMatrix()
// 在Node.swift中的modelMatrix方法,將
let matrix = float4x4()
// 替換為
var matrix = float4x4()

Run一下看看效果吧!!!

MetalKit 紋理加載

在查看MetalKit提供的功能前,我們回顧下MetalTexture.swift文件中加載紋理的方法loadTexture(_ device: MTLDevice, commandQ: MTLCommandQueue, flip: bool)

  1. 從一個文件中加載圖片
  2. 從圖片中獲取像素數據轉為原始字節
  3. 要求MTLDevice創建一個空的紋理
  4. 拷貝字節數據到空的紋理
    很幸運,MetalKit提供了強大的API幫助我們去加載紋理,這時我們主要使用的是MTKTextureLoader類。在我們轉向MetalKit時要將之前的MetalTexture.swift刪掉。
// 在Cubic.swift中 將 `import Metal` --> `import MetalKit`
// 將初始化方法
init(device: MTLDevice, commandQ: MTLCommandQueue) {
// 改成如下
init(device: MTLDevice, commandQ: MTLCommandQueue, textureLoader :MTKTextureLoader) {
// 將如下創建紋理的方法
let texture = MetalTexture(resourceName: "cube", ext: "png", mipmaped: true)
texture.loadTexture(device, commandQ: commandQ, flip: true)    
super.init(name: "Cube", vertices: verticesArray, device: device, texture: texture.texture)
// 改成如下代碼
let path = Bundle.main.path(forResource: "cube", ofType: "png")!
let data = NSData(contentsOfFile: path) as! Data
let texture = try! textureLoader.newTexture(with: data, options: [MTKTextureLoaderOptionSRGB : (false as NSNumber)]    
super.init(name: "Cube", vertices: verticesArray, device: device, texture: texture)
// 在ViewController.swift中 將 `import Metal` --> `import MetalKit`
// 在 MetalViewController 中添加屬性
var textureLoader: MTKTextureLoader!
// 然后在 viewDidLoad 方法中創建 device 的代碼下面加上
textureLoader = MTKTextureLoader(device: device)
// 在 ViewController類中創建 objectToDraw
objectToDraw = Cube(device: device, commandQ: commandQueue)
// 改成如下代碼
objectToDraw = Cube(device: device, commandQ: commandQueue, textureLoader: textureLoader)

MTKView

MTKViewUIView的子類,允許你快速連接到一個視圖到一個渲染通道的輸出,幫忙我實現了:

  1. 配置視圖的layerCAMetalLayer
  2. 控制繪制調用的時間
  3. 快速管理一個 MTLRenderPassDescriptor
  4. 輕松處理大小調整

使用MTKView時,你可以實現代理或者子類化為視圖提供繪制更新,這里選擇第一種。首先要將視圖改成MTKView,在Main.Storyboard選擇控制器將視圖的類切換為MTKView

MetalViewController類中,以下代碼可以刪掉

timer = CADisplayLink(target: self, selector: #selector(MetalViewController.newFrame(_:)))
timer.add(to: RunLoop.main, forMode: RunLoopMode.defaultRunLoopMode)
metalLayer = CAMetalLayer()
metalLayer.device = device
metalLayer.pixelFormat = .bgra8Unorm
metalLayer.framebufferOnly = true
view.layer.addSublayer(metalLayer)
// 以及 newFrame(_:)、gameloop(_:)方法全部刪掉

添加MTKViewDelegate協議

// MARK: - MTKViewDelegate
extension MetalViewController: MTKViewDelegate {
  
  // 1. 當MTKView大小調整時調用(這是是重置projectionMatrix時)
  func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
    projectionMatrix = float4x4.makePerspectiveViewAngle(float4x4.degrees(toRad: 85.0), 
      aspectRatio: Float(self.view.bounds.size.width / self.view.bounds.size.height), 
      nearZ: 0.01, farZ: 100.0)
  }
  
  // 2. 當在view上繪制新的幀時
  func draw(in view: MTKView) {
    render(view.currentDrawable)
  }
}
// 此處更改了render方法,將原先的render方法
func render() {
  if let drawable = metalLayer.nextDrawable() {
    self.metalViewControllerDelegate?.renderObjects(drawable)
  }
}
// 替換為
func render(_ drawable: CAMetalDrawable?) {
  guard let drawable = drawable else { return }
  self.metalViewControllerDelegate?.renderObjects(drawable)
}

我們這里通過代理來響應大小的改變,因此可以將viewDidLayoutSubviews方法刪掉
為了連接視圖的代理到控制器,在MetalViewController類屬性最下面添加如下代碼

@IBOutlet weak var mtkView: MTKView! {
  didSet {
    mtkView.delegate = self
    mtkView.preferredFramesPerSecond = 60
    mtkView.clearColor = MTLClearColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 1.0)
  }
}

添加完代碼后,要將Main.Storyboard中控制的視圖與上面的代碼相連接,這樣才真正的擁有。

現在就是要給MTKView的屬性device設置值

// 在 viewDidLoad 方法中
textureLoader = MTKTextureLoader(device: device)
// 下面添加 
mtkView.device = device

最后,刪掉沒用的屬性

var metalLayer: CAMetalLayer! = nil
var timer: CADisplayLink! = nil
var lastFrameTimestamp: CFTimeInterval = 0.0

Well Done!

參考及更多資料

float4x4-Extension.swift代碼
import Foundation
import simd
import GLKit

extension float4x4 {
  
  init() {
    self = unsafeBitCast(GLKMatrix4Identity, to: float4x4.self)
  }
  
  static func makeScale(_ x: Float, _ y: Float, _ z: Float) -> float4x4 {
    return unsafeBitCast(GLKMatrix4MakeScale(x, y, z), to: float4x4.self)
  }
  
  static func makeRotate(_ radians: Float, _ x: Float, _ y: Float, _ z: Float) -> float4x4 {
    return unsafeBitCast(GLKMatrix4MakeRotation(radians, x, y, z), to: float4x4.self)
  }
  
  static func makeTranslation(_ x: Float, _ y: Float, _ z: Float) -> float4x4 {
    return unsafeBitCast(GLKMatrix4MakeTranslation(x, y, z), to: float4x4.self)
  }
  
  static func makePerspectiveViewAngle(_ fovyRadians: Float, aspectRatio: Float, nearZ: Float, farZ: Float) -> float4x4 {
    var q = unsafeBitCast(GLKMatrix4MakePerspective(fovyRadians, aspectRatio, nearZ, farZ), to: float4x4.self)
    let zs = farZ / (nearZ - farZ)
    q[2][2] = zs
    q[3][2] = zs * nearZ
    return q
  }
  
  static func makeFrustum(_ left: Float, _ right: Float, _ bottom: Float, _ top: Float, _ nearZ: Float, _ farZ: Float) -> float4x4 {
    return unsafeBitCast(GLKMatrix4MakeFrustum(left, right, bottom, top, nearZ, farZ), to: float4x4.self)
  }
  
  static func makeOrtho(_ left: Float, _ right: Float, _ bottom: Float, _ top: Float, _ nearZ: Float, _ farZ: Float) -> float4x4 {
    return unsafeBitCast(GLKMatrix4MakeOrtho(left, right, bottom, top, nearZ, farZ), to: float4x4.self)
  }
  
  static func makeLookAt(_ eyeX: Float, _ eyeY: Float, _ eyeZ: Float, _ centerX: Float, _ centerY: Float, _ centerZ: Float, _ upX: Float, _ upY: Float, _ upZ: Float) -> float4x4 {
    return unsafeBitCast(GLKMatrix4MakeLookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ), to: float4x4.self)
  }
  
  
  mutating func scale(_ x: Float, y: Float, z: Float) {
    self = self * float4x4.makeScale(x, y, z)
  }
  
  mutating func rotate(_ radians: Float, x: Float, y: Float, z: Float) {
    self = float4x4.makeRotate(radians, x, y, z) * self
  }
  
  mutating func rotateAroundX(_ x: Float, y: Float, z: Float) {
    var rotationM = float4x4.makeRotate(x, 1, 0, 0)
    rotationM = rotationM * float4x4.makeRotate(y, 0, 1, 0)
    rotationM = rotationM * float4x4.makeRotate(z, 0, 0, 1)
    self = self * rotationM
  }
  
  mutating func translate(_ x: Float, y: Float, z: Float) {
    self = self * float4x4.makeTranslation(x, y, z)
  }
  
  static func numberOfElements() -> Int {
    return 16
  }
  
  static func degrees(toRad angle: Float) -> Float {
    return Float(Double(angle) * Double.pi / 180)
  }
  
  mutating func multiplyLeft(_ matrix: float4x4) {
    let glMatrix1 = unsafeBitCast(matrix, to: GLKMatrix4.self)
    let glMatrix2 = unsafeBitCast(self, to: GLKMatrix4.self)
    let result = GLKMatrix4Multiply(glMatrix1, glMatrix2)
    self = unsafeBitCast(result, to: float4x4.self)
  }
  
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM