ARKit文檔翻譯之ARKit簡(jiǎn)介

ARKit

ARKit框架通過(guò)集成iOS設(shè)備攝像頭和運(yùn)動(dòng)功能袱饭,在您的應(yīng)用程序或游戲中產(chǎn)生增強(qiáng)現(xiàn)實(shí)體驗(yàn)虑乖。

概述

增強(qiáng)現(xiàn)實(shí)(AR)描述了將2D或3D元素添加到設(shè)備相機(jī)中的實(shí)時(shí)視圖的用戶體驗(yàn),使得這些元素看起來(lái)就存在于現(xiàn)實(shí)世界中疹味。ARKit框架結(jié)合了設(shè)備運(yùn)動(dòng)追蹤、相機(jī)場(chǎng)景捕捉诫咱、高級(jí)場(chǎng)景處理和便利顯示洪灯,簡(jiǎn)化了AR體驗(yàn)的任務(wù)。

重要

ARkit需要A9或更高版本處理器的iOS設(shè)備。
要使應(yīng)用程序僅在支持ARKit的設(shè)備上可用凯亮,請(qǐng)?jiān)趹?yīng)用程序的info.plist這種添加UIRequiredDeviceCapabilities-arkit鍵值對(duì)哄尔。若增強(qiáng)現(xiàn)實(shí)知識(shí)應(yīng)用程序中的一個(gè)輔助功能,請(qǐng)?jiān)陧?xiàng)目中使用isSupported屬性來(lái)確定當(dāng)前設(shè)備是否支持你要使用的會(huì)話配置岭接。

<br />

關(guān)于增強(qiáng)現(xiàn)實(shí)與ARKit

發(fā)現(xiàn)構(gòu)建強(qiáng)大的AR體驗(yàn)的支持概念鸣戴、功能和最佳實(shí)踐方式。

概述

任何AR體驗(yàn)的基本要求以及ARKit的定義特征是創(chuàng)建和追蹤用戶所在的現(xiàn)實(shí)世界空間與可視化內(nèi)容建模的虛擬空間之間的對(duì)應(yīng)關(guān)系的能力窄锅。當(dāng)您的應(yīng)用程序顯示實(shí)時(shí)攝像頭圖像的內(nèi)容時(shí),用戶體驗(yàn)到增強(qiáng)現(xiàn)實(shí):虛擬內(nèi)容是真實(shí)世界的一部分的錯(cuò)覺(jué)追驴。

在所有ARKit體驗(yàn)中疏之,ARKit使用世界和相機(jī)坐標(biāo)系,并遵循右手定則:y軸正向向上锋爪,z軸正向指向觀察者,x軸正向指向觀察者的右側(cè)亏镰。

會(huì)話配置可以改變相對(duì)于現(xiàn)實(shí)世界的坐標(biāo)系的起點(diǎn)和方向(參見(jiàn)worldAlignment)年栓。AR會(huì)話中的每個(gè)錨定義了自己的本地坐標(biāo)系,也遵循右手定則纸兔,例如:ARFaceAnchor類定義了一個(gè)用于定位面部特征的系統(tǒng)否副。

世界追蹤的工作原理

ARKit使用視覺(jué)慣性測(cè)距的技術(shù)來(lái)創(chuàng)建真實(shí)空間和虛擬空間之間的一個(gè)對(duì)應(yīng)關(guān)系。該過(guò)程將來(lái)自iOS設(shè)備的運(yùn)動(dòng)傳感器硬件的信息與設(shè)備相機(jī)可見(jiàn)的場(chǎng)景的計(jì)算機(jī)視覺(jué)分析相結(jié)合备禀。ARKit識(shí)別場(chǎng)景圖像中的顯著特征奈揍,追蹤視頻幀中這些特征位置的差異赋续,并將該信息與運(yùn)動(dòng)傳感器數(shù)據(jù)進(jìn)行比較,之后生成設(shè)備的位置和運(yùn)動(dòng)的高精度模型蛾绎。

世界追蹤還分析和了解場(chǎng)景的內(nèi)容鸦列。使用碰撞測(cè)試方法(參見(jiàn)TestResult類)來(lái)查找與攝像機(jī)圖像中的點(diǎn)相對(duì)應(yīng)的真實(shí)世界凸面。如果在會(huì)話中啟用planeDetection設(shè)置顽爹,ARKit會(huì)檢測(cè)攝像機(jī)圖像中的曲面骆姐,并報(bào)告其位置和大小〔M剩可以使用碰撞測(cè)試結(jié)果或檢測(cè)到的平面,放置在場(chǎng)景中或與虛擬內(nèi)容交互。

最佳實(shí)踐與限制

世界追蹤是一個(gè)不精確的科學(xué)稚矿。這個(gè)過(guò)程經(jīng)常會(huì)產(chǎn)生令人印象深刻的準(zhǔn)確性,從而導(dǎo)致現(xiàn)實(shí)的AR體驗(yàn)桥爽。然而昧识,它依賴于設(shè)備的物理環(huán)境的細(xì)節(jié),這些細(xì)節(jié)并不總是一致的跪楞,或者難以實(shí)時(shí)測(cè)量(帶有一定程度的錯(cuò)誤)甸祭。要建立高品質(zhì)的AR體驗(yàn)缕碎,注意一下注意事項(xiàng)和建議池户。

設(shè)計(jì)可預(yù)見(jiàn)的照明條件的AR體驗(yàn)。世界追蹤涉及圖像分析赊抖,這需要清晰的圖像。當(dāng)相機(jī)無(wú)法看到細(xì)節(jié)房匆,例如當(dāng)相機(jī)指向空白墻或場(chǎng)景太暗時(shí)注暗,追蹤質(zhì)量會(huì)降低。

