无法在 SK3DNode 上方显示精灵

Cannot Display Sprites Above SK3DNode

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

我已经能够使用 SceneKit 节点变换和 SK3DNode 的 position/viewport 大小来根据需要定位节点和 3D 内容。

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

我试过指定 SK3DNode 和 SKSpriteNode 的 zPosition 属性,但没有用。

来自 Apple 关于 SK3DNode 的文档:

Use SK3DNode objects to incorporate 3D SceneKit content into a SpriteKit-based game. When SpriteKit renders the node, the SceneKit scene is animated and rendered first. Then this rendered image is composited into the SpriteKit scene. Use the scnScene property to specify the SceneKit scene to be rendered.

(强调我的)

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

我整理了一份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")
    }
}

...渲染结果是:

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

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


似乎有一个 SKShapeNode(带有填充)和一个 SK3DNode(作为同级,同级树的一部分,或 child),将其绘制在正确的顺序。 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)

...

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

If you want 2D sprites to render on top of SK3DNodes, you need to stop the contents of the SK3DNodes from writing to the depth buffer.


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

...

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

...

繁荣。有效。