通过渲染摄像头图像和使用位置追踪信息来显示重叠内容,构建自定增强现实视图。
概览
ARKit 包含一些视图类,能够轻松通过 SceneKit 或 SpriteKit 呈现增强现实体验。如果你想要构建自己的渲染引擎 (或与第三方引擎整合),ARKit 另有提供所有必要的支持,让你能够通过自定视图呈现增强现实体验。

在任何增强现实体验中,第一步都是配置 ARSession (英文) 对象来管理摄像头捕捉和运动处理。会话用于定义和维护设备所在的现实世界和增强现实内容建模的虚拟空间之间的对应关系。要在自动视图中呈现增强现实体验,需要做到以下几点:
从会话中检索视频帧和追踪信息。
渲染这些帧图像作为视图背景。
使用追踪信息确定增强现实内容在摄像头图像上的位置并绘制这些内容。
注释
本文中使用的是 Xcode 项目模板中的代码。如需完整的示例代码,请使用增强现实模板创建新的 iOS App,并从“Content Technology”(内容技术) 弹出式菜单中选取“Metal”。
从会话中获取视频帧和追踪数据
创建和维护你自己的 ARSession (英文) 实例,并根据你想要支持的增强现实体验类型,使用合适的会话配置来运行这个实例。会话会从摄像头捕捉视频,追踪设备在 3D 模型空间中的位置和方向,并提供 ARFrame (英文) 对象。这类对象每个都包含对应帧捕获时刻的单一视频帧图像和位置追踪信息。
你可以通过两种方法访问增强现实会话产生的 ARFrame (英文) 对象,具体使用哪种方法取决于你的 App 倾向于使用拉取还是推送设计模式。
如果你想要控制帧定时 (拉取设计模式),请使用会话的 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)
}
}
或者,如果你的 App 设计倾向于使用推送模式,请实施 session(_:didUpdate:) (英文) 委托方法,会话会针对捕捉的每个视频帧调用一次这个方法 (默认为 60 fps)。
在获取帧时,你需要绘制摄像头图像,以及更新并渲染增强现实体验包含的任何重叠内容。
绘制摄像头图像
每个 ARFrame (英文) 对象的 capturedImage (英文) 属性都包含从设备摄像头捕捉的像素缓冲。要绘制这个图像作为自定视图的背景,你需要从图像内容创建纹理,并提交使用这些纹理的 GPU 渲染命令。
像素缓冲的内容以双平面 YCbCr (亦称 YUV) 数据格式编码;要渲染图像,你需要将这个像素数据转换为可绘制 RGB 格式。使用 Metal 进行渲染时,完成这个转换的最高效方法是使用 GPU 着色器代码。先使用 CVMetalTextureCache (英文) API 从像素缓冲创建两个 Metal 纹理——缓冲的亮度 (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
}
接着,对渲染命令编码,使用分段函数通过颜色变换矩阵完成从 YCbCr 到 RGB 的转换,从而绘制这两个纹理:
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:) (英文) 方法来确保摄像头图像覆盖整个视图。如需这种方法的使用示例,以及完整的 Metal 管线设置代码,请参阅完整的 Xcode 模板。(使用增强现实模板创建新的 iOS App,并从“Content Technology”(内容技术) 弹出式菜单中选取“Metal”。)
追踪和渲染重叠内容
增强现实体验通常侧重于渲染 3D 重叠内容,让这些内容看似真实存在于摄像头图像里的现实世界中一样。要实现这一视觉效果,可使用 ARAnchor (英文) 类,相对现实世界为自身 3D 内容的位置和方向建模。锚点提供变换属性,在渲染的时候可供参考。
例如,每当用户轻点屏幕时,Xcode 模板都会在设备前方约 20 cm 处创建一个锚点:
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..<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
}
}
注释
在更复杂的增强现实体验中,可以使用命中测试或平面检测来查找现实表面的位置。有关详细信息,请参阅 planeDetection (英文) 属性和 hitTest(_:types:) (英文) 方法。在这两种情况下,ARKit 都以 ARAnchor (英文) 对象形式提供结果,这样你仍可以使用锚点变换来放置视觉内容。
使用逼真灯光进行渲染
配置着色器在场景中绘制 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 App,并从“Content Technology”(内容技术) 弹出式菜单中选取“Metal”。)