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 } }
关键说明
AR_CAMERA_IMAGE是ARKit提供的内置纹理输入,直接对应相机实时视频帧,确保仅处理相机背景- 第一个Pass将处理后的黑白背景输出到临时纹理
filteredBackground,第二个Pass以此为基础渲染3D节点,实现叠加效果 - 使用标准灰度转换公式,比简单色彩平均更符合人眼对色彩的感知
效果验证
运行代码后,相机Feed将显示为黑白,而点击放置的蓝色球体将保留原始色彩,完美实现需求。
内容的提问来源于stack exchange,提问作者user22007718
相关产品推荐
相关产品推荐