使用追蹤質(zhì)量信息提供用戶反饋捆昏。世界追蹤將圖像分析與設(shè)備運(yùn)動(dòng)相關(guān)聯(lián)。如果設(shè)備正在移動(dòng)宠页,即時(shí)只是微妙移動(dòng)寇仓,ARKit可以更好地了解場(chǎng)景。過(guò)多的動(dòng)作--太遠(yuǎn)遍烦、太快或劇烈晃動(dòng)--會(huì)導(dǎo)致圖像模糊或追蹤視頻幀之間的特征距離過(guò)遠(yuǎn),從而降低追蹤質(zhì)量供填。ARCamera類提供追蹤狀態(tài)原因信息罢猪,你可以使用它來(lái)開(kāi)發(fā)UI,告訴用戶如何解決低質(zhì)量的追蹤情況粘捎。

給予平面檢測(cè)一定的時(shí)間以產(chǎn)生清晰的結(jié)果危彩,并在獲得所需結(jié)果時(shí)禁用平面檢測(cè)。平面檢測(cè)結(jié)果隨時(shí)間而變化--當(dāng)首次檢測(cè)到平面時(shí)汤徽,其位置和范圍可能不準(zhǔn)確。隨著時(shí)間的推移漆羔,該平面仍在場(chǎng)景中,ARKit對(duì)其位置和范圍的估計(jì)作了細(xì)化演痒。當(dāng)一個(gè)較大的平面在場(chǎng)景中時(shí),ARKit可能會(huì)在已經(jīng)放置好平面后繼續(xù)改變平面的位置鸟顺、范圍和轉(zhuǎn)換。

<br />

創(chuàng)建第一個(gè)AR體驗(yàn)

創(chuàng)建運(yùn)行AR會(huì)話的應(yīng)用程序蹦锋,并使用平面檢測(cè)通過(guò)SceneKit框架放置3D內(nèi)容欧芽。

下載源碼

概述

此示例app運(yùn)行一個(gè)ARKit世界追蹤會(huì)話,并顯示在SceneKit視圖內(nèi)容千扔。為演示平面檢測(cè),應(yīng)用程序僅放置一個(gè)SCNPlane對(duì)象來(lái)可視化每個(gè)檢測(cè)到的ARPlaneAnchor對(duì)象厘唾。

配置并運(yùn)行AR會(huì)話

ARSCNView類是一個(gè)SceneKit視圖龙誊,它包含一個(gè)ARSession對(duì)象,用于管理和創(chuàng)建增強(qiáng)現(xiàn)實(shí)體驗(yàn)所需的運(yùn)動(dòng)追蹤和圖像處理讯柔。但是,要運(yùn)行會(huì)話,必須提供會(huì)話配置惋耙。

ARWorldTrackingConfiguration類提供高精度的運(yùn)動(dòng)追蹤功能熊昌,可以幫助你將虛擬內(nèi)容放置在真實(shí)世界的表面。要啟動(dòng)AR會(huì)話婿屹,使用所需的選項(xiàng)(如平面檢測(cè))創(chuàng)建會(huì)話配置對(duì)象,然后在ARSCNView實(shí)例的會(huì)話對(duì)象上調(diào)用run(_:options:)方法:

let configuration = ARWorldTrackingConfiguration()
configuration.planeDetection = .horizontal
sceneView.session.run(configuration)

只有當(dāng)顯示它的視圖在屏幕上時(shí)届腐,才能運(yùn)行會(huì)話。

重要提示:如果您的應(yīng)用程序需要ARKit的核心功能硬萍,請(qǐng)使用應(yīng)用程序的Info.plist文件部分中的arkit鍵使您的應(yīng)用程序僅在支持ARKit的設(shè)備上可用围详。 如果AR是應(yīng)用程序的輔助功能,請(qǐng)使用isSupported屬性來(lái)確定是否提供基于AR的功能助赞。

在檢測(cè)到的平面上放置3D內(nèi)容

設(shè)置AR會(huì)話后,可使用SceneKit在視圖中防止虛擬內(nèi)容畜普。

當(dāng)啟用平面檢測(cè)時(shí)婉徘,ARKit會(huì)為每個(gè)檢測(cè)到的平面添加和更新錨點(diǎn)。默認(rèn)情況下盖呼,ARSCNView類為每個(gè)錨點(diǎn)的SceneKit場(chǎng)景添加一個(gè)SCNNode對(duì)象。視圖委托可以實(shí)現(xiàn)renderer(_:didAdd:for:)方法來(lái)向場(chǎng)景添加內(nèi)容约炎。

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
       // Place content only for anchors found by plane detection.
       guard let planeAnchor = anchor as? ARPlaneAnchor else { return }

       // Create a *SceneKit* plane to visualize the plane anchor using its position and extent.
       let plane = SCNPlane(width: CGFloat(planeAnchor.extent.x), height: CGFloat(planeAnchor.extent.z))
       let planeNode = SCNNode(geometry: plane)
       planeNode.simdPosition = float3(planeAnchor.center.x, 0, planeAnchor.center.z)
       
       /*
        `SCNPlane` is vertically oriented in its local coordinate space, so
        rotate the plane to match the horizontal orientation of `ARPlaneAnchor`.
       */
       planeNode.eulerAngles.x = -.pi / 2
       
       // Make the plane visualization semitransparent to clearly show real-world placement.
       planeNode.opacity = 0.25
       
       /*
        Add the plane visualization to the ARKit-managed node so that it tracks
        changes in the plane anchor as plane estimation continues.
       */
       node.addChildNode(planeNode)
}

