Change texture image for 3d model - swift

I'm investigating iOS and trying to understand is it possible to change the texture of the model using any other picture?
Let's say I have model.obj with related texture green.png which is applied to this model so the appearance of the product is green. Is it possible to choose any other image, for example, blue.png, and apply it programmatically in runtime to 3d model and make the appearance of the product blue?
I have one working example
override func viewDidLoad() {
super.viewDidLoad()
let node = SCNNode()
let geometry = SCNSphere(radius: 0.2)
node.position = SCNVector3(0, 0, -1)
sceneView.backgroundColor = .black
sceneView.scene = SCNScene()
node.geometry = self.geometry
let image = UIImage(named: "art.scnassets/green.jpeg")
print(image)
node.geometry?.firstMaterial?.diffuse.contents = image
sceneView.scene.rootNode.addChildNode(node)
}
But when I try apply image to uploaded 3d model it's appearance doesn't change, here is a code.
override func viewDidLoad() {
super.viewDidLoad()
let tempScene = SCNScene(named: "art.scnassets/California_chair_1.obj")!
let node = tempScene.rootNode
node.position = SCNVector3(0, 0, -1)
sceneView.scene = SCNScene()
let image = UIImage(named: "art.scnassets/green.jpeg")
print(image)
/**/
let material = SCNMaterial()
material.isDoubleSided = false
material.diffuse.contents = image
node.geometry?.materials = [material]
/**/
node.geometry?.firstMaterial?.diffuse.contents = image
sceneView.scene.rootNode.addChildNode(node)
}
How to apply image to any 3d model created by designer?
Many thanks for any help!

For retrieving a model from SCNScene, you may use subscript .childNodes[0] several times to get to geometry and its corresponding materials in hierarchy.
import ARKit
class ViewController: UIViewController {
#IBOutlet var sceneView: ARSCNView!
let node = SCNNode()
override func viewDidLoad() {
super.viewDidLoad()
sceneView.scene = SCNScene(named: "art.scnassets/California_chair.scn")!
sceneView.autoenablesDefaultLighting = true
node = sceneView.scene.rootNode.childNode(withName: "firstChair",
recursively: true)
let green = UIColor.green
node?.childNodes[0].geometry?.firstMaterial?.diffuse.contents = green
}
#IBAction func changeTexture(_ sender: UIButton) {
let blue = UIImage(named: "art.scnassets/blueTexture.png")
node.geometry?.firstMaterial?.diffuse.contents = blue
}
}
Consider that SCNGeometry may be nested inside deep hierarchy:
node?.childNodes[0].childNodes[0].childNodes[0].geometry.firstMaterial?.diffuse
In Xcode's Scene graph such a nested hierarchy looks like this:
Also always check your node's size (scale), to find out if your camera is inside 3D model or not.
P.S.
In case you use obj models – use their corresponding mtl textures:
sceneView.scene = SCNScene(named: "art.scnassets/file.obj")!
let obj = sceneView.scene.rootNode.childNode(withName: "default",
recursively: true)
obj?.geometry?.firstMaterial?.diffuse.contents = UIImage(named:
"art.scnassets/file.mtl")

Related

Apply GIF to SCNPlane

Right now my Swift code creates a SCNPlane with an image called we.jpg. What I would like to do is to replace we.jpg with a ball.gif. material.diffuse.contents is where the UIImage is attached to the SCNPlane.
let planeGeometry = SCNPlane(width: 0.2, height: 0.35)
let material = SCNMaterial()
material.diffuse.contents = UIImage(named: "we.jpg")
planeGeometry.materials = [material]
You can easily replace SCNPlane's .jpg texture with .gif texture. But remember that in SceneKit there's no support for animated GIF textures.
Here's a code:
import ARKit
import SceneKit
class ViewController: UIViewController {
#IBOutlet var sceneView: ARSCNView! // in case you're using AR app
//#IBOutlet var sceneView: SCNView! // in case you're using VR app
let planeNode = SCNNode()
override func viewDidLoad() {
super.viewDidLoad()
let scene = SCNScene()
sceneView.scene = scene
sceneView.allowsCameraControl = true
planeNode.geometry = SCNPlane(width: 0.2, height: 0.35)
planeNode.position = SCNVector3(0, 0,-0.5)
sceneView.scene?.rootNode.addChildNode(planeNode)
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { // 3 sec delay
let image = UIImage(named: "texture.jpg")
self.planeNode.geometry?.firstMaterial?.diffuse.contents = image
}
// Other stuff...
}
//...and you can replace a texture with a button's click:
#IBAction func replaceTexture(_ sender: UIButton) {
let image = UIImage(named: "texture.gif")
planeNode.geometry?.firstMaterial?.diffuse.contents = image
}
}

