将 MetalView 与 SwiftUI 结合使用?我如何在那里放置一些东西来展示?

问题描述 投票:0回答:4

我一直坚持使用 SwiftUI 和 Metal,几乎要放弃了。

我从 https://developer.apple.com/forums/thread/119112?answerId=654964022#654964022 得到这个示例:

import MetalKit
struct MetalView: NSViewRepresentable {
    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }
    func makeNSView(context: NSViewRepresentableContext<MetalView>) -> MTKView {
        let mtkView = MTKView()
        mtkView.delegate = context.coordinator
        mtkView.preferredFramesPerSecond = 60
        mtkView.enableSetNeedsDisplay = true
        if let metalDevice = MTLCreateSystemDefaultDevice() {
            mtkView.device = metalDevice
        }
        mtkView.framebufferOnly = false
        mtkView.clearColor = MTLClearColor(red: 0, green: 0, blue: 0, alpha: 0)
        mtkView.drawableSize = mtkView.frame.size
        mtkView.enableSetNeedsDisplay = true
        return mtkView
    }
    func updateNSView(_ nsView: MTKView, context: NSViewRepresentableContext<MetalView>) {
    }
    class Coordinator : NSObject, MTKViewDelegate {
        var parent: MetalView
        var metalDevice: MTLDevice!
        var metalCommandQueue: MTLCommandQueue!
        
        init(_ parent: MetalView) {
            self.parent = parent
            if let metalDevice = MTLCreateSystemDefaultDevice() {
                self.metalDevice = metalDevice
            }
            self.metalCommandQueue = metalDevice.makeCommandQueue()!
            super.init()
        }
        func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) {
        }
        func draw(in view: MTKView) {
            guard let drawable = view.currentDrawable else {
                return
            }
            let commandBuffer = metalCommandQueue.makeCommandBuffer()
            let rpd = view.currentRenderPassDescriptor
            rpd?.colorAttachments[0].clearColor = MTLClearColorMake(0, 1, 0, 1)
            rpd?.colorAttachments[0].loadAction = .clear
            rpd?.colorAttachments[0].storeAction = .store
            let re = commandBuffer?.makeRenderCommandEncoder(descriptor: rpd!)
            re?.endEncoding()
            commandBuffer?.present(drawable)
            commandBuffer?.commit()
        }
    }
}

...但我无法理解如何使用这个 MetalView() 来显示数据,当我从 SwiftUI 视图调用它时,它似乎确实有效。我想用它来显示一个 CIImage,它将被 CIFilters 过滤和操作...

有人可以为我指出正确的方向,告诉我如何告诉这个视图如何显示某些内容吗?我想我需要它来显示纹理的内容,但尝试了无数个小时,最终从头开始无数次......

这就是我现在运行图像滤镜的方式,但它会导致滑块非常慢,这就是我决定尝试学习 Metal 的原因......但这确实非常耗时。由于缺乏文档而令人沮丧......

func ciExposure (inputImage: CIImage, inputEV: Double) -> CIImage {
    let filter = CIFilter(name: "CIExposureAdjust")!
    filter.setValue(inputImage, forKey: kCIInputImageKey)
    filter.setValue(inputEV, forKey: kCIInputEVKey)
    return filter.outputImage!
}

我想我需要获取该filter.outputImage并以某种方式将其传递到MetalView?

任何帮助都非常非常感谢...

swift macos swiftui metal core-image
4个回答
4
投票

Apple 的 WWDC 2022 包含一个名为“使用 Core Image、Metal 和 SwiftUI 显示 EDR 内容”的教程/视频,其中描述了如何将 Core Image 与 Metal 和 SwiftUI 混合。它指向一些名为“使用核心图像渲染目标生成动画”的新示例代码(此处)。

这个示例项目非常以 CoreImage 为中心(这应该非常适合您的目的),但我希望 Apple 发布更多示例代码示例,显示 Metal 与 SwiftUI 集成。


2
投票

我在 Github 上有一个小型 Core Image + SwiftUI 示例项目,这对您来说可能是一个很好的起点。它还没有涵盖很多内容,但它已经演示了如何显示过滤后的相机帧。

