I am showing a 3D object in SwiftUI, and I have a problem with making the object's background clear. I have searched and did not find any solution. Is there any solution for this?
private var scene: SCNScene? {
SCNScene(named: "Globe.scnassets/sphere.scn")
}
private var cameraNode: SCNNode? {
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: 0, y: 0, z: 5)
return cameraNode
}
var body: some View {
SceneView(scene: scene,
pointOfView: cameraNode,
options: [.allowsCameraControl, .autoenablesDefaultLighting])
}
There are, at least, 3 ways to programmatically change a BG in SwiftUI's SceneView.
Default white background
Changing SCNScene's background color
import SwiftUI
import SceneKit
struct ContentView: View {
var scene = SCNScene(named: "model.usdz")
var options: SceneView.Options = [.autoenablesDefaultLighting,
.allowsCameraControl ]
var body: some View {
ZStack {
SceneView(scene: scene, options: options)
.ignoresSafeArea()
let _ = scene?.background.contents = UIColor.black
}
}
}
Using textured double-sided SCNGeometry
struct ContentView: View {
var scene = SCNScene(named: "model.usdz")
var options: SceneView.Options = [.autoenablesDefaultLighting,
.allowsCameraControl ]
let node = SCNNode(geometry: SCNSphere(radius: 500.0))
let img = UIImage(named: "image.jpg")
var body: some View {
ZStack {
let _ = node.geometry?.firstMaterial?.diffuse.contents = img
let _ = node.geometry?.firstMaterial?.isDoubleSided = true
let _ = scene?.rootNode.addChildNode(node)
SceneView(scene: scene, options: options)
.ignoresSafeArea()
}
}
}
Using Procedural Sky Box Texture
You can use MDLSkyCubeTexture as background and lightingEnvironment.
I have found a solution via SceneKit editor, if you want to show an image as background, you can manually do it by going to:
Scene Inspector -> Background and lighting -> Background
and set the image as background:
Related
For performance reasons I have to switch from SceneView to SpriteView in my macOS project (showing more than 63 scenes did not work with SceneView, but it does with SpriteView).
But now im facing an issue that SpriteView is rendering colors differently than SceneView. Below is a simple reproduction of the issue I am facing.
I have tried a multitude of material and lighting options, but I seem to miss something more fundamental. Help is very much appreciated.
var body: some View {
HStack {
// SpriteView
SpriteView(scene: { () -> SKScene in
let scene = SKScene()
scene.backgroundColor = .white
scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let node = SK3DNode()
node.scnScene = self.sphereScene
scene.addChild(node)
return scene
}())
// SceneView
SceneView(scene: sphereScene,
options: [.autoenablesDefaultLighting])
}
}
var sphereScene: SCNScene {
let scnScene = SCNScene()
let ballGeometry = SCNSphere(radius: 5)
let ballNode = SCNNode(geometry: ballGeometry)
let material = SCNMaterial()
material.diffuse.contents = NSColor.purple
material.lightingModel = .physicallyBased
ballGeometry.materials = [material]
scnScene.rootNode.addChildNode(ballNode)
return scnScene
}
You're absolutely right: SpriteKit processes SceneKit's scenes differently than SceneKit. It's visually noticeable that the lighting intensity, blurring of highlights and decolorization of edges with 90 degree reflection are different. The main tool that can be advised in this case is the use of Ambient Light to additionally illuminate the SpriteKit scene based on the SceneKit content. You should turn a default lighting off (in order to get rid of colorization artifacts) and use regular lights. Here I used directional light.
SpriteView:
import SwiftUI
import SceneKit
import SpriteKit
struct SpriteView: NSViewRepresentable {
var scene = SKScene()
func makeNSView(context: Context) -> SKView {
let skView = SKView(frame: .zero)
skView.presentScene(scene)
scene.backgroundColor = .black
return skView
}
func updateNSView(_ uiView: SKView, context: Context) { }
}
ContentView:
struct ContentView: View {
var body: some View {
ZStack {
HStack {
SpriteView(scene: { () -> SKScene in
let scene = SKScene()
scene.anchorPoint = CGPoint(x: 0.5, y: 0.5)
let ambient = SCNNode()
ambient.light = SCNLight()
ambient.light?.type = .ambient
ambient.light?.intensity = 1200
let node = SK3DNode()
node.autoenablesDefaultLighting = false
node.scnScene = self.sphereScene
node.scnScene?.rootNode.addChildNode(ambient)
scene.addChild(node)
return scene
}() )
SceneView(scene: sphereScene, options: [])
}
}
}
var sphereScene: SCNScene {
let scnScene = SCNScene()
scnScene.background.contents = NSColor.black
let ballNode = SCNNode(geometry: SCNSphere(radius: 5.0))
let directional = SCNNode()
directional.light = SCNLight()
directional.light?.type = .directional
directional.light?.intensity = 500
scnScene.rootNode.addChildNode(directional)
let material = SCNMaterial()
material.lightingModel = .physicallyBased
material.diffuse.contents = NSColor.purple
ballNode.geometry?.materials = [material]
scnScene.rootNode.addChildNode(ballNode)
return scnScene
}
}
The following worked for me, correcting saturation and brightness brought me near to the SceneKit defaultLighting appearance:
// get object and manipulate
let object = scene.rootNode.childNode(withName: "object", recursively: false)
let color = NSColor(named: "\(colorNr)")?
.usingColorSpace(.displayP3) // specify color space, important!
object?.geometry?.firstMaterial?.lightingModel = .physicallyBased
// correct color for SpriteView
let color2 = NSColor(hue: color?.hueComponent ?? 0,
saturation: (color?.saturationComponent ?? 0) * 0.55,
brightness: (color?.brightnessComponent ?? 0) * 0.55 + 0.45,
alpha: 1.0)
object?.geometry?.firstMaterial?.diffuse.contents = color2
object?.geometry?.firstMaterial?.diffuse.intensity = 0.9
object?.geometry?.firstMaterial?.roughness.contents = 0.9
iOS 15, Swift 5
I am trying to get sceneView [SwiftUI SceneKit plugin] to work with adding/finding nodes within my scene. And I feel I am almost there and yet I am not.
Sadly the hits function here finds nothing, which is why I ended up commenting it out. I tried used projectPoint after that, but the results make no sense?
Now I did get this working using UIViewRespresentable, but that solution doesn't use sceneView at all.
I want to convert the drag coordinates to the SCNView coordinates since since I think I can find/match objects.
import SwiftUI
import SceneKit
struct ContentView: View {
let settings = SharedView.shared
var (scene,cameraNode) = GameScene.shared.makeView()
var body: some View {
GameView(scene: scene, cameraNode: cameraNode)
}
}
struct GameView: View {
let settings = SharedView.shared
#State var scene: SCNScene
#State var cameraNode: SCNNode
var body: some View {
SceneView(
scene: scene,
pointOfView: cameraNode,
options: [.autoenablesDefaultLighting, .rendersContinuously ], delegate: SceneDelegate())
.gesture(DragGesture(minimumDistance: 0)
.onChanged({ gesture in
settings.location = gesture.location
}).sequenced(before: TapGesture().onEnded({ _ in
GameScene.shared.tapV()
}))
)
}
}
class SharedView: ObservableObject {
#Published var location:CGPoint!
#Published var view:UIView!
#Published var scene:SCNScene!
#Published var sphereNode:SCNNode!
static var shared = SharedView()
}
#MainActor class SceneDelegate: NSObject, SCNSceneRendererDelegate {
}
class GameScene: UIView {
static var shared = GameScene()
let settings = SharedView.shared
var view: SCNView!
var scene: SCNScene!
func makeView() -> (SCNScene,SCNNode) {
let material = SCNMaterial()
material.diffuse.contents = UIColor.red
let sphere = SCNSphere(radius: 1.0)
sphere.materials = [material]
let sphereNode = SCNNode()
sphereNode.geometry = sphere
sphere.name = "sphereNode"
let camera = SCNCamera()
camera.fieldOfView = 90.0
let light = SCNLight()
light.color = UIColor.white
light.type = .omni
let cameraNode = SCNNode()
cameraNode.simdPosition = SIMD3<Float>(0.0, 0.0, 6)
cameraNode.camera = camera
cameraNode.light = light
scene = SCNScene()
scene.background.contents = UIColor.black
scene.rootNode.addChildNode(sphereNode)
scene.rootNode.addChildNode(cameraNode)
view = SCNView()
view.scene = scene
view.pointOfView = cameraNode
settings.sphereNode = sphereNode
return (scene, cameraNode)
}
func tapV() {
let location = settings.location
let hits = view.hitTest(location!, options: [.boundingBoxOnly: true, .firstFoundOnly: true, .searchMode: true])
print("hits \(hits.count)")
// for hit in hits {
let material = SCNMaterial()
material.diffuse.contents = UIColor.yellow
let geometry = SCNSphere(radius: 0.1)
geometry.materials = [material]
let node = SCNNode()
node.geometry = geometry
// node.simdPosition = hit.simdWorldCoordinates
let projectedOrigin = view.projectPoint(SCNVector3Zero)
var worldPoint = view.unprojectPoint(SCNVector3(location!.x, location!.y, CGFloat(0)))
node.worldPosition = worldPoint
view.scene!.rootNode.addChildNode(node)
print("node \(node.position) \(hits.count)")
// }
}
}
Swift 5.x iOS 15
Trying to understand SceneKit, but I am stuck. I created a scene and display it with SwiftUI. But when I try and run a function with that class the scene is defined it does nothing. When I tap on the label "push" I want the ball to jump in the air. Nothing happens.
import SwiftUI
import SceneKit
struct ContentView: View {
var body: some View {
let scene = GameScene()
Text("push")
.onTapGesture {
let foo = SharedView.shared
print("push ",foo.xCord,foo.yCord,foo.zCord)
scene.printMe()
}
}
}
class SharedView: ObservableObject {
#Published var xCord:Float = 0
#Published var yCord:Float = 0
#Published var zCord:Float = 0
#Published var xCord2:Float = 0
#Published var yCord2:Float = 1.8
#Published var zCord2:Float = 0
static var shared = SharedView()
}
...
class SceneDelegate: NSObject, SCNSceneRendererDelegate {
let foo = SharedView.shared
func renderer(_ renderer: SCNSceneRenderer,
updateAtTime time: TimeInterval) {
print("running!!")
}
static var shared = SceneDelegate()
}
struct GameScene: View {
// #ObservedObject var foo = SharedView.shared
#StateObject var foo = SharedView.shared
#State var scene = SCNScene(named: "MyScene.scn")
#State var cameraNode = SCNNode()
#State var ball = SCNNode()
#State var box = SCNNode()
var body: some View {
scene?.isPaused = false
self.scene?.rootNode.enumerateChildNodes({ (node, _ ) in
if node.name == "ball" {
Task {
ball = node
ball.physicsBody = SCNPhysicsBody(type: .dynamic, shape: SCNPhysicsShape(node: ball, options: [:]))
ball.physicsBody?.isAffectedByGravity = true
ball.physicsBody?.restitution = 1.2
}
}
if node.name == "box" {
Task {
box = node
box.physicsBody = SCNPhysicsBody(type: .static, shape: SCNPhysicsShape(node: box, options: [:]))
box.physicsBody?.isAffectedByGravity = false
box.physicsBody?.restitution = 1
}
}
})
cameraNode.camera = SCNCamera()
cameraNode.position.x = foo.xCord
cameraNode.position.y = foo.yCord
cameraNode.worldPosition.z = foo.zCord
cameraNode.eulerAngles = SCNVector3(foo.xCord2,foo.yCord2,foo.zCord2)
cameraNode.camera = SCNCamera()
let sceneView = SceneView(
scene: scene,
pointOfView: cameraNode,
options: [.autoenablesDefaultLighting,.allowsCameraControl], delegate: SceneDelegate.shared)
let dummyNode = scene!.rootNode.childNode(withName: "DummyNode", recursively: false)
dummyNode?.position = SCNVector3(0, 0, 0)
return sceneView
.frame(width: 1024, height: 256, alignment: .center)
}
func printMe() {
print(cameraNode.eulerAngles, cameraNode.position)
ball.physicsBody?.applyForce(SCNVector3(0,10,0), asImpulse: true)
}
}
I have a Scenekit scene in swift UI, I made the scene a UIViewRepresentable, how can I zoom in on one of the nodes of the scene when the user touches that specific node in the Scenekit scene?
import SceneKit
struct HouseView : UIViewRepresentable {
func makeUIView(context: Context) -> SCNView {
return SCNView(frame: .zero)
}
func updateUIView(_ scnView: SCNView, context: Context) {
let HouseScene = SCNScene(named: "House.scn")
scnView.scene = HouseScene
// allows the user to manipulate the camera
scnView.allowsCameraControl = true
// show statistics such as fps and timing information
scnView.showsStatistics = false
scnView.backgroundColor = UIColor.systemBackground
scnView.defaultCameraController.maximumVerticalAngle = 10
scnView.defaultCameraController.minimumVerticalAngle = -10
scnView.defaultCameraController.maximumHorizontalAngle = 180
scnView.defaultCameraController.minimumHorizontalAngle = -10
scnView.isJitteringEnabled = true
let CameraNode = HouseScene?.rootNode.childNode(withName: "CameraNode", recursively: true)
CameraNode?.position = SCNVector3(x: 12, y:2, z: 0)
}
}
struct HouseView_Previews: PreviewProvider {
static var previews: some View {
HouseView()
}
}`''
Please see this post: 54058938
Create a basic camera class and you can focus on the node touched and set the distance away from it.
I want to know how to add 3D shapes (eg. Sphere) in swift UI
I have tried adding a scnscene and using that in swift UI but I get a error message
//SceneKit
class myscene: SCNScene{
override init(){
super.init()
}
required init?(coder: NSCoder) {
fatalError("init(coder: ) has not been implemented")
}
}
//Swift UI
struct ContentView: View {
var body: some View {
let sphere = SCNSphere(radius: 2.0)
sphere.firstMaterial?.diffuse.contents = UIColor.blue
let spherenode = SCNNode(geometry: sphere)
spherenode.position = SCNVector3(x: 0.0, y: 3.0, z: 0.0)
}
}
The error message is on var body: some View { line and reads as follows :
Function declares an opaque return type, but has no return statements in its body from which to infer an underlying type
Please help me with this problem......
Here is simplest code to demo how to setup SceneKit scene with your sphere. Hope it helps.
import SwiftUI
import SceneKit
struct SceneKitView: UIViewRepresentable {
func makeUIView(context: UIViewRepresentableContext<SceneKitView>) -> SCNView {
let sceneView = SCNView()
sceneView.scene = SCNScene()
sceneView.allowsCameraControl = true
sceneView.autoenablesDefaultLighting = true
sceneView.backgroundColor = UIColor.black
let sphere = SCNSphere(radius: 2.0)
sphere.firstMaterial?.diffuse.contents = UIColor.blue
let spherenode = SCNNode(geometry: sphere)
spherenode.position = SCNVector3(x: 0.0, y: 3.0, z: 0.0)
sceneView.scene?.rootNode.addChildNode(spherenode)
return sceneView
}
func updateUIView(_ uiView: SCNView, context: UIViewRepresentableContext<SceneKitView>) {
}
typealias UIViewType = SCNView
}
struct DemoSceneKit: View {
var body: some View {
SceneKitView()
}
}
struct DemoSceneKit_Previews: PreviewProvider {
static var previews: some View {
DemoSceneKit()
}
}