Metal入門教程(六)邊界檢測(cè)

前言

Metal入門教程(一)圖片繪制
Metal入門教程(二)三維變換
Metal入門教程(三)攝像頭采集渲染
Metal入門教程(四)灰度計(jì)算
Metal入門教程(五)視頻渲染

前面的教程既介紹了Metal的圖片繪制、三維變換、視頻渲染,也引入MetalPerformanceShaders處理攝像頭數(shù)據(jù)以及用計(jì)算管道實(shí)現(xiàn)灰度計(jì)算,這次嘗試實(shí)現(xiàn)sobel邊界檢測(cè)。

Metal系列教程的代碼地址;
OpenGL ES系列教程在這里

你的star和fork是我的源動(dòng)力,你的意見(jiàn)能讓我走得更遠(yuǎn)

正文

Metal shading language

這次的學(xué)習(xí)重點(diǎn)是Metal的shader語(yǔ)言Metal shading language,主要有兩個(gè)用途圖形渲染和通用計(jì)算。
Metal著色語(yǔ)言支持部分C++特性,比如說(shuō)重載(除了聲明為圖形渲染和通用計(jì)算入口的函數(shù));Metal著色語(yǔ)言不支持遞歸函數(shù)調(diào)用、new和delete操作符、虛函數(shù)、異常處理、函數(shù)指針等特性。同樣,Metal有自己的標(biāo)準(zhǔn)庫(kù),不能用C++ 11的標(biāo)準(zhǔn)庫(kù)。
Metal中常用的數(shù)據(jù)結(jié)構(gòu)有向量、矩陣、原子數(shù)據(jù)類型、緩存、紋理、采樣器、數(shù)組、用戶自定義結(jié)構(gòu)體等,C++的數(shù)據(jù)結(jié)構(gòu)double, long, unsigned long, long long,unsigned long long, long double均不支持。
常用的基礎(chǔ)數(shù)據(jù)類型:

  • half 是16bit是浮點(diǎn)數(shù),表達(dá)方式0.5h
  • float 是32bit的浮點(diǎn)數(shù),表達(dá)方式0.5f
  • size_t 是64bit的無(wú)符號(hào)整數(shù),通常用于sizeof的返回值
  • ptrdiff_t 是64bit的有符號(hào)整數(shù),通常用于指針的差值

常用的向量數(shù)據(jù)類型:

  • 一維向量:half2、half3、half4、float2、float3、float4等。
  • 二維向量:half4x4、half3x3、float4x4、float3x3等;

對(duì)于向量的訪問(wèn),比如說(shuō)vec=float4(1.0f, 1.0f, 1.0f, 1.0f),其訪問(wèn)方式可以是vec[0]、vec[1],也可以是vec.x、vec.y,也可以是vec.r、vec.g。(.xyzw和.rgba,前者對(duì)應(yīng)三維坐標(biāo),后者對(duì)應(yīng)RGB顏色空間)
同時(shí)只取部分、亂序取均可,比如說(shuō)我們常用到的float4 color=texture.bgra

Metal關(guān)鍵函數(shù)用到的指針參數(shù)要用地址空間修飾符,如下面的buffer參數(shù)