如果將內(nèi)容添加為與錨點(diǎn)對(duì)應(yīng)的節(jié)點(diǎn)的子節(jié)點(diǎn)蟹瘾,則ARSCNView類將自動(dòng)移動(dòng)該內(nèi)容,因?yàn)锳RKit會(huì)優(yōu)化其對(duì)平面位置和范圍的估計(jì)狸捕。為了現(xiàn)實(shí)估計(jì)平面的完整范圍众雷,此示例應(yīng)用程序還實(shí)現(xiàn)了renderer(_:didUpdate:for:)方法更新SCNPlane對(duì)象的大小以反映ARKit提供的預(yù)估值。

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
    // Update content only for plane anchors and nodes matching the setup created in `renderer(_:didAdd:for:)`.
    guard let planeAnchor = anchor as?  ARPlaneAnchor,
        let planeNode = node.childNodes.first,
        let plane = planeNode.geometry as? SCNPlane
        else { return }
    
    // Plane estimation may shift the center of a plane relative to its anchor's transform.
    planeNode.simdPosition = float3(planeAnchor.center.x, 0, planeAnchor.center.z)
    
    /*
     Plane estimation may extend the size of the plane, or combine previously detected
     planes into a larger one. In the latter case, `ARSCNView` automatically deletes the
     corresponding node for one plane, then calls this method to update the size of
     the remaining plane.
    */
    plane.width = CGFloat(planeAnchor.extent.x)
    plane.height = CGFloat(planeAnchor.extent.z)
}

<br />

處理增強(qiáng)現(xiàn)實(shí)中的3D交互和UI控件

遵循AR體驗(yàn)中的視覺(jué)反饋鸡岗、手勢(shì)交互和現(xiàn)實(shí)渲染的最佳實(shí)踐编兄。

源碼下載

概述

增強(qiáng)現(xiàn)實(shí)為用戶提供了與你的應(yīng)用程序中的現(xiàn)實(shí)和虛擬3D內(nèi)容進(jìn)行交互的新方式。然而揣苏,人機(jī)接口設(shè)計(jì)的許多基本原理仍然有效。令人信服的AR幻想需要尤其注意3D資源設(shè)計(jì)和渲染舒岸。iOS人機(jī)接口指南包括有關(guān)AR接口設(shè)計(jì)原則的建議。該項(xiàng)目展現(xiàn)了應(yīng)用這些原則的方法俄认,并輕松創(chuàng)建沉浸式洪乍、直觀的AR體驗(yàn)。

此示例應(yīng)用程序提供了簡(jiǎn)單的AR體驗(yàn)岂贩,允許用戶將一個(gè)或多個(gè)逼真的虛擬對(duì)象放置在現(xiàn)實(shí)環(huán)境中巷波,然后使用直觀的手勢(shì)來(lái)排列這些對(duì)象。該應(yīng)用程序提供了用戶界面提示抹镊,以幫助用戶了解AR體驗(yàn)的狀態(tài)及其交互選項(xiàng)垮耳。

以下部分對(duì)應(yīng)于iOS Human Interface Guidelines > Augmented Reality的部分內(nèi)容,并提供有關(guān)此示例應(yīng)用程序如何實(shí)施這些原則的詳細(xì)信息终佛。

放置虛擬對(duì)象

幫助人們了解何時(shí)找到一個(gè)表面并放置一個(gè)對(duì)象FocusSquare類在AR視圖中繪制一個(gè)方形輪廓绍豁,為用戶提供關(guān)于ARKit世界追蹤狀態(tài)的提示牙捉。

該方塊通過(guò)改變大小和方向以反映預(yù)估的場(chǎng)景深度,并用突出的動(dòng)畫切換開(kāi)啟和關(guān)閉狀態(tài),以現(xiàn)實(shí)ARKit是否檢測(cè)到適合放置對(duì)象的表面霜浴。在用戶放置虛擬對(duì)象后蓝纲,焦點(diǎn)方塊消失晌纫,保持隱藏狀態(tài)永丝,直到用戶將相機(jī)指向另一個(gè)表面。

用戶放置對(duì)象時(shí)作適當(dāng)響應(yīng)哥牍。當(dāng)用戶選擇要防止的虛擬對(duì)象時(shí)喝检,示例應(yīng)用程序的setPosition(_:relativeTo:smoothMovement) 方法使用FocusSquare對(duì)象的簡(jiǎn)單啟發(fā)式方法將對(duì)象放置在屏幕中間大致逼真的位置,即使ARKit尚未在該位置檢測(cè)到一個(gè)平面挠说。

guard let cameraTransform = session.currentFrame?.camera.transform,
    let focusSquarePosition = focusSquare.lastPosition else {
    statusViewController.showMessage("CANNOT PLACE OBJECT\nTry moving left or right.")
    return
}
        
virtualObjectInteraction.selectedObject = virtualObject
virtualObject.setPosition(focusSquarePosition, relativeTo: cameraTransform, smoothMovement: false)
        
updateQueue.async {
    self.sceneView.scene.rootNode.addChildNode(virtualObject)
}

這個(gè)位置可能不是用戶想要放置虛擬對(duì)象的真實(shí)表面的準(zhǔn)確預(yù)估,但是它足夠接近以快速獲得對(duì)象损俭。

隨著時(shí)間的推移,ARKit會(huì)檢測(cè)到平面并調(diào)整其位置的預(yù)估雁仲,調(diào)用renderer(_:didAdd:for:)renderer(_:didUpdate:for:)代理方法來(lái)報(bào)告結(jié)果拧咳。在這些方法中,示例應(yīng)用程序調(diào)用其adjustOntoPlaneAnchor(_:using:)方法來(lái)確定先前放置的虛擬對(duì)象是否接近檢測(cè)到的平面祭衩。如果是這樣阅签,該方法使用微妙的動(dòng)畫將虛擬對(duì)象移動(dòng)到該平面,從而使對(duì)象看起來(lái)處于用戶選擇的位置路克。

