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

代码实现
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
相关产品推荐
相关产品推荐

