【问题标题】:Cannot Display Sprites Above SK3DNode无法在 SK3DNode 上方显示精灵
【发布时间】:2023-04-04 14:03:01
【问题描述】:

我正在使用 SK3DNode 的实例在我的 SpriteKit 场景中显示一些基本的 3D 几何图形,以显示 SceneKit 场景的内容,如 Apple's article here 中所述。

我已经能够使用 SceneKit 节点变换和 SK3DNode 的位置/视口大小来定位节点和 3D 内容。

接下来,我想在我的 SpriteKit 场景中显示其他一些精灵叠加在 3D 内容之上,但我无法这样做:SK3DNode 的内容始终绘制在顶部。

我尝试同时指定 SK3DNode 和 SKSpriteNode 的 zPosition 属性,但无济于事。

来自 Apple 关于 SK3DNode 的文档:

使用 SK3DNode 对象将 3D SceneKit 内容合并到 基于 SpriteKit 的游戏。当 SpriteKit 渲染节点时,SceneKit 场景是动画并首先渲染的。然后这个渲染的图像是 合成到 SpriteKit 场景中。使用 scnScene 属性 指定要渲染的 SceneKit 场景。

(强调我的)

关于 z-order 有点模棱两可(它似乎只提到了渲染发生的 temporal 顺序)。

我整理了一个minimal demo project on GitHub;相关代码为:

1. SceneKit 场景

import SceneKit
class SceneKitScene: SCNScene {