vertex RasterizerData // 返回給片元著色器的結(jié)構(gòu)體
vertexShader(uint vertexID [[ vertex_id ]], // vertex_id是頂點(diǎn)shader每次處理的index,用于定位當(dāng)前的頂點(diǎn)
             constant LYVertex *vertexArray [[ buffer(0) ]]) { // buffer表明是緩存數(shù)據(jù),0是索引

Metal內(nèi)存訪問(wèn)模式主要有兩種:Device和Constant。

  • Device模式,通用的訪問(wèn)模式,使用限制比較少;
  • Constant模式,快速訪問(wèn)只讀模式,參數(shù)對(duì)應(yīng)buffer大小不能改變;

需要注意的是,有些GPU支持**前置深度測(cè)試(early depth testing),其允許在fragment shader之前進(jìn)行深度測(cè)試。如果某個(gè)像素點(diǎn)沒(méi)有被顯示,那么可以放棄渲染,以減少運(yùn)算。
Metal同樣支持前置深度測(cè)試,實(shí)現(xiàn)方式是在fragment關(guān)鍵字前面加上[[early_fragment_tests]],且前置深度測(cè)試要求不能對(duì)像素的深度值進(jìn)行寫(xiě)操作。

深度測(cè)試講解

demo思路

基于Sobel算子,對(duì)圖像進(jìn)行邊界檢測(cè)。自定義計(jì)算shader,接受圖像的輸入并輸出檢測(cè)后的結(jié)果,效果如下:

Sobel算子的實(shí)現(xiàn)需要訪問(wèn)像素周邊的8個(gè)像素的值,在compute shader中,我們可以通過(guò)修改grid的xy坐標(biāo)進(jìn)行操作。在拿到位置的坐標(biāo)后,通過(guò)sourceTexture.read讀取像素值,分別算出橫向和豎向的差別h和v,統(tǒng)一轉(zhuǎn)亮度值。最后求h和v的向量和,再寫(xiě)回紋理中。

constant int sobelStep = 2;
constant half3 kRec709Luma = half3(0.2126, 0.7152, 0.0722); // 把rgba轉(zhuǎn)成亮度值

kernel void
sobelKernel(texture2d<half, access::read>  sourceTexture  [[texture(LYFragmentTextureIndexTextureSource)]],
                texture2d<half, access::write> destTexture [[texture(LYFragmentTextureIndexTextureDest)]],
                uint2                          grid         [[thread_position_in_grid]])
{
    /*
     
     行數(shù)     9個(gè)像素          位置
     上     | * * * |      | 左 中 右 |
     中     | * * * |      | 左 中 右 |
     下     | * * * |      | 左 中 右 |
     
     */
    half4 topLeft = sourceTexture.read(uint2(grid.x - sobelStep, grid.y - sobelStep)); // 左上
    half4 top = sourceTexture.read(uint2(grid.x, grid.y - sobelStep)); // 上
    half4 topRight = sourceTexture.read(uint2(grid.x + sobelStep, grid.y - sobelStep)); // 右上
    half4 centerLeft = sourceTexture.read(uint2(grid.x - sobelStep, grid.y)); // 中左
    half4 centerRight = sourceTexture.read(uint2(grid.x + sobelStep, grid.y)); // 中右
    half4 bottomLeft = sourceTexture.read(uint2(grid.x - sobelStep, grid.y + sobelStep)); // 下左
    half4 bottom = sourceTexture.read(uint2(grid.x, grid.y + sobelStep)); // 下中
    half4 bottomRight = sourceTexture.read(uint2(grid.x + sobelStep, grid.y + sobelStep)); // 下右
    
    half4 h = -topLeft - 2.0 * top - topRight + bottomLeft + 2.0 * bottom + bottomRight; // 橫方向差別
    half4 v = -bottom - 2.0 * centerLeft - topLeft + bottomRight + 2.0 * centerRight + topRight; // 豎方向差別
    
    half  grayH  = dot(h.rgb, kRec709Luma); // 轉(zhuǎn)換成亮度
    half  grayV  = dot(v.rgb, kRec709Luma); // 轉(zhuǎn)換成亮度
    
    // sqrt(h^2 + v^2),相當(dāng)于求點(diǎn)到(h, v)的距離,所以可以用length
    half color = length(half2(grayH, grayV));
    
    destTexture.write(half4(color, color, color, 1.0), grid); // 寫(xiě)回對(duì)應(yīng)紋理
}

demo中以Camera為圖像輸入源,實(shí)時(shí)對(duì)每一幀的圖像進(jìn)行處理


效果展示

總結(jié)

Metal shading language的重要性不言而喻,Metal入門教程(四)灰度計(jì)算重在如何搭建計(jì)算shader的通道,而sobel的實(shí)現(xiàn)相對(duì)灰度計(jì)算略為復(fù)雜,更有益于實(shí)踐練習(xí),后續(xù)還會(huì)補(bǔ)上直方圖均衡的demo。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • <轉(zhuǎn)>我也忘了轉(zhuǎn)自哪里,抱歉,感謝原作者 什么是Shader Shader(著色器)是一段能夠針對(duì)3D對(duì)象進(jìn)行操作...
    星易乾川閱讀 5,861評(píng)論 1 16
  • 轉(zhuǎn)載注明出處:點(diǎn)擊打開(kāi)鏈接 Shader(著色器)是一段能夠針對(duì)3D對(duì)象進(jìn)行操作、并被GPU所執(zhí)行的程序。Shad...
    游戲開(kāi)發(fā)小Y閱讀 3,706評(píng)論 0 4
  • 本文首發(fā)于個(gè)人博客:Lam's Blog - 【OpenGL ES】入門及繪制一個(gè)三角形,文章由MarkDown語(yǔ)...
    格子林ll閱讀 7,518評(píng)論 2 18
  • 我可以慣著你,也可以換了你!
    w小黃鴨w閱讀 221評(píng)論 0 0
  • 出門在外有老鄉(xiāng) 天涯海角不孤單 前世今生未可知 此時(shí)此刻會(huì)心笑 千家萬(wàn)戶明月夜 燈火通明炊煙起 三言兩語(yǔ)說(shuō)不盡 難...
    照亮Br閱讀 400評(píng)論 2 1

友情鏈接更多精彩內(nèi)容