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

SwiftUI Canvas与Metal渲染结果存在差异的原因是什么?

SwiftUI Canvas与Metal渲染不透明度场函数结果不一致问题

我需要渲染一个不透明度由场函数定义的SwiftUI View。参考jrturton的实现,分别做了Canvas版本和性能更优的Metal版本,但两者渲染结果不一致。我观察到两个版本都使用float32类型,搞不懂差异的原因。

渲染结果对比

Canvas与Metal渲染结果差异

代码实现

SwiftUI 视图与场函数代码

struct ContentView: View {
let boardSize = CGSize(width: 100, height: 100)

var body: some View {
    HStack {
        VStack {
            Canvas { context, size in
                let pixelWidth  = Int(size.width)
                let pixelHeight = Int(size.height)
                var shadings: [Double: GraphicsContext.Shading] = [:]
                for y in 0 ..< pixelHeight {
                    for x in 0 ..< pixelWidth {
                        let path = Path(CGRect(x: x, y: y, width: 1, height: 1))
                        let strength = BorderField.borderField(size: boardSize, at: CGPoint(x: x, y: y)) 
                        if let shading = shadings[strength] {
                            context.fill(path, with: shading)
                        } else {
                            let shading = GraphicsContext.Shading.color(.red.opacity(strength))
                            shadings[strength] = shading
                            context.fill(path, with: shading)
                        }
                    }
                }
            }
            .frame(width: boardSize.width, height: boardSize.height)
            Text("Canvas")
        }
        
        VStack {
            Rectangle()
                .frame(width: boardSize.width, height: boardSize.height)
                .colorEffect(ShaderLibrary.borderField(.float2(boardSize.width, boardSize.height), .color(.red)))
            Text("Metal")
        }
    }
}

class BorderField {
    
    static func borderField(size: CGSize, at point: CGPoint) -> Double {
        let a = size.width
        let b = size.height
        let x = point.x
        let y = point.y
        
        // Compute distance to border
        let dt = b - y  // Distance to top
        let db = y      // Distance to bottom
        let dl = x      // Distance to left
        let dr = a - x  // Distance to right
        let minDistance = min(dt, db, dl, dr)
        
        let d = Double(minDistance)
        let r = d + 1.0 // min r is now 1
        let strength = 1.0/sqrt(r)
        return strength
    }

}

Metal 着色器代码

#include <metal_stdlib>
using namespace metal;

[[ stitchable ]] half4 borderField(float2 position, half4 currentColor, float2 size, half4 newColor) {
    
    // Compute distance to border
    float dt = size.y - position.y; // Distance to top
    float db = position.y;          // Distance to bottom
    float dl = position.x;          // Distance to left
    float dr = size.x - position.x; // Distance to right
    float minDistance = min(min(dt, db), min(dl, dr));
    float r = minDistance + 1.0;
    float strength = 1.0 / sqrt(r);
    float str = strength < 0.2 ? 0.0 : strength; // If not set to exactly 0, zstacked views are covered
    return half4(newColor.rgb, str);
}

补充说明

我在Metal着色器中添加了一行代码:

float str = strength < 0.2 ? 0.0 : strength;  

目的是解决ZStack叠加多个场时的覆盖问题,但即使去掉这段代码,原始版本的Canvas和Metal渲染结果仍有差异:

原始代码渲染结果差异

我没有Metal渲染经验,希望有人解释为什么两者渲染结果不同。


内容的提问来源于stack exchange,提问作者Reinhard Männer

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 11:15:12