// Move onto the plane if it is near it (within 5 centimeters).
let verticalAllowance: Float = 0.05
let epsilon: Float = 0.001 // Do not update if the difference is less than 1 mm.
let distanceToPlane = abs(planePosition.y)
if distanceToPlane > epsilon && distanceToPlane < verticalAllowance {
    SCNTransaction.begin()
    SCNTransaction.animationDuration = CFTimeInterval(distanceToPlane * 500) // Move 2 mm per second.
    SCNTransaction.animationTimingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
    position.y = anchor.transform.columns.3.y
    SCNTransaction.commit()
}

用戶與虛擬對(duì)象的交互

允許人們使用標(biāo)準(zhǔn)熟悉的手勢(shì)直接與虛擬對(duì)象進(jìn)行交互养交。示例應(yīng)用程序使用單指點(diǎn)擊、單指和雙指拖拽以及雙指旋轉(zhuǎn)手勢(shì)識(shí)別器來(lái)讓用戶定位和定向虛擬對(duì)象灰羽。示例代碼的VirtualObjectInteraction類管理這些手勢(shì)。

一般來(lái)說(shuō)廉嚼,保持交互的簡(jiǎn)單性怠噪。當(dāng)拖拽虛擬對(duì)象時(shí),示例應(yīng)用程序?qū)?duì)象的移動(dòng)限制為放置在其上的二維平面傍念,類似地,由于虛擬對(duì)象依賴于水平平面口四,旋轉(zhuǎn)手勢(shì)僅繞其垂直軸旋轉(zhuǎn)對(duì)象秦陋,以使對(duì)象保留在平面上。

在交互式虛擬物體的合理接近范圍內(nèi)回應(yīng)手勢(shì)赤嚼。示例代碼的objectInteracting(with:in:)方法實(shí)用手勢(shì)識(shí)別器提供的接觸位置來(lái)執(zhí)行碰撞測(cè)試顺又。該方式通過(guò)虛擬對(duì)象的邊界框進(jìn)行碰撞測(cè)試,使得用戶接觸更可能影響物體蹂空,即使接觸位置不在對(duì)象具有可見(jiàn)內(nèi)容的點(diǎn)上果录。該方法通過(guò)多點(diǎn)觸控手勢(shì)執(zhí)行多次碰撞測(cè)試,使得用戶接觸更可能應(yīng)用預(yù)估對(duì)象:

for index in 0..<gesture.numberOfTouches {
    let touchLocation = gesture.location(ofTouch: index, in: view)
    
    // Look for an object directly under the `touchLocation`.
    if let object = sceneView.virtualObject(at: touchLocation) {
        return object
    }
}
        
// As a last resort look for an object under the center of the touches.
return sceneView.virtualObject(at: gesture.center(in: view))

考慮用戶啟動(dòng)的對(duì)象縮放是否必要弱恒。這個(gè)放置逼真的虛擬物體的AR體驗(yàn)可能會(huì)自然地出現(xiàn)在用戶環(huán)境中返弹,因此對(duì)象的內(nèi)在大小有助于實(shí)現(xiàn)現(xiàn)實(shí)。因此义起,示例應(yīng)用程序不會(huì)添加手勢(shì)或其他UI來(lái)啟用對(duì)象縮放默终。另外椅棺,通過(guò)不啟用縮放手勢(shì),可防止用戶對(duì)于手勢(shì)是調(diào)整對(duì)象大小還是改變對(duì)象距相機(jī)的距離而困惑。(如果選擇在應(yīng)用程序中啟用對(duì)象縮放肴熏,請(qǐng)使用捏合手勢(shì)識(shí)別器。)

警惕潛在的手勢(shì)沖突源哩。示例代碼的ThresholdPanGesture類是一個(gè)UIPanGestureRecognizer子類鸦做,它提供一種延遲手勢(shì)識(shí)別器效果的方法泼诱,直到正在進(jìn)行的手勢(shì)通過(guò)指定的移動(dòng)閥值。示例代碼的touchesMoved(with:)方法使用此類讓用戶在拖拽對(duì)象之間平滑過(guò)渡并在單個(gè)雙指手勢(shì)中旋轉(zhuǎn)對(duì)象:

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
    super.touchesMoved(touches, with: event)
    
    let translationMagnitude = translation(in: view).length
    
    // Adjust the threshold based on the number of touches being used.
    let threshold = ThresholdPanGesture.threshold(forTouchCount: touches.count)
    
    if !isThresholdExceeded && translationMagnitude > threshold {
        isThresholdExceeded = true
        
        // Set the overall translation to zero as the gesture should now begin.
        setTranslation(.zero, in: view)
    }
}

確保虛擬對(duì)象的順利移動(dòng)屉栓。示例代碼的setPosition(_:relativeTo:smoothMovement)方法在導(dǎo)致拖拽對(duì)象觸摸手勢(shì)位置和該對(duì)象的最近位置的歷史記錄之間插值耸袜。該方法通過(guò)根據(jù)距離攝像機(jī)的距離平均最近的位置,可以產(chǎn)生平滑的拖拽運(yùn)動(dòng)域滥,而不會(huì)使拖拽的對(duì)象滯后于用戶的手勢(shì):

if smoothMovement {
    let hitTestResultDistance = simd_length(positionOffsetFromCamera)
    
    // Add the latest position and keep up to 10 recent distances to smooth with.
    recentVirtualObjectDistances.append(hitTestResultDistance)
    recentVirtualObjectDistances = Array(recentVirtualObjectDistances.suffix(10))
    
    let averageDistance = recentVirtualObjectDistances.average!
    let averagedDistancePosition = simd_normalize(positionOffsetFromCamera) * averageDistance
    simdPosition = cameraWorldPosition + averagedDistancePosition
} else {
    simdPosition = cameraWorldPosition + positionOffsetFromCamera
}