    override init() {
        super.init()

        let box = SCNBox(width: 10, height: 10, length: 10, chamferRadius: 0)
        let material = SCNMaterial()
        material.diffuse.contents = UIColor.green
        box.materials = [material]

        let boxNode = SCNNode(geometry: box)
        boxNode.transform = SCNMatrix4MakeRotation(.pi/2, 1, 1, 1)

        self.rootNode.addChildNode(boxNode)
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

2。 SpriteKit 场景

import SpriteKit
class SpriteKitScene: SKScene {

    override init(size: CGSize) {
        super.init(size: size)

        // Scene Background
        self.backgroundColor = .red

        // 3D Node
        let objectNode = SK3DNode(viewportSize: size)
        objectNode.scnScene = SceneKitScene()
        addChild(objectNode)
        objectNode.position = CGPoint(x: size.width/2, y: size.height/2)

        let camera = SCNCamera()
        let cameraNode = SCNNode()
        cameraNode.camera = camera
        objectNode.pointOfView = cameraNode
        objectNode.pointOfView?.position = SCNVector3(x: 0, y: 0, z: 60)
        objectNode.zPosition = -100

        // 2D Sprite
        let sprite = SKSpriteNode(color: .yellow, size: CGSize(width: 250, height: 60))
        sprite.position = objectNode.position
        sprite.zPosition = +100
        addChild(sprite)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

...而渲染结果是:

(我想要黄色矩形绿色框上方)

【问题讨论】:

    标签: ios sprite-kit scenekit


    【解决方案1】:

    我就此向 Apple 提出了技术支持事件,他们刚刚回复了我。解决方案其实非常非常简单。

    如果您希望 2D 精灵在 SK3DNodes 之上渲染,您需要阻止 SK3DNodes 的内容写入深度缓冲区。


    为此,您只需将SCNMaterial 上的writesToDepthBuffer 设置为false

    ...
    
    let material = SCNMaterial()
    material.diffuse.contents = UIColor.green
    material.writesToDepthBuffer = false
    
    ...
    

    轰隆隆。有效。

    【讨论】:

    • 谢谢,很棒的发现!我想 this 应该是公认的答案。这应该在 SK3DNode 的文档中的某个地方。我想如果我事先彻底阅读了 SCNMaterial 文档,我可能已经弄明白了。
    • 但我想知道这样做是否不会妨碍 SK3DNode 渲染的各种 3D 对象之间执行的深度测试。
    • 来自 Apple 工程师的后续跟进:“如果您对此类渲染问题在幕后可能发生的事情感到好奇,您可以执行 GPU Frame Capture 以了解可能发生了什么。在这种情况下,您会看到 SK3DNode 内容正在写入深度缓冲区,但其他 SK 内容没有,所以无论您的精灵的 z 位置是什么,SK3DNode总是“处于领先地位”。”
    • 基于the docs for writesToDepthBuffer,它似乎不会影响 3D 对象之间的绘制顺序,但它可能会影响性能,因为它会查看“深度关于它们背后不透明物体的信息。” Aka 它可能会做更多的工作?但如果深度缓冲区中没有任何内容,那可能没问题?
    【解决方案2】:

    请注意,这只是我偶然发现的。我不知道它为什么会起作用,如果没有进一步了解,我可能不会相信它,但也许它会帮助找到解释或真正的解决方案。


    似乎将SKShapeNode(带填充)与SK3DNode(作为同级、同级树的一部分或子级)一起以正确的顺序绘制。 SKShapeNode 似乎也不需要与 SK3DNode 相交。

    填充很重要,因为使用透明填充会使这不起作用。中风似乎没有任何影响。

    SKShapeNode 尺寸极小且 Alpha 填充几乎为零。

    这是我的游乐场:

    import PlaygroundSupport
    import SceneKit
    import SpriteKit
    
    let viewSize = CGSize(width: 300, height: 150)
    let viewportSize: CGFloat = viewSize.height * 0.75
    
    func skview(color: UIColor, index: Int) -> SKView {
        let scene = SKScene(size: viewSize)
        scene.backgroundColor = color
    
        let view = SKView(
            frame: CGRect(
                origin: CGPoint(x: 0, y: CGFloat(index) * viewSize.height),
                size: viewSize
            )
        )
        view.presentScene(scene)
        view.showsDrawCount = true
    
        // Draw the box of the 3d node view port
        let viewport = SKSpriteNode(color: .orange, size: CGSize(width: viewportSize, height: viewportSize))
        viewport.position = CGPoint(x: viewSize.width / 2, y: viewSize.height / 2)
        scene.addChild(viewport)
    
        return view
    }
    
    func cube() -> SK3DNode {
        let mat = SCNMaterial()
        mat.diffuse.contents = UIColor.green
    
        let box = SCNBox(width: viewportSize, height: viewportSize, length: viewportSize, chamferRadius: 0)
        box.firstMaterial = mat
    
        let boxNode3d = SCNNode(geometry: box)
        boxNode3d.runAction(.repeatForever(.rotateBy(x: 10, y: 10, z: 10, duration: 10)))
    
        let scene = SCNScene()
        scene.rootNode.addChildNode(boxNode3d)
    
        let boxNode2d = SK3DNode(viewportSize: CGSize(width: viewportSize, height: viewportSize))
        boxNode2d.position = CGPoint(x: viewSize.width / 2, y: viewSize.height / 2)
        boxNode2d.scnScene = scene
    
        return boxNode2d
    }
    
    func shape() -> SKShapeNode {
        let shape = SKShapeNode(rectOf: CGSize(width: viewSize.height / 4, height: viewSize.height / 4))
        shape.strokeColor = .clear
        shape.fillColor = .purple
        return shape
    }
    
    func rect(_ color: UIColor) -> SKSpriteNode {
        let sp = SKSpriteNode(texture: nil, color: color, size: CGSize(width: 200, height: viewSize.height / 4))
        sp.position = CGPoint(x: viewSize.width / 2, y: viewSize.height / 2)
        return sp
    }
    
    // The original issue, untouched.
    func v1() -> SKView {
        let v = skview(color: .red, index: 0)
    
        v.scene?.addChild(cube())
        v.scene?.addChild(rect(.yellow))
    
        return v
    }
    
    // Shape added as sibling after the 3d node. Notice that it doesn't overlap the SK3DNode.
    func v2() -> SKView {
        let v = skview(color: .blue, index: 1)
    
        v.scene?.addChild(cube())
        v.scene?.addChild(shape())
        v.scene?.addChild(rect(.yellow))
    
        return v
    }
    
    // Shape added to the 3d node.
    func v3() -> SKView {
        let v = skview(color: .magenta, index: 2)
    
        let box = cube()
        box.addChild(shape())
    
        v.scene?.addChild(box)
        v.scene?.addChild(rect(.yellow))
    
        return v
    }
    
    // 3d node added after, but zPos set to -1.
    func v4() -> SKView {
        let v = skview(color: .cyan, index: 3)
    
        v.scene?.addChild(shape())
        v.scene?.addChild(rect(.yellow))
    
        let box = cube()
        box.zPosition = -1
        v.scene?.addChild(box)
    
        return v
    }
    
    // Shape added after the 3d node, but not as a sibling.
    func v5() -> SKView {
        let v = skview(color: .green, index: 4)
    
        let parent = SKNode()
        parent.addChild(cube())
        parent.addChild(rect(.yellow))
    
        v.scene?.addChild(parent)
        v.scene?.addChild(shape())
    
        return v
    }
    
    let container = UIView(frame: CGRect(origin: .zero, size: CGSize(width: viewSize.width, height: viewSize.height * 5)))
    container.addSubview(v1())
    container.addSubview(v2())
    container.addSubview(v3())
    container.addSubview(v4())
    container.addSubview(v5())
    
    PlaygroundPage.current.liveView = container
    


    TL;DR

    在您的代码中,尝试:

    ...
    
    let shape = SKShapeNode(rectOf: CGSize(width: 0.01, height: 0.01))
    shape.strokeColor = .clear
    shape.fillColor = UIColor.black.withAlphaComponent(0.01)
    
    // 3D Node
    let objectNode = SK3DNode(viewportSize: size)
    objectNode.addChild(shape)
    
    ...
    

    【讨论】:

    • 非常感谢您的详细解答。最后,我将我的应用程序迁移到 SceneKit,并使用 SCNView 的 overlaySKScene 属性覆盖的 SKScenes 实现了所有 GUI(SK3DNode 还对您可以渲染的视觉效果施加了限制)。
    • 听起来不错,我也在考虑走那条路。我对使用这个 hack 感觉不舒服,哈哈。幸运的是,我只将 UIKit 用于我的 GUI,所以这不是问题,但我需要在游戏中的一些 3D 对象上渲染精灵。也许知道发生了什么的人会看到这一点并有所了解。对于 2D 大型项目,您是否注意到 SceneKit 有任何明显的缺点?
    • 我遇到的唯一一个问题是,当您的场景是 SceneKit 视图覆盖时,您不能再使用 SpriteKit 场景转换。我在 Medium 上写了一篇文章:medium.com/macoclock/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多