ARKit Apple官翻 04 使用Metal開(kāi)發(fā)AR

通過(guò)渲染相機(jī)圖像和使用位置跟蹤信息來(lái)顯示復(fù)雜內(nèi)容并構(gòu)建自定義AR視圖。

概觀

ARKit庫(kù)包含了使用SceneKit或SpriteKit顯示簡(jiǎn)單AR體驗(yàn)的視圖類。但是,如果你建立自己的渲染引擎(或與第三方引擎集成)蓖柔,則ARKit還提供了AR體驗(yàn)自定義視圖所需的全部支持類。



在任何AR場(chǎng)景中风纠,第一步是配置一個(gè)ARSession對(duì)象來(lái)管理攝像頭拍攝和運(yùn)動(dòng)處理况鸣。該會(huì)話定義和維護(hù)設(shè)備所在真實(shí)世界空間與AR內(nèi)容建模的虛擬空間之間的對(duì)應(yīng)關(guān)系。要在自定義視圖中顯示你的AR場(chǎng)景竹观,需要:

  • 從會(huì)話中檢索視頻幀和跟蹤信息镐捧。
  • 將這些框架圖像作為視圖的背景渲染。
  • 使用跟蹤信息在攝像機(jī)圖像上方定位和繪制AR內(nèi)容臭增。
Tips

本文包含了Xcode項(xiàng)目模板中的代碼懂酱。有關(guān)完整示例代碼,請(qǐng)使用AR模板創(chuàng)建新的iOS應(yīng)用程序誊抛,然后在彈出菜單中選擇“Metal”列牺。

從會(huì)話獲取視頻幀和跟蹤數(shù)據(jù)

創(chuàng)建和持有自己的ARSession實(shí)例,并使用適合你想要支持的AR場(chǎng)景類型的會(huì)話配置來(lái)運(yùn)行它拗窃。(請(qǐng)參閱Building a Basic AR Experience瞎领。)會(huì)話從相機(jī)捕獲影像,在已經(jīng)建模的3D空間中跟蹤設(shè)備的位置和方向随夸,并提供ARFrame對(duì)象九默。每個(gè)這樣的對(duì)象都可以從當(dāng)前捕獲的幀中獲取單獨(dú)的視頻幀圖像和位置跟蹤信息。
AR會(huì)話有兩種訪問(wèn)ARFrame的方式逃魄,這取決于你的應(yīng)用程序是傾向于pull或push設(shè)計(jì)模式荤西。
如果您喜歡控制幀的時(shí)序(pull設(shè)計(jì)模式)澜搅,請(qǐng)使用會(huì)話的currentFrame屬性伍俘,在每次重新繪制視圖的內(nèi)容時(shí)來(lái)獲取當(dāng)前的幀圖像和跟蹤信息邪锌。 ARKit Xcode示例使用這種方法:

//在Renderer類中,從MTKViewDelegate.draw(in:)通過(guò)Renderer.update()中調(diào)用
func updateGameState() {           
    guard let currentFrame = session.currentFrame else {        return    }       
    updateSharedUniforms(frame: currentFrame)          
    updateAnchors(frame: currentFrame)    
    updateCapturedImageTextures(frame: currentFrame)       
    if viewportSizeDidChange {        
         viewportSizeDidChange = false                       
         updateImagePlane(frame: currentFrame)    
    }
}

或者癌瘾,如果您的應(yīng)用程序傾向于push模式觅丰,可以實(shí)現(xiàn)session(_:didUpdate:)
代理方法,并且會(huì)話將會(huì)在每次捕獲視頻幀時(shí)調(diào)用(默認(rèn)情況下為每秒60幀)妨退。
獲得一個(gè)視頻幀后妇萄,你將需要繪制相機(jī)圖像,并更新和呈現(xiàn)AR場(chǎng)景內(nèi)任何重疊內(nèi)容咬荷。

繪制相機(jī)圖像

每個(gè)ARFrame對(duì)象的capturedImage屬性包含了一個(gè)從設(shè)備攝像頭捕獲的像素緩沖區(qū)冠句。要將此圖像繪制為自定義視圖的背景,你需要從圖像內(nèi)容創(chuàng)建紋理幸乒,并提交使用這些紋理的GPU渲染命令懦底。
像素緩沖器的內(nèi)容被編碼為雙平面YCbCr(也稱為YUV)的數(shù)據(jù)格式;要渲染圖像,你需要將此像素?cái)?shù)據(jù)轉(zhuǎn)換為可繪制的RGB格式罕扎。對(duì)于使用Metal渲染聚唐,您可以在GPU著色器代碼中最有效地執(zhí)行此轉(zhuǎn)換。使用CVMetalTextureCache API從像素緩沖區(qū)創(chuàng)建兩個(gè)Metal紋理腔召,用于緩沖區(qū)的亮度(Y)和色度(CbCr)平面:

