Metal圖片渲染

本章內(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中在處理貼圖上使用CGImageCGContextdraw的方法來取得圖像, 但是通過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例子

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末章办,一起剝皮案震驚了整個濱河市锉走,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌纲菌,老刑警劉巖挠日,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異翰舌,居然都是意外死亡嚣潜,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門椅贱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來懂算,“玉大人,你說我怎么就攤上這事庇麦〖萍迹” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵山橄,是天一觀的道長垮媒。 經(jīng)常有香客問我,道長航棱,這世上最難降的妖魔是什么睡雇? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮饮醇,結果婚禮上它抱,老公的妹妹穿的比我還像新娘。我一直安慰自己朴艰,他們只是感情好观蓄,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布混移。 她就那樣靜靜地躺著,像睡著了一般侮穿。 火紅的嫁衣襯著肌膚如雪歌径。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天亲茅,我揣著相機與錄音沮脖,去河邊找鬼。 笑死芯急,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的驶俊。 我是一名探鬼主播娶耍,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼饼酿!你這毒婦竟也來了榕酒?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤故俐,失蹤者是張志新(化名)和其女友劉穎想鹰,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體药版,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡辑舷,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了槽片。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片何缓。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖还栓,靈堂內(nèi)的尸體忽然破棺而出碌廓,到底是詐尸還是另有隱情,我是刑警寧澤剩盒,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布谷婆,位于F島的核電站,受9級特大地震影響辽聊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜廷区,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望贾铝。 院中可真熱鬧埠帕,春花似錦、人聲如沸敛瓷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蚀瘸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間贮勃,已是汗流浹背贪惹。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工寂嘉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人泉孩。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓硼端,卻偏偏與公主長得像,于是被迫代替她去往敵國和親寓搬。 傳聞我的和親對象是個殘疾皇子珍昨,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容