探索更有吸引力的交互方式蜈抓。在AR體驗(yàn)中资昧,拖拽手勢(shì)--即將手指移動(dòng)到設(shè)備屏幕上--并不是將虛擬內(nèi)容拖到新位置的唯一自然方式。用戶還可以直觀地嘗試在移動(dòng)設(shè)備的同時(shí)將手指保持在屏幕上格带,在AR場(chǎng)景中有效地拖拽觸摸點(diǎn)叽唱。

示例應(yīng)用程序通過(guò)在拖拽手勢(shì)正在進(jìn)行時(shí)持續(xù)調(diào)用updateObjectToCurrentTrackingPosition()方法來(lái)支持這種手勢(shì),即時(shí)手勢(shì)的觸摸位置沒(méi)有改變棺亭。如果設(shè)備在拖拽期間移動(dòng),則該方法會(huì)計(jì)算與觸摸位置相對(duì)應(yīng)的新世界位置嗽桩,并相應(yīng)地移動(dòng)虛擬對(duì)象碌冶。

進(jìn)入增強(qiáng)現(xiàn)實(shí)

指示正在進(jìn)行的初始化并提示用戶。示例應(yīng)用程序顯示有關(guān)AR會(huì)話狀態(tài)的文本提示以及使用浮動(dòng)文本視圖與AR體驗(yàn)進(jìn)行交互的說(shuō)明扑庞。示例代碼的StatusViewController類管理此視圖罐氨,顯示允許用戶讀取它們之后淡出的臨時(shí)指令或重要的狀態(tài)信息,直到用戶更正為止塔嬉。

處理問(wèn)題

當(dāng)不符合用戶期望時(shí)租悄,允許重置體驗(yàn)。示例應(yīng)用程序具有始終在UI右上角可見(jiàn)的重置按鈕记盒,無(wú)論當(dāng)前狀態(tài)如何允許用戶重新啟動(dòng)AR體驗(yàn)外傅。參閱示例代碼的restartExperience()方法。

僅在兼容的設(shè)備上提供AR功能碾盟。示例應(yīng)用程序需要ARKit的核心功能技竟,需在項(xiàng)目的Info.plist文件中設(shè)置UIRequiredDeviceCapabilities - arkit鍵值對(duì)。部署內(nèi)建項(xiàng)目時(shí)熙尉,此鍵可防止在不支持ARKit的設(shè)備上安裝應(yīng)用程序搓扯。

若app僅將AR作為次要功能,請(qǐng)使用isSupported方法來(lái)確定是否隱藏需要ARKit的功能铅歼。

<br />

創(chuàng)建基于面部的AR體驗(yàn)

使用iPhone X上的TrueDepth攝像頭,放置和動(dòng)畫追蹤用戶臉部的3D內(nèi)容并匹配面部表情厦幅。

下載源碼

概述

此示例應(yīng)用程序提供了一個(gè)簡(jiǎn)單的界面慨飘,可讓你在具有TrueDepth前置攝像頭的設(shè)備上選擇一下四個(gè)增強(qiáng)現(xiàn)實(shí)可視化(參閱iOS設(shè)備兼容性參考)。

  • 單獨(dú)的相機(jī)視圖,沒(méi)有任何AR內(nèi)容
  • ARKit提供的面部網(wǎng)格堤瘤,可以自動(dòng)估計(jì)真實(shí)的定向照明環(huán)境
  • 附加到用戶真實(shí)面孔的虛擬3D內(nèi)容(并被部分遮蔽)
  • 一個(gè)可動(dòng)簡(jiǎn)單的機(jī)器人角色浆熔,其表情與用戶匹配

使用示例應(yīng)用程序的“+”按鈕在這些模式之間切換,如下所示:

SceneKit視圖中啟動(dòng)面部追蹤會(huì)話

與ARKit其他用途一樣慎皱,面部追蹤需要配置和運(yùn)行會(huì)話(ARSession對(duì)象)叶骨,并將視頻圖像與虛擬內(nèi)容一起渲染忽刽。有關(guān)會(huì)話和視圖設(shè)置的更詳細(xì)的說(shuō)明,請(qǐng)參閱關(guān)于增強(qiáng)現(xiàn)實(shí)與ARKit創(chuàng)建第一個(gè)AR體驗(yàn)跪帝。此示例使用SceneKit顯示AR體驗(yàn)伞剑,但也可以使用SpriteKit或使用Metal創(chuàng)建自己的渲染器(參閱ARSKView使用Metal顯示AR體驗(yàn)**)。

面部追蹤與用于配置會(huì)話的類中的ARKit的其他用途不同黎泣。要啟用面部追蹤抒倚,請(qǐng)創(chuàng)建一個(gè)ARFaceTrackingConfiguration實(shí)例,配置其屬性献起,并將其傳遞給與視圖關(guān)聯(lián)的AR會(huì)話的run(_:options:)方法,如下所示:

guard ARFaceTrackingConfiguration.isSupported else { return }
let configuration = ARFaceTrackingConfiguration()
configuration.isLightEstimationEnabled = true
session.run(configuration, options: [.resetTracking, .removeExistingAnchors])

在提供需要面部追蹤AR會(huì)話的用戶功能之前姻政,請(qǐng)檢查ARFaceTrackingConfiguration.isSupported屬性以確定當(dāng)前設(shè)備是否支持ARKit面部追蹤岂嗓。

面部追蹤的位置和方向