func updateCapturedImageTextures(frame: ARFrame) {
    // 從提供的幀的捕獲圖像創(chuàng)建兩個(gè)紋理(Y和CbCr)
    let pixelBuffer = frame.capturedImage
    if (CVPixelBufferGetPlaneCount(pixelBuffer) < 2) {
        return
    }
    capturedImageTextureY = createTexture(fromPixelBuffer: pixelBuffer, pixelFormat:.r8Unorm, planeIndex:0)!
    capturedImageTextureCbCr = createTexture(fromPixelBuffer: pixelBuffer, pixelFormat:.rg8Unorm, planeIndex:1)!
}

func createTexture(fromPixelBuffer pixelBuffer: CVPixelBuffer, pixelFormat: MTLPixelFormat, planeIndex: Int) -> MTLTexture? {
    var mtlTexture: MTLTexture? = nil
    let width = CVPixelBufferGetWidthOfPlane(pixelBuffer, planeIndex)
    let height = CVPixelBufferGetHeightOfPlane(pixelBuffer, planeIndex)
   
    var texture: CVMetalTexture? = nil
    let status = CVMetalTextureCacheCreateTextureFromImage(nil, capturedImageTextureCache, pixelBuffer, nil, pixelFormat, width, height, planeIndex, &texture)
    if status == kCVReturnSuccess {
        mtlTexture = CVMetalTextureGetTexture(texture!)
    }
   
    return mtlTexture
}

接下來(lái)杆查,使用將顏色變換矩陣執(zhí)行YCbCr到RGB轉(zhuǎn)換的方法來(lái)編碼繪制這兩個(gè)紋理的渲染命令:

fragment float4 capturedImageFragmentShader(ImageColorInOut in [[stage_in]],                                            texture2d<float, access::sample> capturedImageTextureY [[ texture(kTextureIndexY) ]],                                            texture2d<float, access::sample> capturedImageTextureCbCr [[ texture(kTextureIndexCbCr) ]]) {   
    constexpr sampler colorSampler(mip_filter::linear, mag_filter::linear, min_filter::linear);       
    const float4x4 ycbcrToRGBTransform = float4x4(       
        float4(+1.164380f, +1.164380f, +1.164380f, +0.000000f),       
        float4(+0.000000f, -0.391762f, +2.017230f, +0.000000f),       
        float4(+1.596030f, -0.812968f, +0.000000f, +0.000000f),       
        float4(-0.874202f, +0.531668f, -1.085630f, +1.000000f)   
    );       
    // Sample Y and CbCr textures to get the YCbCr color at the given texture coordinate    
    float4 ycbcr = float4(capturedImageTextureY.sample(colorSampler, in.texCoord).r, capturedImageTextureCbCr.sample(colorSampler, in.texCoord).rg, 1.0);       
    // Return converted RGB color    
    return ycbcrToRGBTransform * ycbcr;
}
Tips

使用 displayTransform(withViewportSize:orientation:)方法來(lái)確保攝像機(jī)圖像覆蓋整個(gè)視圖。 使用該方法臀蛛,以及完整的Metal管道設(shè)置代碼的實(shí)例代碼亲桦,請(qǐng)參閱完整的Xcode模板。(使用AR模板創(chuàng)建新的iOS應(yīng)用程序浊仆,然后從彈出菜單中選擇Metal烙肺。)

跟蹤和渲染覆蓋內(nèi)容

AR經(jīng)驗(yàn)通常側(cè)重于渲染3D重疊內(nèi)容,使內(nèi)容似乎是在相機(jī)圖像中看到的真實(shí)世界的一部分氧卧。 為了達(dá)到這個(gè)幻想桃笙,使用ARAnchor課程來(lái)模擬您自己的3D內(nèi)容相對(duì)于現(xiàn)實(shí)世界空間的位置和方向。 錨點(diǎn)提供可在渲染過(guò)程中引用的變換沙绝。
例如搏明,當(dāng)用戶點(diǎn)擊屏幕時(shí),Xcode模板創(chuàng)建位于設(shè)備前面大約20厘米處的錨點(diǎn):

func handleTap(gestureRecognize: UITapGestureRecognizer) {    
    // Create anchor using the camera's current position    
    if let currentFrame = session.currentFrame {               
        // Create a transform with a translation of 0.2 meters in front of the camera        
        var translation = matrix_identity_float4x4      
        translation.columns.3.z = -0.2        
        let transform = simd_mul(currentFrame.camera.transform, translation)               
        // Add a new anchor to the session       
        let anchor = ARAnchor(transform: transform)      
        session.add(anchor: anchor)    
    }
}