特别检查视图的

draw

功能
。它用于将 CIImage
 渲染到 
MTKView
 中(您可以在委托的 
draw
 函数中执行相同操作)。


1
投票
好吧,这对我有用:

func draw(in view: MTKView) { guard let drawable = view.currentDrawable else { return } let colorSpace = CGColorSpaceCreateDeviceRGB() let commandBuffer = metalCommandQueue.makeCommandBuffer() let rpd = view.currentRenderPassDescriptor rpd?.colorAttachments[0].clearColor = MTLClearColorMake(0, 1, 0, 1) rpd?.colorAttachments[0].loadAction = .clear rpd?.colorAttachments[0].storeAction = .store let re = commandBuffer?.makeRenderCommandEncoder(descriptor: rpd!) re?.endEncoding() context.render((AppState.shared.rawImage ?? AppState.shared.rawImageOriginal)!, to: drawable.texture, commandBuffer: commandBuffer, bounds: AppState.shared.rawImageOriginal!.extent, colorSpace: colorSpace) commandBuffer?.present(drawable) commandBuffer?.commit() }
AppState.shared.rawImage 是我从过滤功能获得的 CIImage 纹理。

上下文是在其他地方创建的,但应该是:

context = CIContext(mtlDevice: metalDevice)
下一步是添加 Frank Schlegel 提供的代码的居中部分。


0
投票
从 macOS 14.0 / Xcode 15 开始,您可以使用 Metal 代码作为视图修改器。

因此,您可以创建一个 Rectangle() 并应用金属视觉效果(或扭曲效果)。

这是一个包含许多参数的长示例。

ContentView.swift


import SwiftUI struct ContentView: View { @State private var startTime: Date = .now @State private var touch: CGPoint = .zero @State private var schlorp: CGFloat = 5.0 @State private var crisp: CGFloat = 0.005 @State private var shapeCount: CGFloat = 5.0 var body: some View { TimelineView(.animation) { timeline in let time = startTime.distance(to: timeline.date) ZStack { Image("sf-ferry-bldg") .resizable() .aspectRatio(contentMode: .fill) .visualEffect { content, proxy in content .colorEffect(ShaderLibrary.wavy( .float2(proxy.size), .float2(touch), .float(time), .float(schlorp), .float(crisp), .float(shapeCount) ) ) } .gesture( DragGesture(minimumDistance: .zero) .onChanged { value in touch = value.location } ) } } .toolbar { ToolbarItem { HStack { Button(action: { shapeCount -= 1.0 }, label: { Image(systemName: "minus") }) Text("Shapes: \(shapeCount.rounded())") Button(action: { shapeCount += 1.0 }, label: { Image(systemName: "plus") }) } } ToolbarItem { Text("Crisp \((crisp * 1_000).formatted())") } ToolbarItem { Slider(value: $crisp, in: 0.0001...0.01) { Text("Crisp") } minimumValueLabel: { Text("1") } maximumValueLabel: { Text("100") } .frame(minWidth: 200) } ToolbarItem { Text("Schlorp \(schlorp.formatted())") } ToolbarItem { Slider(value: $schlorp, in: 2.0...100.0) { Text("Schlorp") } minimumValueLabel: { Text("1.5") } maximumValueLabel: { Text("50") } .frame(minWidth: 200) } } } }
你需要一个像这样的金属锉刀:

[[ stitchable ]] half4 wavy(float2 position, half4 color, float2 size, float2 touch, float time, float schlorp = 5.0, float crisp = 10.0, float inShapeCount = 5) { // Calculate the UV coordinates float2 uv = position / size.xy; // Calculate the sine wave displacement based on time and UV coordinates float displacement = cos(uv.x * schlep + time) * sin(uv.y * crisp + time) * .1; // * 10.0 + time) * 0.1; // Apply the displacement to the X coordinate to create the wavy effect float x = uv.x + displacement; // Create a grayscale color based on the X coordinate float3 wavyColor = float3(x, -x, 1.0); // Output the final color return half4(half3(color), 1.0); }
    
© www.soinside.com 2019 - 2024. All rights reserved.