在相機信息流上控制你的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
。)