在您的渲染引擎中闪檬,使用每個(gè)ARAnchortransform屬性來(lái)放置可視內(nèi)容星著。 Xcode模板使用其handleTap方法中添加到會(huì)話中的每個(gè)錨來(lái)放置簡(jiǎn)單的立方體:

func updateAnchors(frame: ARFrame) {
    //使用當(dāng)前幀的錨點(diǎn)矩陣更新錨的緩沖
    anchorInstanceCount = min(frame.anchors.count, kMaxAnchorInstanceCount)
   
    var anchorOffset: Int = 0
    if anchorInstanceCount == kMaxAnchorInstanceCount {
        anchorOffset = max(frame.anchors.count - kMaxAnchorInstanceCount, 0)
    }
   
    for index in 0..<anchorInstanceCount {
        let anchor = frame.anchors[index + anchorOffset]
       
        //翻轉(zhuǎn)Z軸將坐標(biāo)系從右手轉(zhuǎn)換為左手
        var coordinateSpaceTransform = matrix_identity_float4x4
        coordinateSpaceTransform.columns.2.z = -1.0
       
        let modelMatrix = simd_mul(anchor.transform, coordinateSpaceTransform)
       
        let anchorUniforms = anchorUniformBufferAddress.assumingMemoryBound(to: InstanceUniforms.self).advanced(by: index)
        anchorUniforms.pointee.modelMatrix = modelMatrix
    }
}
Tips

在復(fù)雜的AR場(chǎng)景中,您可以使用命中測(cè)試或平面檢測(cè)來(lái)尋找真實(shí)世界平面的位置粗悯。 更多的詳細(xì)信息虚循,請(qǐng)參閱planeDetection 屬性和hitTest(_:types:)方法。 在這兩種情況下,ARKit都提供了ARAnchor的結(jié)果對(duì)象横缔,所以你仍然可以使用錨矩陣來(lái)放置視覺(jué)內(nèi)容铺遂。

渲染真實(shí)光照

當(dāng)你在場(chǎng)景中配置用于繪制3D內(nèi)容的陰影時(shí),請(qǐng)使用每個(gè)ARFrame中的預(yù)估照明信息來(lái)產(chǎn)生更逼真的陰影:

// 在Renderer.updateSharedUniforms(frame:):中
// 使用環(huán)境強(qiáng)度(如果提供)為場(chǎng)景設(shè)置光照

var ambientIntensity: Float = 1.0
if let lightEstimate = frame.lightEstimate {
    ambientIntensity = Float(lightEstimate.ambientIntensity) / 1000.0
}
let ambientLightColor: vector_float3 = vector3(0.5, 0.5, 0.5)
uniforms.pointee.ambientLightColor = ambientLightColor * ambientIntensity
Tips

有關(guān)本示例的完整Metal設(shè)置和渲染命令集茎刚,請(qǐng)參閱完整的Xcode模板襟锐。 (使用AR模板創(chuàng)建新的iOS應(yīng)用程序,然后從彈出菜單中選擇Metal膛锭。)

參考:

Apple官方文檔 :Displaying an AR Experience with Metal

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末粮坞,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子初狰,更是在濱河造成了極大的恐慌莫杈,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件奢入,死亡現(xiàn)場(chǎng)離奇詭異姓迅,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)俊马,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門丁存,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人柴我,你說(shuō)我怎么就攤上這事解寝。” “怎么了艘儒?”我有些...
    開(kāi)封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵聋伦,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我界睁,道長(zhǎng)觉增,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任翻斟,我火速辦了婚禮逾礁,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘访惜。我一直安慰自己嘹履,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布债热。 她就那樣靜靜地躺著砾嫉,像睡著了一般。 火紅的嫁衣襯著肌膚如雪窒篱。 梳的紋絲不亂的頭發(fā)上焕刮,一...
    開(kāi)封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天舶沿,我揣著相機(jī)與錄音,去河邊找鬼配并。 笑死括荡,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的荐绝。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼避消,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼低滩!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起岩喷,我...
    開(kāi)封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤恕沫,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后纱意,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體婶溯,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年偷霉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了迄委。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡类少,死狀恐怖叙身,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情硫狞,我是刑警寧澤信轿,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站残吩,受9級(jí)特大地震影響财忽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜泣侮,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一即彪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧活尊,春花似錦祖凫、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至宁仔,卻和暖如春稠屠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工权埠, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留榨了,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓攘蔽,卻偏偏與公主長(zhǎng)得像龙屉,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子满俗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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