ARKit框架详细解析(六)—— 用Metal展示AR体验

版本记录

版本号 时间
V1.0 2017.09.28

前言

苹果最近新出的一个API就是ARKit,是在2017年6月6日,苹果发布iOS11系统所新增框架,它能够帮助我们以最简单快捷的方式实现AR技术功能。接下来几篇我们就详细的对ARKit框架进行详细的解析。感兴趣的可以看上面几篇。
1. ARKit框架详细解析(一)—— 基本概览
2. ARKit框架详细解析(二)—— 关于增强现实和ARKit
3. ARKit框架详细解析(三)—— 开启你的第一个AR体验之旅
4. ARKit框架详细解析(四)—— 处理增强现实中的3D交互和UI控件
5. ARKit框架详细解析(五)—— 创建基于面部的AR体验

概览

通过渲染相机图像和使用位置跟踪信息来显示叠加内容来构建自定义AR视图。

ARKit包括用于使用SceneKitSpriteKit轻松显示AR体验的视图类。 但是,如果您建立您自己的渲染引擎(或与第三方引擎集成),则ARKit还提供必要的支持以显示具有自定义视图的AR体验。

ARKit框架详细解析(六)—— 用Metal展示AR体验_第1张图片

在任何AR体验中,第一步是配置一个ARSession对象来管理摄像头捕捉和运动处理。 会话定义和维护设备所在的真实世界空间与您为AR内容建模的虚拟空间之间的对应关系。 要在自定义视图中显示您的AR体验,您需要:

  • 从会话中检索视频帧和跟踪信息。
  • 将这些帧图像作为视图的背景渲染。
  • 使用跟踪信息在相机图像上方定位和绘制AR内容。

注意:本文介绍了Xcode项目模板中的代码。 有关完整的示例代码,请使用“增强现实”模板创建一个新的iOS应用程序,然后从“内容技术”弹出菜单中选择“Metal”。


Get Video Frames and Tracking Data from the Session - 从会话获取视频帧和跟踪数据

创建和维护您自己的ARSession实例,并使用适合您想要支持的AR体验类型的会话配置运行它。 会话从相机捕获视频,在建模的3D空间中跟踪设备的位置和方向,并提供 ARFrame对象。 每个这样的对象从捕获帧的时刻都包含单独的视频帧图像和位置跟踪信息。

有两种方法来访问由AR会话产生的ARFrame对象,这取决于您的应用是否有利于拉取或推送设计模式。

如果您喜欢控制帧定时(拉线设计模式),请使用会话的 currentFrame属性来获取当前帧图像和每次重绘视图内容时的跟踪信息。 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)
    }
}

或者,如果您的应用程序设计有利于推送模式,请执行会话:session:didUpdateFrame: 代理方法,并且会话将为每个捕获的视频帧调用一次(默认情况下为每秒60帧)。

获得一帧后,您需要绘制相机图像,并更新和渲染您的AR体验包含的任何重叠内容。


Draw the Camera Image - 绘制相机图片

每个ARFrame对象的capturedImage属性都包含从设备摄像头捕获的像素缓冲区。 要将此图像绘制为自定义视图的背景,您需要从图像内容创建纹理,并提交使用这些纹理的GPU渲染命令。

像素缓冲器的内容被编码为双平面YCbCr(也称为YUV)数据格式; 要渲染图像,您需要将此像素数据转换为可绘制的RGB格式。 对于使用Metal渲染,您可以在GPU着色器代码中最有效地执行此转换。 使用CVMetalTextureCache API从像素缓冲区创建两个金属纹理,每个用于缓冲区的亮度(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
}

接下来,使用使用颜色变换矩阵执行YCbCrRGB转换的片段函数来编码绘制这两个纹理的渲染命令:

fragment float4 capturedImageFragmentShader(ImageColorInOut in [[stage_in]],
                                            texture2d capturedImageTextureY [[ texture(kTextureIndexY) ]],
                                            texture2d 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;
}

注意:使用 displayTransformForOrientation:viewportSize:方法确保相机图像覆盖整个视图。 例如使用这种方法,以及完整的Metal流水线设置代码,请参阅完整的Xcode模板。 (使用增强现实模板创建新的iOS应用程序,然后从内容技术弹出菜单中选择Metal。)


Track and Render Overlay Content - 跟踪和渲染覆盖内容

AR体验通常侧重于渲染3D重叠内容,使内容看起来是相机图像中看到的真实世界的一部分。 为了实现这种幻觉,使用ARAnchor类来模拟您自己的3D内容相对于现实世界空间的位置和方向。 锚点提供转换,您可以在渲染过程中引用它们。

例如,Xcode模板在用户点击屏幕时创建位于设备前面大约20厘米处的锚点:

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)
    }
}

在渲染引擎中,使用每个ARAnchor 对象的transform属性来放置可视化内容。 Xcode模板使用其handleTap方法中添加到会话中的每个锚点来定位简单的多维数据集网格:

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..

注意:在更复杂的AR体验中,您可以使用命中测试或平面检测来查找真实世界曲面的位置。 有关详细信息,请参阅 planeDetection属性和 hitTest:types:方法。 在这两种情况下,ARKit都会将结果提供为ARAnchor对象,因此您仍然可以使用锚点转换来放置视觉内容。


Render with Realistic Lighting - 渲染与现实照明

当您配置用于在场景中绘制3D内容的着色器时,请使用每个ARFrame对象中的估计照明信息来产生更逼真的阴影:

// 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

注意:有关本示例的完整的Metal设置和渲染命令集,请参阅完整的Xcode模板。 (使用增强现实模板创建新的iOS应用程序,然后从内容技术弹出菜单中选择Metal。)

后记

未完,待续~~~~

ARKit框架详细解析(六)—— 用Metal展示AR体验_第2张图片

你可能感兴趣的:(ARKit框架详细解析(六)—— 用Metal展示AR体验)