在相机信息流上控制你的app
虚拟内容渲染。
一、概述
ARKit
包含视图类,可轻松显示SceneKit
或SpriteKit
的AR
体验。 但是,如果改为使用Metal
来构建自己的渲染引擎,则ARKit
也将提供所有必要的支持,以使用自定义视图显示AR
体验。
在任何AR
体验中,第一步都是配置一个ARSession
对象以管理相机捕获和运动处理。 session
定义并维持设备所居住的真实空间与您为AR
内容建模的虚拟空间之间的对应关系。 要在自定义视图中显示您的AR
体验,您需要:
- 从
session
中检索视频帧和跟踪信息。 - 渲染这些帧图像作为您的视图背景。
- 使用跟踪信息将
AR
内容定位并绘制在相机图像上。
注意
本文介绍了在Xcode
项目模板中找到的代码。 有关完整的示例代码,请使用Augmented Reality
模板创建一个新的iOS
应用程序,然后从Content Technology
弹出菜单中选择Metal
。
二、从Session中获取视频帧和跟踪数据
创建并维护您自己的ARSession
实例,并使用适合您要支持的AR
体验的session
配置来运行它。 session
从相机捕获视频,在建模的3D
空间中跟踪设备的位置和方向,并提供ARFrame
对象。 从捕获帧的那一刻起,每个这样的对象都包含一个单独的视频帧图像和位置跟踪信息。
有两种方法可以访问AR session
生成的ARFrame
对象,具体取决于您的应用是支持拉还是推设计模式。
如果您希望控制帧计时(拉动设计模式),则每次重绘视图内容时,都可以使用session
的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(_:didUpdate :)
委托方法,session
将针对其捕获的每个视频帧对其调用一次(默认为每秒60
帧)。
获取一帧后,您需要绘制相机图像,并更新和渲染您的AR
体验中包括的任何叠加内容。
三、绘制相机图像
每个ARFrame
对象的captureImage
属性都包含一个从设备相机捕获的像素缓冲区。 要将图像绘制为自定义视图的背景,您需要根据图像内容创建纹理,并提交使用这些纹理的GPU
渲染命令。
像素缓冲区的内容以双平面YCbCr
(也称为Y'UV
)数据格式进行编码; 要渲染图像,您需要将该像素数据转换为可绘制的RGB
格式。 对于使用Metal
渲染,您可以在GPU
着色器代码中最高效地执行此转换。 使用CVMetalTextureCache API
从像素缓冲区创建两个Metal
纹理———缓冲区的亮度luma(Y)
和色度chroma(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
应用程序,然后从Content Technology
弹出菜单中选择Metal
。)
四、跟踪和渲染叠加内容
AR
体验通常专注于渲染3D
叠加内容,因此该内容似乎是在相机图像中看到的真实世界的一部分。 要实现这种错觉,请使用ARAnchor
类对您自己的3D
内容相对于真实空间的位置和方向进行建模。 Anchors
提供您可以在渲染期间参考的转换。
例如,每当用户点击屏幕时,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
方法中添加到session
的每个锚点来定位简单的多维数据集网格:
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
}
}
注意
在更复杂的AR
体验中,您可以使用命中测试或平面检测来找到真实表面的位置。 有关详细信息,请参见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
应用程序,然后从Content Technology
弹出菜单中选择Metal
。)
网友评论