當(dāng)面部追蹤處于活動(dòng)狀態(tài)時(shí),ARKit會(huì)自動(dòng)將ARFaceAnchor對(duì)象添加到正在運(yùn)行的AR會(huì)話中食绿,其中包含有關(guān)用戶面部的信息公罕,包括其位置和方向。

注意

ARKit會(huì)檢測(cè)并提供有關(guān)用戶面部的信息铲汪。如果攝像機(jī)圖像中存在多個(gè)面部罐柳,則ARKit會(huì)選擇最大或最清晰可識(shí)別的面部。

在基于SceneKit的AR體驗(yàn)中齿梁,可以在renderer(_:didAdd:for:)方法中添加與面部錨點(diǎn)對(duì)應(yīng)的3D內(nèi)容肮蛹。ARKit為錨點(diǎn)添加了一個(gè)SceneKit節(jié)點(diǎn),并更新了每個(gè)frame上節(jié)點(diǎn)的位置和方向酵幕,因此你添加到該節(jié)點(diǎn)的任何SceneKit內(nèi)容都會(huì)自動(dòng)跟隨用戶面部的位置和方向缓苛。

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    // Hold onto the `faceNode` so that the session does not need to be restarted when switching masks.
    faceNode = node
    serialQueue.async {
        self.setupFaceNodeContent()
    }
}

在此示例中未桥,renderer(_:didAdd:for:)方法將SceneKit內(nèi)容添加到faceNode。例如舌菜,如果更改示例代碼中的showsCoordinateOrigin變量亦镶,則應(yīng)用程序?qū)/y/z軸的可視化添加到該節(jié)點(diǎn)袱瓮,指示面部錨點(diǎn)坐標(biāo)系的起點(diǎn)爱咬。

使用面部幾何體給用戶面部建模

ARKit提供了一個(gè)與用戶面部的大小、形狀燎斩、拓?fù)渑c當(dāng)前面部表情相匹配的粗略3D網(wǎng)格幾何體蜂绎。還提供了ARSCNFaceGeometry類师枣,它是一種在SceneKit中可視化此網(wǎng)格的簡(jiǎn)單方法。

AR體驗(yàn)可以使用此網(wǎng)格放置或繪制看起來(lái)附加到面部的內(nèi)容坛吁。例如拨脉,通過(guò)將半透明紋理應(yīng)用于此幾何體宣增,可以將虛擬紋身或化妝畫到用戶的皮膚上。

要?jiǎng)?chuàng)建SceneKit面部幾何體帖旨,請(qǐng)使用SceneKit視圖用于渲染的Metal設(shè)備初始化一個(gè)ARSCNFaceGeometry對(duì)象:

// This relies on the earlier check of `ARFaceTrackingConfiguration.isSupported`.
let device = sceneView.device!
let maskGeometry = ARSCNFaceGeometry(device: device)!

示例代碼的setupFaceNodeContent方法將包含面部幾何體的節(jié)點(diǎn)添加到場(chǎng)景中灵妨。通過(guò)使該節(jié)點(diǎn)成為由面部錨點(diǎn)提供的節(jié)點(diǎn)的子節(jié)點(diǎn)泌霍,面部模型會(huì)自動(dòng)追蹤用戶面部的位置和方向。

為了使屏幕上的面部模型符合用戶面部的形狀朱转,即使用戶眨眼藤为、說(shuō)話與進(jìn)行各種面部表情,你需要在renderer(_:didUpdate:for:)代理回調(diào)中檢索更新的面部網(wǎng)格缅疟。

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
    guard let faceAnchor = anchor as? ARFaceAnchor else { return }
    
    virtualFaceNode?.update(withFaceAnchor: faceAnchor)
}

然后,通過(guò)將新的面部網(wǎng)格傳遞到update(from:)方法茎杂,更新場(chǎng)景中的ARSCNFaceGeometry對(duì)象:

func update(withFaceAnchor anchor: ARFaceAnchor) {
    let faceGeometry = geometry as! ARSCNFaceGeometry
    faceGeometry.update(from: anchor.geometry)
}

在用戶面部放置3D內(nèi)容

ARKit提供的面部網(wǎng)格的另一個(gè)用途是在場(chǎng)景中創(chuàng)建遮擋幾何體煌往。遮擋幾何體是不會(huì)呈現(xiàn)任何可見(jiàn)內(nèi)容(允許相機(jī)圖像顯示)的3D模型,但會(huì)阻礙相機(jī)對(duì)場(chǎng)景中其他虛擬內(nèi)容的視圖羞海。

這種技術(shù)創(chuàng)造出真實(shí)面部與虛擬對(duì)象交互的錯(cuò)覺(jué)曲管,即使面部是2D攝像機(jī)圖像,虛擬內(nèi)容是渲染的3D對(duì)象奸鬓。例如钓试,如果將遮擋幾何體圖形和虛擬眼鏡放置在用戶的面部上恢恼,則面部可能會(huì)遮擋眼睛的frame

要?jiǎng)?chuàng)建面部的遮擋幾何體漓踢,請(qǐng)先創(chuàng)建一個(gè)ARSCNFaceGeometry對(duì)象漏隐,如上例所示,但是薯酝,不要使用可見(jiàn)的外觀來(lái)配置該對(duì)象的SceneKit材質(zhì)爽柒,而是在渲染期間將材質(zhì)設(shè)置為渲染深度而不是顏色:

geometry.firstMaterial!.colorBufferWriteMask = []
occlusionNode = SCNNode(geometry: geometry)
occlusionNode.renderingOrder = -1