How to show metallic and transparent textures in SceneKit?

I have trouble showing some textures in SceneKit, this is the model I would like to use:
Model in Sketchfab : https://skfb.ly/6QVTQ
The model should appear in these colors and textures in the AR environment using Scene Kit. But the golden tips appear black and the transparent lenses do not appear at all. Are there any suggestions to solve this problem?
The model is .scn format. here is the the model materials properties:
https://drive.google.com/file/d/1HIHEsyONLXyL95dcSy9xWMGX89udMPND/view?usp=sharing
https://drive.google.com/file/d/1ndrZfcjqIQ4d2OfG6ZNvwyfDXnihJbnH/view?usp=sharing
If you need any additional information please let me know.
Thank you in advance.
There's no photorealistic glass with true index of refraction (IoR) in SceneKit but you can easily create a fake one using phong shader. Phong shader also has three important properties of a glass – specularity, reflectivity, and fresnelExponent.
For metallic material use physicallyBased shading model.
Here's a code:
import SceneKit
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let sceneView = self.view as! SCNView
sceneView.scene = SCNScene(named: "glasses.scn")!
sceneView.allowsCameraControl = true
sceneView.pointOfView?.position.z = 20
let glassesFrame = sceneView.scene?.rootNode.childNode(withName: "glassesFrame",
recursively: true)
glassesFrame?.geometry?.firstMaterial?.lightingModel = .physicallyBased
glassesFrame?.geometry?.firstMaterial?.metalness.intensity = 1
glassesFrame?.geometry?.firstMaterial?.diffuse.contents = NSColor.systemBrown
let lens1 = sceneView.scene?.rootNode.childNode(withName: "rightLens",
recursively: true)
let lens2 = sceneView.scene?.rootNode.childNode(withName: "leftLens",
recursively: true)
let material = SCNMaterial()
material.lightingModel = .phong
material.diffuse.contents = NSColor(white: 0.2,
alpha: 1)
material.diffuse.intensity = 0.9
material.specular.contents = NSColor(white: 1,
alpha: 1)
material.specular.intensity = 1.0
material.reflective.contents = NSImage.Name("art.scnassets/texture.png")
material.reflective.intensity = 2.0
material.transparencyMode = .dualLayer
material.fresnelExponent = 2.2
material.isDoubleSided = true
material.blendMode = .alpha
material.shininess = 100
material.transparency.native = 0.7
material.cullMode = .back
lens1?.geometry?.firstMaterial = material
lens2?.geometry?.firstMaterial = material
}
}

SceneKit's instance property "autoenablesDefaultLighting" doesn't work

I tried to turn on and off default lighting in SCNView via .autoenablesDefaultLighting instance property but in doesn't work (Neither in UI nor programmatically).
I need all objects to be black when there's no light.
How to turn default lighting off?
Here's a code:
import SceneKit
import QuartzCore
class GameViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let scnView = SCNView(frame: NSRect(x: 0,
y: 0,
width: 450,
height: 300))
view.addSubview(scnView)
scnView.autoenablesDefaultLighting = false // DOESN'T WORK
scnView.allowsCameraControl = true
scnView.backgroundColor = NSColor.blue
let scene = SCNScene()
scnView.scene = scene
let sphereGeo = SCNSphere(radius: 2)
sphereGeo.segmentCount = 4
sphereGeo.materials.first?.diffuse.contents = NSColor.lightGray
let sphereNode = SCNNode(geometry: sphereGeo)
sphereNode.name = "Sphere Node"
scene.rootNode.addChildNode(sphereNode)
}
}
It seems it's working only when I'm using Physically Based Rendering shading model.
let material = SCNMaterial()
material.lightingModel = SCNMaterial.LightingModel.physicallyBased
sceneView.autoenablesDefaultLighting = false
If I use .physicallyBased type property for shading my models the lighting works as supposed.

Orbit Scenekit camera around specific node

