无法在 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
...
繁荣。有效。
我在我的 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 theSK3DNodes
from writing to the depth buffer.
为此,您只需将 SCNMaterial
上的 writesToDepthBuffer
设置为 false
。
...
let material = SCNMaterial()
material.diffuse.contents = UIColor.green
material.writesToDepthBuffer = false
...
繁荣。有效。