由于材質(zhì)呈現(xiàn)深度浩村,所以SceneKit渲染的其他對(duì)象正確地出現(xiàn)在它的前面或后面。但是由于材質(zhì)不會(huì)呈現(xiàn)顏色酿矢,相機(jī)圖像會(huì)出現(xiàn)在其位置上。示例應(yīng)用程序?qū)⒋思夹g(shù)與位于用戶眼睛前方的SceneKit對(duì)象相結(jié)合瘫筐,創(chuàng)建一個(gè)效果策肝,其中對(duì)象被用戶的鼻子實(shí)際遮擋。

用混合形狀動(dòng)畫化一個(gè)角色

除了上述示例中所示的面部網(wǎng)格之外拙毫,ARKit還提供了一個(gè)更為抽象的用戶面部表情模型棺禾,其形式為blendShapes字典∪鼻埃可以使用該字典中的命名系數(shù)值來(lái)控制自己的2D或3D資源的動(dòng)畫參數(shù)悬襟,創(chuàng)建遵循用戶真實(shí)面部動(dòng)作和表情的角色(如頭像或木偶)。

作為混合形狀動(dòng)畫的基本演示肆良,此示例包含使用ScenceKit原始形狀創(chuàng)建的機(jī)器人角色頭部的簡(jiǎn)單模型逸绎。(參閱源代碼中的robotHead.scn文件棺牧。)

要獲取用戶當(dāng)前的面部表情朗儒,請(qǐng)從renderer(_:didUpdate:for:)代理回調(diào)中讀取面部錨點(diǎn)的blendShapes字典:

func update(withFaceAnchor faceAnchor: ARFaceAnchor) {
    blendShapes = faceAnchor.blendShapes
}

然后,檢查該字典中的鍵值對(duì)以計(jì)算模型的動(dòng)畫參數(shù)乏悄。有52個(gè)獨(dú)特的ARFaceAnchor.BlendShapeLocation系數(shù)恳不。app可以使用盡可能少的或很多的必要條件來(lái)創(chuàng)建想要的藝術(shù)效果。在此示例中规求,RobotHead類執(zhí)行此計(jì)算,將eyeBlinkLefteyeBlinkRight參數(shù)映射到機(jī)器人眼睛的比例因子的一個(gè)軸線瓦戚,并將jawOpen參數(shù)映射到機(jī)器人頜骨的位置丛塌。

var blendShapes: [ARFaceAnchor.BlendShapeLocation: Any] = [:] {
    didSet {
        guard let eyeBlinkLeft = blendShapes[.eyeBlinkLeft] as? Float,
            let eyeBlinkRight = blendShapes[.eyeBlinkRight] as? Float,
            let jawOpen = blendShapes[.jawOpen] as? Float
            else { return }
        eyeLeftNode.scale.z = 1 - eyeBlinkLeft
        eyeRightNode.scale.z = 1 - eyeBlinkRight
        jawNode.position.y = originalJawY - jawHeight * jawOpen
    }
}

<br />

使用Metal顯示AR體驗(yàn)

通過(guò)渲染相機(jī)圖像和使用位置追蹤信息顯示疊加內(nèi)容來(lái)構(gòu)建自定義AR視圖姨伤。

概述

ARKit包括用于使用SceneKitSpriteKit輕松現(xiàn)實(shí)AR體驗(yàn)的視圖類。但是当编,如果建立自己的渲染引擎(或集成第三方引擎)徒溪,則ARKit還提供必要的支持以現(xiàn)實(shí)具有自定義視圖的AR體驗(yàn)。

在任何AR體驗(yàn)中鲤桥,第一步是配置一個(gè)ARSession對(duì)象來(lái)管理攝像頭捕獲和運(yùn)動(dòng)處理渠概。會(huì)話定義和維護(hù)設(shè)備所在的真實(shí)世界與你為AR內(nèi)容建模的虛擬空間之間的對(duì)應(yīng)關(guān)系播揪。要在自定義視圖中顯示AR體驗(yàn),需要:

  1. 從會(huì)話中檢索視頻幀和追蹤信息猪狈。
  2. 將這些frame圖像作為視圖的背景渲染雇庙。
  3. 使用追蹤信息在相機(jī)圖像上方定位和繪制AR內(nèi)容。

注意

下面介紹了Xcode項(xiàng)目模版中的代碼寒跳。有關(guān)完整的示例代碼竹椒,請(qǐng)使用Augmented Realitytemplate模版創(chuàng)建一個(gè)新的iOS應(yīng)用程序,然后從Content Technology彈出菜單中選擇Metal康愤。

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

創(chuàng)建和維護(hù)自己的ARSession實(shí)例,并使用適合你想要支持的AR體驗(yàn)類型的會(huì)話配置運(yùn)行它择膝。會(huì)話從相機(jī)捕獲視頻检激,在建模的3D空間中追蹤設(shè)備的位置和方向愛(ài)哪個(gè),并提供ARFrame對(duì)象齿穗。每個(gè)這樣的對(duì)象從捕獲幀的時(shí)刻都包含單獨(dú)的視頻幀圖像和位置追蹤信息饺律。

有兩種方法來(lái)訪問(wèn)由AR會(huì)話產(chǎn)生的ARFrame對(duì)象复濒,這取決于你的應(yīng)用是否偏好pullpush設(shè)計(jì)模式。

如果你偏重控制幀定時(shí)(pull設(shè)計(jì)模式)巧颈,請(qǐng)使用會(huì)話的currentFrame屬性來(lái)獲取當(dāng)前幀圖像和每次重繪視圖內(nèi)容時(shí)的追蹤信息砸泛。ARKit Xcode模版使用這種方法:

// in Renderer class, called from MTKViewDelegate.draw(in:) via Renderer.update()
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ì)話將為其捕獲的每個(gè)視頻幀調(diào)用一次(默認(rèn)為60幀/秒)妒蛇。

