06_ARKit 相機:使用場景深度打造霧效

對物理環(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)
        }
        
    }
}

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末羽历,一起剝皮案震驚了整個濱河市焊虏,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌秕磷,老刑警劉巖诵闭,帶你破解...
    沈念sama閱讀 219,589評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡疏尿,警方通過查閱死者的電腦和手機瘟芝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,615評論 3 396
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來褥琐,“玉大人锌俱,你說我怎么就攤上這事〉谐剩” “怎么了贸宏?”我有些...
    開封第一講書人閱讀 165,933評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長磕洪。 經(jīng)常有香客問我吭练,道長,這世上最難降的妖魔是什么析显? 我笑而不...
    開封第一講書人閱讀 58,976評論 1 295
  • 正文 為了忘掉前任鲫咽,我火速辦了婚禮,結果婚禮上叫榕,老公的妹妹穿的比我還像新娘浑侥。我一直安慰自己,他們只是感情好晰绎,可當我...
    茶點故事閱讀 67,999評論 6 393
  • 文/花漫 我一把揭開白布寓落。 她就那樣靜靜地躺著,像睡著了一般荞下。 火紅的嫁衣襯著肌膚如雪伶选。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,775評論 1 307
  • 那天尖昏,我揣著相機與錄音仰税,去河邊找鬼。 笑死抽诉,一個胖子當著我的面吹牛陨簇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播迹淌,決...
    沈念sama閱讀 40,474評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼河绽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了唉窃?” 一聲冷哼從身側響起耙饰,我...
    開封第一講書人閱讀 39,359評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎纹份,沒想到半個月后苟跪,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體廷痘,經(jīng)...
    沈念sama閱讀 45,854評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,007評論 3 338
  • 正文 我和宋清朗相戀三年件已,在試婚紗的時候發(fā)現(xiàn)自己被綠了笋额。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,146評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡拨齐,死狀恐怖鳞陨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情瞻惋,我是刑警寧澤厦滤,帶...
    沈念sama閱讀 35,826評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站歼狼,受9級特大地震影響掏导,放射性物質發(fā)生泄漏。R本人自食惡果不足惜羽峰,卻給世界環(huán)境...
    茶點故事閱讀 41,484評論 3 331
  • 文/蒙蒙 一趟咆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧梅屉,春花似錦值纱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,029評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至惰聂,卻和暖如春疆偿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背搓幌。 一陣腳步聲響...
    開封第一講書人閱讀 33,153評論 1 272
  • 我被黑心中介騙來泰國打工杆故, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人溉愁。 一個月前我還...
    沈念sama閱讀 48,420評論 3 373
  • 正文 我出身青樓处铛,卻偏偏與公主長得像,于是被迫代替她去往敵國和親拐揭。 傳聞我的和親對象是個殘疾皇子罢缸,可洞房花燭夜當晚...
    茶點故事閱讀 45,107評論 2 356

推薦閱讀更多精彩內(nèi)容