本章內(nèi)容主要是在上一篇繪制三角形的基礎上添加了圖片渲染的功能,分別說明了使用Metal和MetalKit中創(chuàng)建紋理的方法绞佩。
1袋坑、首先修改Metal shader的著色里的內(nèi)容
- 添加頂點輸入和輸出的結構體
// 輸入的頂點和紋理坐標
struct VertexIn
{
packed_float3 position;
packed_float2 st;
};
// 輸出頂點和紋理坐標,因為需要渲染紋理曲饱,可以不用輸入頂點顏色
struct VertexOut
{
float4 position [[position]];
float2 st;
};
- 頂點函數(shù)和片段函數(shù)內(nèi)容
// 添加紋理頂點坐標
vertex VertexOut texture_vertex(uint vid[[vertex_id]], const device VertexIn *vertex_array[[buffer(0)]])
{
VertexOut outVertex;
VertexIn vertexIn = vertex_array[vid];
outVertex.position = float4(vertexIn.position, 1.0);
outVertex.st = vertexIn.st;
// outVertex.color = color[vid];
return outVertex;
};
fragment float4 texture_fragment(VertextInOut inFrag[[stage_in]], texture2d<float> texas[[texture(0)]])
{
constexpr sampler defaultSampler;
float4 rgba = texas.sample(defaultSampler, inFrag.st).rgba;
return rgba;
};
2灾前、加載圖片創(chuàng)建Metal紋理
- Metal Framework中在處理貼圖上使用
CGImage
在CGContext
上draw
的方法來取得圖像, 但是通過draw
方法繪制的圖像是上下顛倒的防症。 - 首先要說的是,在iOS的不同framework中使用著不同的坐標系:
UIKit - y軸向下
Core Graphics(Quartz) - y軸向上
OpenGL ES - y軸向上
UIKit是iPhone SDK的Cocoa Touch層的核心framework哎甲,是iPhone應用程序圖形界面和事件驅(qū)動的基礎蔫敲,它和傳統(tǒng)windows桌面一樣,坐標系是y軸向下的; Core Graphics(Quartz)一個基于2D的圖形繪制引擎炭玫,它的坐標系則是y軸向上的奈嘿;而OpenGL ES是iPhone SDK的2D和3D繪制引擎,它使用左手坐標系吞加,它的坐標系也是y軸向上的裙犹,如果不考慮z軸酝惧,在 二維下它的坐標系和Quartz是一樣的。
注:不知道是不是API更新等原因伯诬,有小伙伴說圖片倒置的問題還是存在,經(jīng)過測試巫财,發(fā)現(xiàn)CGContextDrawImage繪制的圖片已經(jīng)不需要處理倒置的問題了盗似,具體原因還有待證實,或者說我這些觀點有誤的話希望有人能詳細指出
以下內(nèi)容可以忽略??
當通過CGContextDrawImage繪制圖片到一個context中時平项,如果傳入的是UIImage的CGImageRef赫舒,因為UIKit和CG坐標系y軸相反,所以圖片繪制將會上下顛倒闽瓢。解決方法有以下幾種接癌,
解決方法一:在繪制到context前通過矩陣垂直翻轉(zhuǎn)坐標系
解決方法二:使用UIImage的drawInRect函數(shù),該函數(shù)內(nèi)部能自動處理圖片的正確方向
解決方法三:垂直翻轉(zhuǎn)投影矩陣,這種方法通過設置上下顛倒的投影矩陣扣讼,使得原本y軸向上的GL坐標系看起來變成了y軸向下缺猛,并且坐標原點從屏幕左下角移到了屏幕左上角。如果你習慣使用y軸向下的坐標系進行二維操作椭符,可以使用這種方法荔燎,同時原本顛倒的圖片經(jīng)過再次顛倒后回到了正確的方向:
本人能力有限,對于我來說矩陣的處理還是有難度的销钝,所以選擇第二種相對簡單一些的方法來解決圖片上下顛倒的問題有咨。
- 新建Swift文件,引入頭文件
import Metal
import UIKit
import CoreGraphics
- 添加圖片加載方法蒸健,調(diào)用
makeTexture()
方法生成紋理
var type: MTLTextureType!
var texture: MTLTexture!
// 在處理貼圖上使用CGImage在CGContext上draw的方法來取得圖像, 但是通過draw方法繪制的圖像是上下顛倒的座享,可以通過UIImage的drawInRect函數(shù),該函數(shù)內(nèi)部能自動處理圖片的正確方向似忧,生成紋理
func loadIntoTextureWithDevice(device: MTLDevice, name: String, ext: String) -> Bool {
let path = Bundle.main.path(forResource: name, ofType: ext)
if !(path != nil) {
return false
}
let image = UIImage(contentsOfFile: path!)
let width = (image?.cgImage)!.width
let height = (image?.cgImage)!.height
let dataSize = width * height * 4
let data = UnsafeMutablePointer<UInt8>.allocate(capacity: dataSize)
let colorSpace = CGColorSpaceCreateDeviceRGB()
let context = CGContext(data: data, width: width, height: height, bitsPerComponent: 8, bytesPerRow: 4 * width, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue);
context?.draw((image?.cgImage)!, in: CGRect(x: 0, y: 0, width: CGFloat(width), height: CGFloat(height)))
// 通過UIImage的drawInRect函數(shù)渣叛,該函數(shù)內(nèi)部能自動處理圖片的正確方向
// 不知道是不是API更新了 已經(jīng)不需要這一步處理圖片方向了
// UIGraphicsPushContext(context!);
// image?.draw(in: CGRect(x: 0, y: 0, width: CGFloat(width), height: CGFloat(height)))
let textDes = MTLTextureDescriptor.texture2DDescriptor(pixelFormat: .rgba8Unorm, width: Int(width), height: Int(height), mipmapped: false)
type = textDes.textureType
texture = device.makeTexture(descriptor: textDes)
if !(texture != nil) {
return false
}
texture.replace(region: MTLRegionMake2D(0, 0, Int(width), Int(height)), mipmapLevel: 0, withBytes: context!.data!, bytesPerRow: width * 4)
// UIGraphicsPopContext()
free(data)
return true
}
- MetalKit Framework則直接提供了
MTKTextureLoader
創(chuàng)建紋理 - 引入MetalKit頭文件
import Foundation
import MetalKit
-
MTKTextureLoader
加載圖片創(chuàng)建紋理,MTKTextureLoader
中提供了異步和同步加載的方法
enum TextureError: Error {
case UIImageCreationError
case MTKTextureLoaderError
}
/*----------創(chuàng)建Metal紋理--------------
* @param device 設備
* @param name 圖片名稱
* @retun MTLTexture 紋理
*/
func makeTexture(device: MTLDevice, name: String) throws -> MTLTexture {
guard let image = UIImage(named: name) else {
throw TextureError.UIImageCreationError
}
// 處理后的圖片是倒置橡娄,要先將其倒置過來才能顯示出正圖像, 或者修改紋理坐標诗箍,將紋理坐標左上角設置為(0,0),這一步驟可以省略
let mirrorImage = UIImage(cgImage: (image.cgImage)!, scale: 1, orientation: UIImageOrientation.downMirrored)
let scaledImage = UIImage.scaleToSize(mirrorImage, size: image.size)
do {
let textureLoader = MTKTextureLoader(device: device)
let textureLoaderOption:[String: NSNumber] = [ MTKTextureLoaderOptionSRGB: false]
// 異步加載
// try textureLoader.newTexture(with: image.cgImage!, options: textureLoaderOption, completionHandler: { (<#MTLTexture?#>, <#Error?#>) in
//
// })
// 同步根據(jù)圖片創(chuàng)建新的Metal紋理
// Synchronously loads image data and creates a new Metal texturefrom a given bitmap image.
return try textureLoader.newTexture(with: scaledImage.cgImage!, options: textureLoaderOption)
}
}
// 自定義UIImage的類方法挽唉,設置圖片大小
extension UIImage {
class func scaleToSize(_ image: UIImage, size: CGSize)->UIImage {
UIGraphicsBeginImageContext(size)
image.draw(in: CGRect(origin: CGPoint.zero, size: size))
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return scaledImage!
}
3滤祖、獲取紋理坐標,渲染圖片
- 之前繪制三角形是使用頂點繪制的瓶籽,這次使用索引繪制一個四邊形匠童。
- 添加紋理屬性
var quaTexture: MTLTexture! = nil
- 添加頂點buffer
var indexBuffer: MTLBuffer! = nil
- 添加頂點和索引數(shù)組
// 3.1 在CPU創(chuàng)建一個浮點數(shù)數(shù)組,需要通過把它移動到一個MTLBuffer塑顺,來發(fā)送這些數(shù)據(jù)到GPU汤求。
let vertexData:[Float] = [
// 0.0, 1.0, 0.0,
// -1.0, -1.0, 0.0,
// 1.0, -1.0, 0.0
//position s, t
-0.5, -0.5, 0, 0, 0,
0.5, -0.5, 0, 1, 0,
0.5, 0.5, 0, 1, 1,
-0.5, 0.5, 0, 0, 1,
]
let indices:[Int32] = [
0, 1, 2,
0, 2, 3
]
- 創(chuàng)建一個新的indexBuffer俏险,存放索引數(shù)組
// 3.3 在GPU創(chuàng)建一個新的indexBuffer,存放索引數(shù)組扬绪,從CPU里輸送data
indexBuffer = device.makeBuffer(bytes: indices, length: indices.count * 4, options: MTLResourceOptions(rawValue: UInt(0)))
indexBuffer.label = "Indices"
- 編譯shader
// 6.1 通過調(diào)用device.newDefaultLibrary方法獲得的MTLibrary對象訪問到你項目中的預編譯shaders,然后通過名字檢索每個shader
let defaultLibrary = device.newDefaultLibrary()
let fragmentProgram = defaultLibrary?.makeFunction(name: "texture_fragment")
let vertextProgram = defaultLibrary?.makeFunction(name: "texture_vertex")
- 加載紋理
// 加載紋理
// 1 使用Metal
let loaded = loadTntoTextureWithDevice(device: device, name: "lena", ext: "png")
if !loaded {
print("Failed to load texture")
}
quaTexture = texture
// 2 使用MetalKit
do {
quaTexture = try makeTexture(device: device, name: "lena")
} catch {
fatalError("Error: Can not load texture")
}
- 在render方法中配置渲染命令編碼器竖独,調(diào)用
setFragmentTexture
添加紋理,drawIndexedPrimitives
根據(jù)索引數(shù)組繪制圖形挤牛。
renderEncoder.setFragmentTexture(quaTexture, at: 0)
// 根據(jù)索引畫圖
renderEncoder.drawIndexedPrimitives(type: .triangle, indexCount: indices.count, indexType: .uint32, indexBuffer: indexBuffer, indexBufferOffset: 0)
-
最終效果圖如圖所示莹痢,對于這些原理的東西了解還不是很深,網(wǎng)上的資料太少墓赴,能力有限竞膳,只能琢磨一些簡單的東西。
如果對Metal感興趣的可以下載Metal Swift Demo, GitHub上偶然看到別人寫的Demo诫硕,里面有紋理和矩陣坦辟,ModelIO和MetalKit的結合使用等例子。
最后獻上Demo例子