You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift中如何让ARKit的ARView背景黑白化但保留3D节点色彩?

实现ARKit相机背景黑白滤镜并保留3D节点色彩

你的核心问题在于当前的SCNTechnique直接对整个渲染结果(包含相机背景和3D节点)应用了灰度滤镜,且渲染流程未区分相机背景与3D节点的渲染步骤。以下是正确的实现方案:

步骤1:重新设计SCNTechnique渲染流程

拆分两个渲染Pass,分别处理相机背景和3D节点:

  • 第一个Pass:仅对相机实时帧应用黑白滤镜,输出到临时纹理
  • 第二个Pass:渲染3D节点并叠加在处理后的相机背景上,最终输出到屏幕

修改后的SceneFilterTechnique.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>sequence</key>
    <array>
        <string>process_camera_feed</string>
        <string>render_scene</string>
    </array>
    <key>passes</key>
    <dict>
        <!-- Pass 1: 处理相机背景为黑白 -->
        <key>process_camera_feed</key>
        <dict>
            <key>metalVertexShader</key>
            <string>mapTexture</string>
            <key>metalFragmentShader</key>
            <string>grayscaleFilter</string>
            <key>draw</key>
            <string>DRAW_QUAD</string>
            <key>inputs</key>
            <dict>
                <key>cameraTexture</key>
                <string>AR_CAMERA_IMAGE</string>
            </dict>
            <key>outputs</key>
            <dict>
                <key>filteredBackground</key>
                <string>COLOR</string>
            </dict>
            <key>clear</key>
            <false/>
        </dict>
        
        <!-- Pass 2: 渲染3D节点到处理后的背景上 -->
        <key>render_scene</key>
        <dict>
            <key>draw</key>
            <string>DRAW_SCENE</string>
            <key>inputs</key>
            <dict>
                <key>color</key>
                <string>filteredBackground</string>
            </dict>
            <key>outputs</key>
            <dict>
                <key>color</key>
                <string>COLOR</string>
            </dict>
            <key>clear</key>
            <false/>
        </dict>
    </dict>
    <key>targets</key>
    <dict>
        <key>filteredBackground</key>
        <dict>
            <key>type</key>
            <string>texture</string>
            <key>size</key>
            <string>framebuffer</string>
            <key>format</key>
            <string>RGBA8</string>
        </dict>
    </dict>
</dict>
</plist>

步骤2:更新Metal着色器

修改SceneFilterTechnique.metal,确保灰度滤镜仅作用于相机纹理:

#include <metal_stdlib>
using namespace metal;

typedef struct {
    float4 renderedCoordinate [[position]];
    float2 textureCoordinate;
} TextureMappingVertex;

vertex TextureMappingVertex mapTexture(unsigned int vertex_id [[ vertex_id ]]) {
    float4x4 renderedCoordinates = float4x4(
        float4(-1.0, -1.0, 0.0, 1.0),
        float4(1.0, -1.0, 0.0, 1.0),
        float4(-1.0, 1.0, 0.0, 1.0),
        float4(1.0, 1.0, 0.0, 1.0)
    );

    float4x2 textureCoordinates = float4x2(
        float2(0.0, 1.0),
        float2(1.0, 1.0),
        float2(0.0, 0.0),
        float2(1.0, 0.0)
    );

    TextureMappingVertex outVertex;
    outVertex.renderedCoordinate = renderedCoordinates[vertex_id];
    outVertex.textureCoordinate = textureCoordinates[vertex_id];
    return outVertex;
}

// 相机背景灰度滤镜(采用人眼感知更自然的灰度公式)
fragment half4 grayscaleFilter(TextureMappingVertex mappingVertex [[ stage_in ]],
                              texture2d<float, access::sample> cameraTexture [[ texture(0) ]]) {
    constexpr sampler s(address::clamp_to_edge, filter::linear);
    float4 color = cameraTexture.sample(s, mappingVertex.textureCoordinate);
    float grayscale = (color.r * 0.299 + color.g * 0.587 + color.b * 0.114);
    return half4(grayscale, grayscale, grayscale, color.a);
}

步骤3:调整Swift代码

确保ARSCNView正确加载Technique并启动AR会话:

import UIKit
import ARKit

class ViewController: UIViewController, ARSCNViewDelegate {
    @IBOutlet var sceneView: ARSCNView!

    override func viewDidLoad() {
        super.viewDidLoad()
        sceneView.delegate = self
        
        // 加载自定义渲染Technique
        let filterTechnique = makeTechnique(fromPlistNamed: "SceneFilterTechnique")
        sceneView.technique = filterTechnique
        
        // 启动AR世界追踪会话
        let configuration = ARWorldTrackingConfiguration()
        sceneView.session.run(configuration)
        
        // 添加点击放置节点的手势
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
        sceneView.addGestureRecognizer(tapGesture)
    }

    @objc func handleTap(_ gesture: UITapGestureRecognizer) {
        let location = gesture.location(in: sceneView)
        let hitResults = sceneView.hitTest(location, types: .featurePoint)
        guard let hitResult = hitResults.first else { return }
        
        let sphere = SCNSphere(radius: 0.05)
        sphere.firstMaterial?.diffuse.contents = UIColor.blue
        let node = SCNNode(geometry: sphere)
        node.position = SCNVector3(
            hitResult.worldTransform.columns.3.x,
            hitResult.worldTransform.columns.3.y,
            hitResult.worldTransform.columns.3.z
        )
        sceneView.scene.rootNode.addChildNode(node)
    }
    
    private func makeTechnique(fromPlistNamed plistName: String) -> SCNTechnique {
        guard let url = Bundle.main.url(forResource: plistName, withExtension: "plist") else {
            fatalError("\(plistName).plist 不存在于主Bundle中")
        }
        
        guard let dictionary = NSDictionary(contentsOf: url) as? [String: Any] else {
            fatalError("解析 \(plistName).plist 失败")
        }
        
        guard let technique = SCNTechnique(dictionary: dictionary) else {
            fatalError("初始化SCNTechnique失败")
        }
        
        return technique
    }
}

关键说明

  1. AR_CAMERA_IMAGE是ARKit提供的内置纹理输入,直接对应相机实时视频帧,确保仅处理相机背景
  2. 第一个Pass将处理后的黑白背景输出到临时纹理filteredBackground,第二个Pass以此为基础渲染3D节点,实现叠加效果
  3. 使用标准灰度转换公式,比简单色彩平均更符合人眼对色彩的感知

效果验证

运行代码后,相机Feed将显示为黑白,而点击放置的蓝色球体将保留原始色彩,完美实现需求。

内容的提问来源于stack exchange,提问作者user22007718

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 19:37:32