I'm having a difficult time trying to figure out how to get my SceneKit camera to orbit around a specific node in my game.
If I have a single node (a ship) and a camera in my scene everything works fine. If I add an additional node (a planet) the cameras pivot point appears to change from my ship to a space between my ship and planet.
Things I've tried:
Setting a lookat constraint on my camera (set to the ship)
Settingcamera position to my ship (it will move but the pivot point
still seems to be between the two objects)
Changing the cameras pivot point
example:
class TestSceneViewController: UIViewController, SCNSceneRendererDelegate {
var scnView: SCNView = SCNView()
var scnScene: SCNScene!
var cameraNode: SCNNode!
var ship: SCNNode!
override func viewDidLoad() {
super.viewDidLoad()
setupView()
setupScene()
setupCamera()
...
func setupView() {
// scnView = self.view as! SCNView
// retrieve the SCNView
scnView = SCNView(frame: view.frame)
scnView.showsStatistics = true
view.addSubview(scnView)
scnView.allowsCameraControl = true
scnView.defaultCameraController.interactionMode = .orbitTurntable
scnView.defaultCameraController.inertiaEnabled = true
scnView.delegate = self
scnView.isPlaying = true
scnView.loops = true
}
func setupScene () {
scnScene = SCNScene()
scnView.scene = scnScene
let ships = SCNScene(named: "art.scnassets/simpleshuttle3.scn")
ship = ships!.rootNode.childNode(withName: "ship", recursively: true)
ship?.position = SCNVector3(x: 0, y: 0, z: 0)
scnScene.rootNode.addChildNode(ship!)
let planets = SCNScene(named: "art.scnassets/sphere.scn")!
if let planet = planets.rootNode.childNode(withName: "Ball", recursively: true){
planet.position = SCNVector3(x: 0, y: 0, z: 40)
scnScene.rootNode.addChildNode(planet)
}
}
func setupCamera() {
cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3(x: ship.position.x, y: ship.position.y, z: 80)
cameraNode.camera?.motionBlurIntensity = 1.0
cameraNode.camera?.automaticallyAdjustsZRange = true
scnScene.rootNode.addChildNode(cameraNode)
}
You are enabling the manual camera control.
scnView.allowsCameraControl = true
If you want to use e.g. SCNLookAtConstraint you have to disable that. Otherwise you have a conflict. The camera is not supposed to point at a certain position while simultaneously being rotated by the user.
If you want to stay with the default camera controller you can create an additional SCNNode as the parent for your planet and the ships. This parent node can now be moved so that the pivot point is at your desired position.

Not able to add texture image to a cube in ARKit

I am not able to add an image to the cube in ARKit using the "Material" object.
Here is the code:
import UIKit
import SceneKit
import ARKit
class SimpleBoxViewController: UIViewController, ARSCNViewDelegate {
var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
self.sceneView = ARSCNView(frame: self.view.frame)
self.view.addSubview(self.sceneView)
sceneView.delegate = self
sceneView.showsStatistics = true
let scene = SCNScene()
let box = SCNBox(width: 0.2, height: 0.2, length: 0.2, chamferRadius: 0)
let material = SCNMaterial()
//This is not working
material.diffuse.contents = UIImage(named: "<someImage>.png")
let node = SCNNode()
node.geometry = box
node.geometry?.materials = [material]
node.position = SCNVector3(0, -0.1, -0.5)
scene.rootNode.addChildNode(node)
sceneView.scene = scene
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let configuration = ARWorldTrackingConfiguration()
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Pause the view's session
sceneView.session.pause()
}
I tried to add various different images but nothing works. The only image that works is the image named "textures.png" which is preloaded into an ARKit project.
Is there a specific requirement for an image to be to loaded?
I'm not 100% sure on this one, but the issue might be with including the .png with the imageName, since this image should be in your Assets folder.
Anyway, this code is working for me and tries do to the same thing with regards to creating the cube with an image.
var box = SCNBox(width: pd.width, height: pd.height, length: 0.01,
chamferRadius: 0.0)
var imageMaterial = SCNMaterial()
var image = UIImage(named: "image")
imageMaterial.diffuse.contents = image
box.materials = [imageMaterial, imageMaterial, imageMaterial, imageMaterial, imageMaterial, imageMaterial]
var cube = SCNNode(geometry: box)
you have to add the path like:
material.diffuse.contents = UIImage(named: "art.scnassets/textur")
this works for me.
I was also facing the same issue. I have copied the png files into Assets.xcassets folder and its works for me.