對物理環(huán)境應用虛擬霧次慢。
概述
第二代 11 英寸 iPad Pro 和第四代 12.9 英寸 iPad Pro 等設備可以使用激光雷達掃描儀計算現(xiàn)實世界中物體與用戶的距離巫员。 在 iOS 14 的世界追蹤體驗中始赎,ARKit 提供了一個緩沖區(qū)蓄髓,用于描述對象與設備的距離(以米為單位)贡未。
此示例應用程序使用深度緩沖區(qū)實時創(chuàng)建虛擬霧效果汹族。 為了繪制其圖形,示例應用程序使用了一個小型 Metal 渲染器托享。 ARKit 為攝像機源中的對象提供精確的深度值骚烧,因此示例應用程序使用金屬性能著色器 (MPS) 應用高斯模糊來柔化霧效果。 在將相機圖像繪制到屏幕上時闰围,渲染器會檢查每個像素的深度紋理赃绊,并根據(jù)該像素與設備的距離覆蓋霧色。 有關使用金屬采樣紋理和繪圖的更多信息羡榴,請參閱創(chuàng)建和采樣紋理碧查。
啟用場景深度并運行會話
為了避免運行不支持的配置,示例應用首先檢查設備是否支持場景深度炕矮。
if #available(iOS 14.0, *) {
if (!ARWorldTrackingConfiguration.supportsFrameSemantics(.sceneDepth) || !ARWorldTrackingConfiguration.supportsFrameSemantics(.smoothedSceneDepth)) {
print("不支持")
} else {
print("支持")
loadWorld()
}
} else {
// Fallback on earlier versions
}
如果運行應用程序的設備不支持場景深度么夫,示例項目將停止。 或者肤视,應用程序可以向用戶顯示錯誤消息并在沒有場景深度的情況下繼續(xù)體驗档痪。
如果設備支持場景深度,示例應用程序會創(chuàng)建一個世界跟蹤配置并啟用 ARFrameSemantics 屬性上的 ARFrameSemanticSmoothedSceneDepth 選項邢滑。
configuration.frameSemantics = .smoothedSceneDepth
然后腐螟,示例項目通過運行會話開始 AR 體驗。
session.run(configuration)
訪問場景的深度
ARKit 在當前幀的 ARFrameSemanticSceneDepth 或 ARFrameSemanticSmoothedSceneDepth 屬性上提供深度緩沖區(qū) (depthMap) 作為 CVPixelBuffer困后,具體取決于啟用的幀語義乐纸。 此示例應用默認可視化 ARFrameSemanticSmoothedSceneDepth。 ARFrameSemanticSceneDepth 中的原始深度值可以創(chuàng)建閃爍效果的印象摇予,但是平均幀間深度差異的過程可以將視覺平滑為更逼真的霧效果汽绢。 出于調(diào)試目的,該示例允許通過屏幕切換在 ARFrameSemanticSmoothedSceneDepth 和 ARFrameSemanticSceneDepth 之間切換侧戴。
guard let sceneDepth = frame.smoothedSceneDepth ?? frame.sceneDepth else {
print("Failed to acquire scene depth.")
return
}
var pixelBuffer: CVPixelBuffer!
pixelBuffer = sceneDepth.depthMap
深度緩沖區(qū)中的每個像素都映射到可見場景的一個區(qū)域宁昭,該區(qū)域定義了該區(qū)域與設備的距離(以米為單位)跌宛。 因為示例項目使用 Metal 繪制到屏幕上,所以它將像素緩沖區(qū)轉換為 Metal 紋理以將深度數(shù)據(jù)傳輸?shù)?GPU 進行渲染积仗。
var texturePixelFormat: MTLPixelFormat!
setMTLPixelFormat(&texturePixelFormat, basedOn: pixelBuffer)
depthTexture = createTexture(fromPixelBuffer: pixelBuffer, pixelFormat: texturePixelFormat, planeIndex: 0)
為了設置深度紋理的金屬像素格式疆拘,示例項目使用 depthMap 調(diào)用 CVPixelBufferGetPixelFormatType 并根據(jù)結果選擇適當?shù)挠成洹?/p>
fileprivate func setMTLPixelFormat(_ texturePixelFormat: inout MTLPixelFormat?, basedOn pixelBuffer: CVPixelBuffer!) {
if CVPixelBufferGetPixelFormatType(pixelBuffer) == kCVPixelFormatType_DepthFloat32 {
texturePixelFormat = .r32Float
} else if CVPixelBufferGetPixelFormatType(pixelBuffer) == kCVPixelFormatType_OneComponent8 {
texturePixelFormat = .r8Uint
} else {
fatalError("Unsupported ARDepthData pixel-buffer format.")
}
}
對深度緩沖區(qū)應用模糊
作為使用 Metal 渲染圖形的一個好處,這個應用程序可以使用 MPS 的顯示便利寂曹。 示例項目使用 MPS 高斯模糊濾鏡制作逼真的霧哎迄。 實例化過濾器時,示例項目通過 sigma 5 來指定 5 像素半徑模糊隆圆。
blurFilter = MPSImageGaussianBlur(device: device, sigma: 5)
為了以精度為代價獲得性能漱挚,應用程序可以將 MPSKernelOptionsAllowReducedPrecision 添加到模糊過濾器的選項中,從而通過使用 half 而不是 float 來減少計算時間匾灶。
MPS 需要輸入和輸出圖像來定義過濾操作的源和目標像素數(shù)據(jù)棱烂。
let inputImage = MPSImage(texture: depthTexture, featureChannels: 1)
let outputImage = MPSImage(texture: filteredDepthTexture, featureChannels: 1)
示例應用程序將輸入和輸出圖像傳遞給模糊的編碼函數(shù)租漂,該函數(shù)將模糊安排在 GPU 上發(fā)生阶女。
blur.encode(commandBuffer: commandBuffer, sourceImage: inputImage, destinationImage: outputImage)
In-place MPS 操作可以節(jié)省時間、內(nèi)存和功耗哩治。 由于 In-place MPS 需要為不支持它的設備提供后備代碼秃踩,因此此示例項目不使用它。 有關就地操作的更多信息业筏,請參閱圖像過濾器憔杨。
可視化模糊深度以創(chuàng)建霧
Metal 通過向 GPU 提供一個繪制應用程序圖形的片段著色器來進行渲染。 由于示例項目渲染相機圖像蒜胖,它通過調(diào)用 setFragmentTexture 為片段著色器打包相機圖像消别。
enderEncoder.setFragmentTexture(CVMetalTextureGetTexture(cameraImageY), index: 0)
renderEncoder.setFragmentTexture(CVMetalTextureGetTexture(cameraImageCbCr), index: 1)
接下來,示例應用程序打包過濾后的深度紋理台谢。
renderEncoder.setFragmentTexture(filteredDepthTexture, index: 2)
示例項目的 GPU 端代碼按索引參數(shù)的順序字段紋理參數(shù)寻狂。 例如,片段著色器將上面索引為 0 的紋理字段作為包含后綴紋理(0)的參數(shù)朋沮,如下例所示蛇券。
fragment half4 fogFragmentShader(FogColorInOut in [[ stage_in ]],
texture2d<float, access::sample> cameraImageTextureY [[ texture(0) ]],
texture2d<float, access::sample> cameraImageTextureCbCr [[ texture(1) ]],
depth2d<float, access::sample> arDepthTexture [[ texture(2) ]],
為了輸出渲染,Metal 為它繪制到目標的每個像素調(diào)用一次片段著色器樊拓。 示例項目的片段著色器首先讀取相機圖像中當前像素的 RGB 值纠亚。 對象“s”是一個采樣器,它使著色器能夠檢查特定位置的紋理筋夏。 in.texCoordCamera 中的值是指該像素在相機圖像中的相對位置蒂胞。
constexpr sampler s(address::clamp_to_edge, filter::linear);
// 采樣此像素的相機圖像顏色。
float4 rgb = ycbcrToRGBTransform(
cameraImageTextureY.sample(s, in.texCoordCamera),
cameraImageTextureCbCr.sample(s, in.texCoordCamera)
);
half4 cameraColor = half4(rgb);
通過在 in.texCoordCamera 處對深度紋理進行采樣条篷,著色器在與相機圖像相同的相對位置查詢深度骗随,并獲取當前像素與設備的距離(以米為單位)岳瞭。
float depth = arDepthTexture.sample(s, in.texCoordCamera);
為了確定覆蓋該像素的霧量,示例應用程序使用當前像素的距離除以霧效果使場景完全飽和的距離來計算分數(shù)蚊锹。
// 確定此片段的霧百分比瞳筏。
float fogPercentage = depth / fogMax;
混合功能根據(jù)百分比混合兩種顏色。 示例項目傳入 RGB 值牡昆、霧顏色和霧百分比來為當前像素創(chuàng)建適量的霧姚炕。
half4 foggedColor = mix(cameraColor, fogColor, fogPercentage);
在 Metal 為每個像素調(diào)用片段著色器之后,視圖將物理環(huán)境的最終霧化圖像呈現(xiàn)到屏幕上丢烘。
可視化置信數(shù)據(jù)
ARKit 在 ARDepthData 中提供了 confidenceMap 屬性來測量相應深度數(shù)據(jù)(depthMap)的準確度柱宦。 盡管此示例項目沒有將深度置信度納入其霧效果,但如果應用程序的算法需要播瞳,置信度數(shù)據(jù)可以過濾掉精度較低的深度值掸刊。
為了提供深度置信度,此示例應用程序使用 Shaders.metal 文件中的 confidenceDebugVisualizationEnabled 在運行時可視化置信度數(shù)據(jù)赢乓。
// 設置為 `true` 以可視化置信度忧侧。
bool confidenceDebugVisualizationEnabled = false;
當渲染器訪問當前幀的場景深度時,示例項目會創(chuàng)建 confidenceMap 的 Metal 紋理以將其繪制到 GPU 上牌芋。
pixelBuffer = sceneDepth.confidenceMap
setMTLPixelFormat(&texturePixelFormat, basedOn: pixelBuffer)
confidenceTexture = createTexture(fromPixelBuffer: pixelBuffer, pixelFormat: texturePixelFormat, planeIndex: 0)
在渲染器安排其繪制時蚓炬,示例項目通過調(diào)用 setFragmentTexture 為 GPU 打包置信度紋理。
renderEncoder.setFragmentTexture(CVMetalTextureGetTexture(confidenceTexture), index: 3)
GPU 端代碼將置信度數(shù)據(jù)字段作為片段著色器的第三個紋理參數(shù)躺屁。
texture2d<uint> arDepthConfidence [[ texture(3) ]])
為了訪問當前像素深度的置信度值肯夏,片段著色器在 in.texCoordCamera 處對置信度紋理進行采樣。 此紋理中的每個置信度值都是其在 ARConfidenceLevel 枚舉中的對應情況的 uint 等價物犀暑。
uint confidence = arDepthConfidence.sample(s, in.texCoordCamera).x;
基于當前像素的置信度值驯击,片段著色器創(chuàng)建一個歸一化的置信度百分比來覆蓋。
float confidencePercentage = (float)confidence / (float)maxConfidence;
示例項目調(diào)用 mix 函數(shù)耐亏,根據(jù)置信百分比將置信顏色混合到處理后的像素中徊都。
return mix(confidenceColor, foggedColor, confidencePercentage);
在 Metal 為每個像素調(diào)用片段著色器之后,視圖會顯示增強了置信度可視化的相機圖像苹熏。
此示例使用紅色來識別深度置信度低于 ARConfidenceLevelHigh 的場景部分碟贾。 在歸一化百分比為 0 的低置信深度值下,可視化呈現(xiàn)純紅色 (confidenceColor)轨域。 對于值為 1 的高置信深度值袱耽,mix 調(diào)用返回未經(jīng)過濾的霧化相機圖像顏色 (foggedColor)。 在場景的中等置信度區(qū)域干发,mix 調(diào)用返回兩種顏色的混合朱巨,將微紅色調(diào)應用到霧化的相機圖像。
代碼:
Shaders.metal
// 示例應用程序的著色器枉长。
#include <metal_stdlib>
#include <simd/simd.h>
// 包括在此 Metal 著色器代碼和執(zhí)行 Metal API 命令的 C 代碼之間共享的標頭冀续。
#import "ShaderTypes.h"
using namespace metal;
typedef struct {
float2 position [[attribute(kVertexAttributePosition)]];
float2 texCoord [[attribute(kVertexAttributeTexcoord)]];
} ImageVertex;
typedef struct {
float4 position [[position]];
float2 texCoord;
} ImageColorInOut;
// 從 YCbCr 轉換為 rgb琼讽。
float4 ycbcrToRGBTransform(float4 y, float4 CbCr) {
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)
);
float4 ycbcr = float4(y.r, CbCr.rg, 1.0);
return ycbcrToRGBTransform * ycbcr;
}
typedef struct {
float2 position;
float2 texCoord;
} FogVertex;
typedef struct {
float4 position [[position]];
float2 texCoordCamera;
float2 texCoordScene;
} FogColorInOut;
// 霧化圖像頂點函數(shù)。
vertex FogColorInOut fogVertexTransform(const device FogVertex* cameraVertices [[ buffer(0) ]],
const device FogVertex* sceneVertices [[ buffer(1) ]],
unsigned int vid [[ vertex_id ]]) {
FogColorInOut out;
const device FogVertex& cv = cameraVertices[vid];
const device FogVertex& sv = sceneVertices[vid];
out.position = float4(cv.position, 0.0, 1.0);
out.texCoordCamera = cv.texCoord;
out.texCoordScene = sv.texCoord;
return out;
}
// 霧片段功能洪唐。
fragment half4 fogFragmentShader(FogColorInOut in [[ stage_in ]],
texture2d<float, access::sample> cameraImageTextureY [[ texture(0) ]],
texture2d<float, access::sample> cameraImageTextureCbCr [[ texture(1) ]],
depth2d<float, access::sample> arDepthTexture [[ texture(2) ]],
texture2d<uint> arDepthConfidence [[ texture(3) ]])
{
// 是否顯示置信度調(diào)試可視化钻蹬。
// -標簽: ConfidenceVisualization-
// 設置為“true”以可視化信度(Confidence)。
bool confidenceDebugVisualizationEnabled = false;
// 將最大霧飽和度設置為 4.0 米凭需。 設備最大為 5.0 米问欠。
const float fogMax = 4.0;
// 霧完全不透明,中灰色
const half4 fogColor = half4(0.5, 0.5, 0.5, 1.0);
// 信度調(diào)試可視化是紅色的粒蜈。
const half4 confidenceColor = half4(1.0, 0.0, 0.0, 1.0);
// 最大置信度為 `ARConfidenceLevelHigh` = 2顺献。
const uint maxConfidence = 2;
// 創(chuàng)建一個對象來采樣紋理。
constexpr sampler s(address::clamp_to_edge, filter::linear);
// 采樣此像素的相機圖像顏色枯怖。
float4 rgb = ycbcrToRGBTransform(
cameraImageTextureY.sample(s, in.texCoordCamera),
cameraImageTextureCbCr.sample(s, in.texCoordCamera)
);
half4 cameraColor = half4(rgb);
// 采樣此像素的深度值注整。
float depth = arDepthTexture.sample(s, in.texCoordCamera);
// 忽略大于最大霧距離的深度值。
depth = clamp(depth, 0.0, fogMax);
// 確定此片段的霧百分比度硝。
float fogPercentage = depth / fogMax;
// 根據(jù)霧的百分比混合相機和霧的顏色肿轨。
half4 foggedColor = mix(cameraColor, fogColor, fogPercentage);
// 如果禁用置信度可視化,只需返回霧化顏色塘淑。
if(!confidenceDebugVisualizationEnabled) {
return foggedColor;
} else {
// 采樣深度置信度萝招。
uint confidence = arDepthConfidence.sample(s, in.texCoordCamera).x;
// 根據(jù)置信度分配顏色百分比。
float confidencePercentage = (float)confidence / (float)maxConfidence;
// 回混合置信度和foggedColor存捺。
return mix(confidenceColor, foggedColor, confidencePercentage);
}
}
// 在著色器和宿主應用程序代碼之間共享的類型和枚舉。
#ifndef ShaderTypes_h
#define ShaderTypes_h
#include <simd/simd.h>
// 著色器和 C 代碼之間共享的緩沖區(qū)索引值曙蒸,
// 以確保 Metal 著色器緩沖區(qū)輸入匹配 Metal API 緩沖區(qū)集調(diào)用
typedef enum BufferIndices {
kBufferIndexMeshPositions = 0,
kBufferIndexMeshGenerics = 1,
kBufferIndexInstanceUniforms = 2,
kBufferIndexSharedUniforms = 3
} BufferIndices;
// 著色器和 C 代碼之間共享的屬性索引值捌治,
// 以確保 Metal 著色器頂點屬性索引與 Metal API 頂點描述符屬性索引匹配
typedef enum VertexAttributes {
kVertexAttributePosition = 0,
kVertexAttributeTexcoord = 1,
kVertexAttributeNormal = 2
} VertexAttributes;
// 著色器和 C 代碼之間共享的紋理索引值,
// 以確保 Metal 著色器紋理索引匹配 Metal API 紋理集調(diào)用的索引
typedef enum TextureIndices {
kTextureIndexColor = 0,
kTextureIndexY = 1,
kTextureIndexCbCr = 2,
} TextureIndices;
// 著色器和 C 代碼之間共享的結構纽窟,
// 以確保在 Metal 著色器中訪問的共享統(tǒng)一數(shù)據(jù)的布局與 C 代碼中統(tǒng)一數(shù)據(jù)集的布局相匹配
typedef struct {
// 相機
matrix_float4x4 projectionMatrix;
matrix_float4x4 viewMatrix;
// 照明屬性
vector_float3 ambientLightColor;
vector_float3 directionalLightDirection;
vector_float3 directionalLightColor;
float materialShininess;
// 消光
int useDepth;
} SharedUniforms;
// 著色器和 C 代碼之間共享的結構肖油,
// 以確保在 Metal 著色器中訪問的實例統(tǒng)一數(shù)據(jù)的布局與 C 代碼中統(tǒng)一數(shù)據(jù)集的布局相匹配
typedef struct {
matrix_float4x4 modelMatrix;
} InstanceUniforms;
#endif /* ShaderTypes_h */
// 主應用程序渲染器
import Foundation
import Metal
import MetalKit
import ARKit
import MetalPerformanceShaders
protocol RenderDestinationProvider {
var currentRenderPassDescriptor: MTLRenderPassDescriptor? { get }
var currentDrawable: CAMetalDrawable? { get }
var colorPixelFormat: MTLPixelFormat { get set }
var sampleCount: Int { get set }
}
// 飛行中的命令緩沖區(qū)的最大數(shù)量。
let kMaxBuffersInFlight: Int = 3
// 圖像平面的頂點數(shù)據(jù)臂港。
let kImagePlaneVertexData: [Float] = [
-1.0, -1.0, 0.0, 1.0,
1.0, -1.0, 1.0, 1.0,
-1.0, 1.0, 0.0, 0.0,
1.0, 1.0, 1.0, 0.0
]
class Renderer {
let session: ARSession
let device: MTLDevice
let inFlightSemaphore = DispatchSemaphore(value: kMaxBuffersInFlight)
var renderDestination: RenderDestinationProvider
// Metal 物體森枪。
var commandQueue: MTLCommandQueue!
// 保存用于源和目標渲染的頂點信息的對象。
var imagePlaneVertexBuffer: MTLBuffer!
// 定義渲染相機圖像和霧的 Metal 著色器的對象审孽。
var fogPipelineState: MTLRenderPipelineState!
// 用于將當前相機圖像傳輸?shù)?GPU 進行渲染的紋理县袱。
var cameraImageTextureY: CVMetalTexture?
var cameraImageTextureCbCr: CVMetalTexture?
// 用于存儲當前幀深度信息的紋理。
var depthTexture: CVMetalTexture?
// 用于將置信度信息傳遞給 GPU 以進行霧渲染的紋理佑力。
var confidenceTexture: CVMetalTexture?
// 將模糊深度數(shù)據(jù)的紋理傳遞給 GPU 以進行霧渲染式散。
var filteredDepthTexture: MTLTexture!
// 用于模糊渲染霧的深度數(shù)據(jù)的過濾器。
var blurFilter: MPSImageGaussianBlur?
// 捕獲的圖像紋理緩存打颤。
var cameraImageTextureCache: CVMetalTextureCache!
// 當前視口大小暴拄。
var viewportSize: CGSize = CGSize()
// 視口大小更改的標志漓滔。
var viewportSizeDidChange: Bool = false
// 通過設置 AR 會話、GPU 和屏幕后備存儲來初始化渲染器乖篷。
init(session: ARSession, metalDevice device: MTLDevice, renderDestination: RenderDestinationProvider) {
self.session = session
self.device = device
self.renderDestination = renderDestination
// 執(zhí)行 Metal 對象的一次性設置响驴。
loadMetal()
}
// 重新繪制尺寸
func drawRectResized(size: CGSize) {
viewportSize = size
viewportSizeDidChange = true
}
func update() {
// 等待以確保只有 kMaxBuffersInFlight 被 Metal 管道中的任何階段(應用程序、Metal撕蔼、驅動程序踏施、GPU 等)處理陷嘴。
_ = inFlightSemaphore.wait(timeout: DispatchTime.distantFuture)
// 為當前可繪制對象的每個渲染通道創(chuàng)建一個新的命令緩沖區(qū)剖张。
if let commandBuffer = commandQueue.makeCommandBuffer() {
commandBuffer.label = "MyCommand"
// 添加完成處理程序,它在 Metal 和 GPU 完全完成處理
// 我們正在編碼此幀的命令時發(fā)出 _inFlightSemaphore 信號尤筐。
// 這表示 Metal 和 GPU 何時不再需要我們正在寫入此幀的動態(tài)緩沖區(qū)诉探。
commandBuffer.addCompletedHandler { [weak self] commandBuffer in
if let strongSelf = self {
strongSelf.inFlightSemaphore.signal()
}
}
updateAppState()
applyGaussianBlur(commandBuffer: commandBuffer)
// 將深度和置信度像素緩沖區(qū)傳遞給 GPU 以在霧中著色日熬。
if let renderPassDescriptor = renderDestination.currentRenderPassDescriptor, let currentDrawable = renderDestination.currentDrawable {
if let fogRenderEncoding = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor) {
// 設置標簽以在捕獲的 Metal 幀中標識此渲染通道。
fogRenderEncoding.label = "MyFogRenderEncoder"
// 安排將相機圖像和霧繪制到屏幕上肾胯。
doFogRenderPass(renderEncoder: fogRenderEncoding)
// 完成編碼命令竖席。
fogRenderEncoding.endEncoding()
}
// 使用當前可繪制對象在幀緩沖區(qū)完成后安排呈現(xiàn)。
commandBuffer.present(currentDrawable)
}
// 在此處完成渲染并將命令緩沖區(qū)推送到 GPU敬肚。
commandBuffer.commit()
}
}
// MARK: - Private
// 創(chuàng)建并加載我們的基本 Metal 狀態(tài)對象毕荐。
func loadMetal() {
// 設置渲染所需的默認格式。
renderDestination.colorPixelFormat = .bgra8Unorm
renderDestination.sampleCount = 1
// 使用我們的圖像平面頂點數(shù)據(jù)創(chuàng)建一個頂點緩沖區(qū)艳馒。
let imagePlaneVertexDataCount = kImagePlaneVertexData.count * MemoryLayout<Float>.size
imagePlaneVertexBuffer = device.makeBuffer(bytes: kImagePlaneVertexData, length: imagePlaneVertexDataCount, options: [])
imagePlaneVertexBuffer.label = "ImagePlaneVertexBuffer"
// 在項目中加載所有帶有 Metal 文件擴展名的著色器文件憎亚。
let defaultLibrary = device.makeDefaultLibrary()!
// 為我們的圖像平面頂點緩沖區(qū)創(chuàng)建一個頂點描述符。
let imagePlaneVertexDescriptor = MTLVertexDescriptor()
// 位置
imagePlaneVertexDescriptor.attributes[0].format = .float2
imagePlaneVertexDescriptor.attributes[0].offset = 0
imagePlaneVertexDescriptor.attributes[0].bufferIndex = Int(kBufferIndexMeshPositions.rawValue)
// 紋理坐標
imagePlaneVertexDescriptor.attributes[1].format = .float2
imagePlaneVertexDescriptor.attributes[1].offset = 8
imagePlaneVertexDescriptor.attributes[1].bufferIndex = Int(kBufferIndexMeshPositions.rawValue)
// 緩沖區(qū)布局弄慰。
imagePlaneVertexDescriptor.layouts[0].stride = 16
imagePlaneVertexDescriptor.layouts[0].stepRate = 1
imagePlaneVertexDescriptor.layouts[0].stepFunction = .perVertex
// 創(chuàng)建相機圖像紋理緩存第美。
var textureCache: CVMetalTextureCache?
CVMetalTextureCacheCreate(nil, nil, device, nil, &textureCache)
cameraImageTextureCache = textureCache
// 定義將在 GPU 上渲染相機圖像和霧的著色器。
let fogVertexFunction = defaultLibrary.makeFunction(name: "fogVertexTransform")!
let fogFragmentFunction = defaultLibrary.makeFunction(name: "fogFragmentShader")!
let fogPipelineStateDescriptor = MTLRenderPipelineDescriptor()
fogPipelineStateDescriptor.label = "MyFogPipeline"
fogPipelineStateDescriptor.sampleCount = renderDestination.sampleCount
fogPipelineStateDescriptor.vertexFunction = fogVertexFunction
fogPipelineStateDescriptor.fragmentFunction = fogFragmentFunction
fogPipelineStateDescriptor.vertexDescriptor = imagePlaneVertexDescriptor
fogPipelineStateDescriptor.colorAttachments[0].pixelFormat = renderDestination.colorPixelFormat
// 初始化管道陆爽。
do {
try fogPipelineState = device.makeRenderPipelineState(descriptor: fogPipelineStateDescriptor)
} catch let error {
print("未能創(chuàng)建霧管道狀態(tài), error \(error)")
}
// 一幀渲染工作創(chuàng)建命令隊列什往。
commandQueue = device.makeCommandQueue()
}
// 更新任何應用程序狀態(tài)。
func updateAppState() {
// 獲取 AR 會話的當前幀慌闭。
guard let currentFrame = session.currentFrame else {
return
}
// 準備當前幀的相機圖像以傳輸?shù)?GPU别威。
updateCameraImageTextures(frame: currentFrame)
// 準備當前幀的深度和置信度圖像以傳輸?shù)?GPU。
updateARDepthTexures(frame: currentFrame)
// 如果屏幕大小發(fā)生變化驴剔,則更新目標渲染頂點信息省古。
if viewportSizeDidChange {
viewportSizeDidChange = false
updateImagePlane(frame: currentFrame)
}
}
// 創(chuàng)建兩個紋理(Y 和 CbCr)以將當前幀的相機圖像傳輸?shù)?GPU 進行渲染。
func updateCameraImageTextures(frame: ARFrame) {
if CVPixelBufferGetPlaneCount(frame.capturedImage) < 2 {
return
}
cameraImageTextureY = createTexture(fromPixelBuffer: frame.capturedImage, pixelFormat: .r8Unorm, planeIndex: 0)
cameraImageTextureCbCr = createTexture(fromPixelBuffer: frame.capturedImage, pixelFormat: .rg8Unorm, planeIndex: 1)
}
// 給定參數(shù)像素緩沖區(qū)的格式仔拟,分配適當?shù)?MTL 像素格式衫樊。
fileprivate func setMTLPixelFormat(_ texturePixelFormat: inout MTLPixelFormat?, basedOn pixelBuffer: CVPixelBuffer!) {
if CVPixelBufferGetPixelFormatType(pixelBuffer) == kCVPixelFormatType_DepthFloat32 {
texturePixelFormat = .r32Float
} else if CVPixelBufferGetPixelFormatType(pixelBuffer) == kCVPixelFormatType_OneComponent8 {
texturePixelFormat = .r8Uint
} else {
fatalError("Unsupported ARDepthData pixel-buffer format.")
}
}
// 準備場景深度信息以傳輸?shù)?GPU 進行渲染。
func updateARDepthTexures(frame: ARFrame) {
if #available(iOS 14.0, *) {
// 當前幀獲取場景深度或平滑場景深度。
guard let sceneDepth = frame.smoothedSceneDepth ?? frame.sceneDepth else {
print("獲取場景深度失敗科侈。")
return
}
var pixelBuffer: CVPixelBuffer!
pixelBuffer = sceneDepth.depthMap
// 為深度信息設置目標像素格式载佳,
// 并根據(jù) ARKit 提供的深度圖像創(chuàng)建金屬紋理。
var texturePixelFormat: MTLPixelFormat!
setMTLPixelFormat(&texturePixelFormat, basedOn: pixelBuffer)
depthTexture = createTexture(fromPixelBuffer: pixelBuffer, pixelFormat: texturePixelFormat, planeIndex: 0)
// 從當前幀獲取當前深度置信度值臀栈。
// 設置置信度信息的目標像素格式蔫慧,
// 并根據(jù) ARKit 提供的置信度圖像創(chuàng)建 Metal 紋理。
pixelBuffer = sceneDepth.confidenceMap
setMTLPixelFormat(&texturePixelFormat, basedOn: pixelBuffer)
confidenceTexture = createTexture(fromPixelBuffer: pixelBuffer, pixelFormat: texturePixelFormat, planeIndex: 0)
}
}
// 從參數(shù)平面索引處的 CVPixelBuffer 創(chuàng)建具有參數(shù)像素格式的 Metal 紋理权薯。
func createTexture(fromPixelBuffer pixelBuffer: CVPixelBuffer, pixelFormat: MTLPixelFormat, planeIndex: Int) -> CVMetalTexture? {
let width = CVPixelBufferGetWidthOfPlane(pixelBuffer, planeIndex)
let height = CVPixelBufferGetHeightOfPlane(pixelBuffer, planeIndex)
var texture: CVMetalTexture? = nil
let status = CVMetalTextureCacheCreateTextureFromImage(nil, cameraImageTextureCache, pixelBuffer, nil, pixelFormat,
width, height, planeIndex, &texture)
if status != kCVReturnSuccess {
texture = nil
}
return texture
}
// 置頂點數(shù)據(jù)(源和目標矩形)渲染姑躲。
func updateImagePlane(frame: ARFrame) {
// 更新圖像平面的紋理坐標以填充視口。
let displayToCameraTransform = frame.displayTransform(for: .landscapeRight, viewportSize: viewportSize).inverted()
let vertexData = imagePlaneVertexBuffer.contents().assumingMemoryBound(to: Float.self)
let fogVertexData = imagePlaneVertexBuffer.contents().assumingMemoryBound(to: Float.self)
for index in 0...3 {
let textureCoordIndex = 4 * index + 2
let textureCoord = CGPoint(x: CGFloat(kImagePlaneVertexData[textureCoordIndex]), y: CGFloat(kImagePlaneVertexData[textureCoordIndex + 1]))
let transformedCoord = textureCoord.applying(displayToCameraTransform)
vertexData[textureCoordIndex] = Float(transformedCoord.x)
vertexData[textureCoordIndex + 1] = Float(transformedCoord.y)
fogVertexData[textureCoordIndex] = Float(transformedCoord.x)
fogVertexData[textureCoordIndex + 1] = Float(transformedCoord.y)
}
}
// 安排要在 GPU 上渲染的相機圖像和霧盟蚣。
func doFogRenderPass(renderEncoder: MTLRenderCommandEncoder) {
guard let cameraImageY = cameraImageTextureY, let cameraImageCbCr = cameraImageTextureCbCr,
let confidenceTexture = confidenceTexture else {
return
}
// 推送一個調(diào)試組黍析,使您能夠在 Metal 幀捕獲中識別此渲染通道。
renderEncoder.pushDebugGroup("FogPass")
// 設置渲染命令編碼器狀態(tài)屎开。
renderEncoder.setCullMode(.none)
renderEncoder.setRenderPipelineState(fogPipelineState)
// 設置平面頂點緩沖區(qū)阐枣。
renderEncoder.setVertexBuffer(imagePlaneVertexBuffer, offset: 0, index: 0)
renderEncoder.setVertexBuffer(imagePlaneVertexBuffer, offset: 0, index: 1)
// 為霧片段著色器設置紋理。
renderEncoder.setFragmentTexture(CVMetalTextureGetTexture(cameraImageY), index: 0)
renderEncoder.setFragmentTexture(CVMetalTextureGetTexture(cameraImageCbCr), index: 1)
renderEncoder.setFragmentTexture(filteredDepthTexture, index: 2)
renderEncoder.setFragmentTexture(CVMetalTextureGetTexture(confidenceTexture), index: 3)
// 繪制最終四邊形以顯示
renderEncoder.drawPrimitives(type: .triangleStrip, vertexStart: 0, vertexCount: 4)
renderEncoder.popDebugGroup()
}
// MARK: - MPS Filter
// 設置過濾器來處理深度紋理奄抽。
func setupFilter(width: Int, height: Int) {
// 創(chuàng)建一個目標后備存儲來保存模糊結果蔼两。
let filteredDepthDescriptor = MTLTextureDescriptor()
filteredDepthDescriptor.pixelFormat = .r32Float
filteredDepthDescriptor.width = width
filteredDepthDescriptor.height = height
filteredDepthDescriptor.usage = [.shaderRead, .shaderWrite]
filteredDepthTexture = device.makeTexture(descriptor: filteredDepthDescriptor)
blurFilter = MPSImageGaussianBlur(device: device, sigma: 5)
}
// 使用 `blurFilter` 在 GPU 上安排要模糊的深度紋理。
func applyGaussianBlur(commandBuffer: MTLCommandBuffer) {
guard let arDepthTexture = depthTexture, let depthTexture = CVMetalTextureGetTexture(arDepthTexture) else {
print("錯誤:無法應用 MPS 過濾器逞度。")
return
}
guard let blur = blurFilter else {
setupFilter(width: depthTexture.width, height: depthTexture.height)
return
}
let inputImage = MPSImage(texture: depthTexture, featureChannels: 1)
let outputImage = MPSImage(texture: filteredDepthTexture, featureChannels: 1)
blur.encode(commandBuffer: commandBuffer, sourceImage: inputImage, destinationImage: outputImage)
}
}
import UIKit
import ARKit
class Demo01_SelectARViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if #available(iOS 14.0, *) {
if (!ARWorldTrackingConfiguration.supportsFrameSemantics(.sceneDepth) || !ARWorldTrackingConfiguration.supportsFrameSemantics(.smoothedSceneDepth)) {
print("不支持")
} else {
print("支持")
let vc = Demo01_ARViewController()
vc.modalPresentationStyle = .fullScreen
present(vc, animated: true)
}
} else {
// Fallback on earlier versions
}
}
}
import UIKit
import ARKit
import Metal
import MetalKit
extension MTKView: RenderDestinationProvider {}
class Demo01_ARViewController: UIViewController, ARSessionDelegate, MTKViewDelegate {
let mtkView = MTKView()
var session: ARSession!
var configuration = ARWorldTrackingConfiguration()
var renderer: Renderer!
override func viewDidLoad() {
super.viewDidLoad()
// 將此視圖控制器設置為會話的委托额划。
session = ARSession()
session.delegate = self
// 將視圖設置為使用默認設備。
mtkView.frame = self.view.bounds
self.view.addSubview(mtkView)
mtkView.device = MTLCreateSystemDefaultDevice()
mtkView.backgroundColor = .clear
mtkView.delegate = self
guard mtkView.device != nil else {
print("Metal 不支持這設備")
return
}
// 配置渲染器以繪制到視圖档泽。
renderer = Renderer(session: session, metalDevice: mtkView.device!, renderDestination: mtkView)
// 計劃第一次繪制的屏幕大小俊戳。
renderer.drawRectResized(size: view.bounds.size)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// 啟用平滑的場景深度幀語義。
if #available(iOS 14.0, *) {
configuration.frameSemantics = .smoothedSceneDepth
configuration.frameSemantics = .sceneDepth
}
// 運行視圖的會話茁瘦。
session.run(configuration)
// 在 AR 體驗期間屏幕不應變暗品抽。
UIApplication.shared.isIdleTimerDisabled = true
}
// 自動隱藏主頁指示器以最大限度地沉浸在 AR 體驗中。
override var prefersHomeIndicatorAutoHidden: Bool {
return true
}
// 隱藏狀態(tài)欄以最大限度地沉浸在 AR 體驗中甜熔。
override var prefersStatusBarHidden: Bool {
return true
}
// MARK: - MTKViewDelegate
func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
renderer.drawRectResized(size: size)
}
func draw(in view: MTKView) {
renderer.update()
}
// MARK: - ARSessionDelegate
func session(_ session: ARSession, didFailWithError error: Error) {
// 向用戶顯示錯誤消息。
guard error is ARError else { return }
let errorWithInfo = error as NSError
let messages = [
errorWithInfo.localizedDescription,
errorWithInfo.localizedFailureReason,
errorWithInfo.localizedRecoverySuggestion
]
let errorMessage = messages.compactMap({ $0 }).joined(separator: "\n")
DispatchQueue.main.async {
// 顯示警報突倍,告知已發(fā)生的錯誤腔稀。
let alertController = UIAlertController(title: "AR 會話失敗。", message: errorMessage, preferredStyle: .alert)
let restartAction = UIAlertAction(title: "重新啟動會話", style: .default) { _ in
alertController.dismiss(animated: true, completion: nil)
self.session.run(self.configuration, options: .resetSceneReconstruction)
}
alertController.addAction(restartAction)
self.present(alertController, animated: true, completion: nil)
}
}
}