獲得一個(gè)frame后绣夺,你需要繪制相機(jī)圖像,并更新和渲染你的AR體驗(yàn)中包含的任何覆蓋內(nèi)容奋蔚。

繪制相機(jī)圖像

每個(gè)ARFrame對(duì)象的capturedImage屬性都包含從設(shè)備攝像頭捕獲到的像素緩沖區(qū)。要將此圖像繪制為自定義視圖的背景坤按,需要從圖像內(nèi)容創(chuàng)建紋理馒过,并提交使用這些紋理的GPU渲染命令。

像素緩沖區(qū)的內(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紋理--每個(gè)用于緩沖區(qū)的亮度(Y)和色度(CbCr)平面:

func updateCapturedImageTextures(frame: ARFrame) {
    // Create two textures (Y and CbCr) from the provided frame's captured image
    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)匠题,使用顏色轉(zhuǎn)換矩陣執(zhí)行YCbCrRGB轉(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.0000f, +1.0000f, +1.0000f, +0.0000f),
        float4(+0.0000f, -0.3441f, +1.7720f, +0.0000f),
        float4(+1.4020f, -0.7141f, +0.0000f, +0.0000f),
        float4(-0.7010f, +0.5291f, -0.8860f, +1.0000f)
    );
    
    // 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;
}

注意

使用displayTransform(for:viewportSize:)方法確保相機(jī)圖像是否覆蓋整個(gè)視圖韭山。例如使用這個(gè)方法,以及完整的Metal管道設(shè)置代碼梦裂,請(qǐng)參閱完整的Xcode模版盖淡。

追蹤并渲染覆蓋內(nèi)容

AR體驗(yàn)通常側(cè)重于渲染3D覆蓋內(nèi)容,使內(nèi)容看起來(lái)是從相機(jī)圖像中看到的真實(shí)世界的一部分冗恨。為了實(shí)現(xiàn)這種幻象味赃,使用ARAnchor類來(lái)模擬自己的3D內(nèi)容相對(duì)于現(xiàn)實(shí)世界空間的位置和方向。錨點(diǎn)對(duì)象提供了可在渲染過(guò)程中引用的轉(zhuǎn)換傲武。

例如,Xcode模版在用戶點(diǎn)擊屏幕時(shí)創(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è)ARAnchor對(duì)象的transform屬性來(lái)放置可視化內(nèi)容诗茎。Xcode模版使用在其handleTap方法添加到會(huì)話中的每個(gè)錨點(diǎn)來(lái)定位簡(jiǎn)單的立體網(wǎng)格:

func updateAnchors(frame: ARFrame) {
    // Update the anchor uniform buffer with transforms of the current frame's anchors
    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]
        
        // Flip Z axis to convert geometry from right handed to left handed
        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
    }
}

注意

在更復(fù)雜的AR體驗(yàn)中献汗,可以使用碰撞測(cè)試或平面檢測(cè)來(lái)查找真實(shí)世界曲面的位置罢吃。有關(guān)詳細(xì)信息,請(qǐng)參閱planeDetection屬性和hitTest(_:types:)方法尿招。在這兩種情況下就谜,ARKit都會(huì)提供ARAnchor對(duì)象的結(jié)果,因此你仍然可以使用錨點(diǎn)轉(zhuǎn)換來(lái)放置視覺(jué)內(nèi)容缆瓣。

現(xiàn)實(shí)光照渲染

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

// in Renderer.updateSharedUniforms(frame:):
// Set up lighting for the scene using the ambient intensity if provided
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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末虹统,一起剝皮案震驚了整個(gè)濱河市弓坞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌车荔,老刑警劉巖渡冻,帶你破解...
    沈念sama閱讀 218,755評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異忧便,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)珠增,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門超歌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人切平,你說(shuō)我怎么就攤上這事握础》” “怎么了悴品?”我有些...
    開(kāi)封第一講書人閱讀 165,138評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我苔严,道長(zhǎng)定枷,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,791評(píng)論 1 295
  • 正文 為了忘掉前任届氢,我火速辦了婚禮欠窒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘退子。我一直安慰自己岖妄,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布寂祥。 她就那樣靜靜地躺著荐虐,像睡著了一般。 火紅的嫁衣襯著肌膚如雪丸凭。 梳的紋絲不亂的頭發(fā)上福扬,一...
    開(kāi)封第一講書人閱讀 51,631評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音惜犀,去河邊找鬼铛碑。 笑死,一個(gè)胖子當(dāng)著我的面吹牛虽界,可吹牛的內(nèi)容都是我干的汽烦。 我是一名探鬼主播,決...
    沈念sama閱讀 40,362評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼莉御,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼刹缝!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起颈将,我...
    開(kāi)封第一講書人閱讀 39,264評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤梢夯,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后晴圾,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體颂砸,經(jīng)...
    沈念sama閱讀 45,724評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年死姚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了人乓。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,040評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡都毒,死狀恐怖色罚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情账劲,我是刑警寧澤戳护,帶...
    沈念sama閱讀 35,742評(píng)論 5 346
  • 正文 年R本政府宣布金抡,位于F島的核電站,受9級(jí)特大地震影響腌且,放射性物質(zhì)發(fā)生泄漏梗肝。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評(píng)論 3 330
  • 文/蒙蒙 一铺董、第九天 我趴在偏房一處隱蔽的房頂上張望巫击。 院中可真熱鬧,春花似錦精续、人聲如沸坝锰。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,944評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)什黑。三九已至,卻和暖如春堪夭,著一層夾襖步出監(jiān)牢的瞬間愕把,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,060評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工森爽, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留恨豁,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,247評(píng)論 3 371
  • 正文 我出身青樓爬迟,卻偏偏與公主長(zhǎng)得像橘蜜,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子付呕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評(píng)論 2 355

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