Comment bien démarrer avec SceneKit

Emmanuel Orvain
7 min readJun 29, 2018

Sur l’AppStore, une multitude de jeux font chaque jour leur apparition. Ces applications sont basées sur les moteurs 3D. Le framework ARKit a le vent en poupe. Il est disponible depuis 2017, et il est fortement dépendant de SceneKit. ARKit permet le développement d’applications de réalité augmenté sur iOS. Il est donc important de bien comprendre SceneKit avant d’utiliser ARKit.

Il est tentant de démarrer avec cette approche :

Je vais faire quelques tutos, et rapidement je saurais maîtriser SceneKit

Mais très vite, on se retrouve confronté à des manipulations de repère 3D, et on passe plusieurs heures à s’arracher les cheveux pour appliquer une rotation à un modèle 3D.

Le but de cet article est de faire un tour d’horizon des objets fondamentaux de SceneKit.

Ici, le but n’est pas de proposer un autre tutoriel sur SceneKit, il en existe des très bien.

https://www.raywenderlich.com/category/apple-game-frameworks/agf-scenekit

SCNView

SceneKit est compatible avec les SDK iOS et MacOS, à une exception près, la SCNView.

La SCNView est l’objet qui lie SceneKit et UIKit. Il hérite de la classe UIView, donc du système de layout d’UIKit. Sur MacOS, la SCNView hérite de NSView.

Le graphe de scène n’est pas contenu directement dans la SCNView, mais il y accède grâce à sa propriété scene.

// retrieve the SCNView
let scnView = self.view as! SCNView
// set the scene to the view
scnView.scene = scene
La SCNView depuis Interface Builder

SCNScene

La scène est l’objet qui lie la vue au graphe de scène. Le graphe de scène contient essentiellement une arborescence de SCNNode.

Dans ce type d’application, il n’existe pas de dimension (mètre, pieds, kilomètre, …). Toutes les valeurs sont scalaires. Attention toutefois, lorsqu’on utilise ARKit, on mixe le monde virtuel et le monde physique. ARKit utilise des données avec des dimensions physiques (mètres, centimètres,…) et il faudra veiller à ce que la scène virtuelle et les données d’ARKit coïncident.

La scène contient la propriété rootNode. Le root node est fondamental, il représente le centre de la scène, et quoi qu’il arrive dans votre graphe de scène, on peut lui faire confiance. Il nous indiquera toujours où est le centre de la scène.

Dans l’image suivante, on peut voir une représentation du root node au centre de l’image. Par convention historique, les couleurs ont une signification :

  1. l’axe rouge est l’axe des X
  2. l’axe vert est l’axe des Y
  3. L’axe bleu est l’axe des Z
Le root node est au centre de l’image

SCNNode

De la même manière qu’UIKit permet de créer une hiérarchie de vue 2D, SceneKit permet de créer une hiérarchie de node 3D. Une node peut avoir un unique parent, mais peut posséder plusieurs enfants.

L’objectif premier de la SCNNode est de décrire sa position, son orientation et le scale par rapport à son node parent.

On peut croire qu’une node apparaîtra forcément à l’écran mais non. Pour qu’une node apparaisse à l’écran, il faut lui attacher une géométrie. Il n’est pas obligatoire d’attacher de la géométrie à une node. On peut par exemple s’en servir comme point de pivot pour appliquer une animation à sa hiérarchie descendante.

Au niveau du silicone, une node est représentée par une matrice 4x4. Pour faire simple, le GPU, il adore ce type de structure, il ne veut que ça, et il est le meilleur pour ça.

Rencontre du 4x4 type

La matrice 4x4 décrit à la fois :

  1. sa position par rapport au node parent
  2. son orientation par rapport au node parent
  3. son scale par rapport au node parent

La propriété qui décrit cela est transform, qu’il faut lire comme “Transformation par rapport au parent”. Mais pas de panique, il existe des méthodes utilitaires pour appliquer des transformations de base à nos matrices 4x4. Lors de la création, une node est initialisée avec une matrice identité, à ce moment leur repère coïncide.

L’exemple suivant, montre comment créer une node fille, à laquelle on applique une translation par rapport au root node.

let tutoNode = SCNNode.debugNode()
tutoNode.position = SCNVector3Make(3, 0, 2)
scene.rootNode.addChildNode(tutoNode)
Nouvelle node

SCNCamera

La caméra est l’objet par lequel on donne la possibilité à l’utilisateur de visualiser la scène 3D. Pour créer une caméra, il suffit d’associer une SCNCamera à l’attribut caméra d’une node.

let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()

Pour que les choses restent compliquées :

  • L’axe de vision de la caméra est l’axe -Z de la node à laquelle elle est rattachée.
  • Sa verticale (vecteur up) est l’axe Y (en vert) de la node à laquelle elle est rattachée.

SCNLight

De la même manière que pour la SCNCamera, une SCNLight est attachée à une SCNNode et hérite donc de sa matrice de transformation.

// create and add an ambient light to the scene
let ambientLightNode = SCNNode()
ambientLightNode.light = SCNLight()
ambientLightNode.light!.type = .ambient
ambientLightNode.light!.color = UIColor.darkGray
scene.rootNode.addChildNode(ambientLightNode)

Il existe plusieurs types de lumières :

  • La lumière ambiante
  • La lumière directionnelle
  • La lumière omni directionnelle
  • Le spot

Attention, suivant le type de lumière, la matrice de transformation va affecter ou non l’éclairage de la scène.

  • La lumière ambiante peut être assimilée à une source de lumière qui se trouve partout et qui éclaire dans toutes les directions. Sa position et son orientation ne vont donc pas modifier l’éclairage de la scène 3D.
  • La lumière directionnelle, est une lumière positionnée à l’infini et qui va éclairer une scène suivant une direction (On peut considérer que le soleil est une lumière directionnelle sur la Terre par exemple). C’est donc uniquement l’orientation de la node associée qui va affecter l’éclairage de la scène.
  • La lumière omni directionnelle, est une lumière placée en un point précis de l’espace et qui éclaire dans toutes les directions. On peut considérer qu’une ampoule est une source omni-directionelle. C’est donc uniquement sa position qui va déterminer l’éclairage de la scène.
  • Le spot de lumière, est une lumière avec une position précise et un angle précis. Elle se rapproche du phare de mer ou de la lumière frontale. C’est sa position et son orientation qui vont déterminer l’éclairage qu’elle apporte à la scène.

Les types de lumières qui sont sensibles à la position de la node, vont aussi être sensibles aux paramètres d’atténuation de la lumière.

Plusieurs lumières peuvent être combinées dans une scène, mais il faut veiller à rester raisonnable, car chaque lumière demande un rendu de la scène et va donc directement impacter les performances.

Ok tout ça, mais concrètement, comment j’affiche des objets 3D ?

SCNGeometry

Parce qu’on ne change pas un système qui gagne, on applique la même mécanique… Une SCNGeometry est attachée à une SCNNode. Il peut même être attaché à plusieurs nodes pour des gains de performance.

let plane = SCNNode()
let geometry = SCNPlane(width: 16, height: 8)

SceneKit propose plusieurs types de primitives qui héritent de SCNGeometry :

  1. SCNPlane
  2. SCNBox
  3. SCNSphere
  4. SCNPyramid
  5. SCNCone
  6. SCNCylinder
  7. SCNCapsule
  8. SCNTube
  9. SCNTorus

La première fois qu’on utilise de la géométrie, il peut y avoir de la confusion entre le repère de la géométrie et le repère de la node associée. Pour bien comprendre ce qui se passe, au moment où on attache la géométrie à un node, la node devient le repère origine de la géométrie. Autrement dit, il devient le centre de la scène de votre éditeur 3D (Blender). Si la bounding box de la géométrie est centrée en 0,0,0 dans votre éditeur 3D, alors la géométrie de votre modèle importé apparaîtra bien au centre de la node.

De ce fait, dès que la node subira une transformation (translation, rotation, scale), c’est toute sa géométrie qui va suivre.

Par exemple un SCNPlane d’une largeur de 10 et d’une hauteur de 20.

  • aura une dimension 10 selon l’axe X de la node associée.
  • aura une dimension 20 selon l’axe Y de la node associée.

Tour cela est expliqué dans la doc du SDK, il n’y a pas de magie :

A plane defines a flat surface in the x- and y-axis dimensions of its local coordinate space according to its width and height properties.

Nous pouvons également importer des modèles 3D que nos talentueux amis infographistes 3D auront réalisé pour notre projet.

Il est possible d’importer des objets 3D DAE, OBJ, …

Il faut toutefois rester vigilant, car le système de coordonnées du modèle 3D n’est pas forcément celui attendu. Il faudra soit passer par des transformations de la node, soit demander à l’infographiste un export qui convienne mieux.

let path = Bundle.main.path(forResource: “model.scnassets/luigi_textured_obj”, ofType: “obj”)
let url = URL(string: path!)
let marioPack = MDLAsset(url: url!)
let mario = marioPack.object(at:0)
let node = SCNNode.init(mdlObject:mario)
node.scale = SCNVector3Make(0.05, 0.05, 0.05)
scene.rootNode.addChildNode(node)

Voilà, vous en savez maintenant davantage sur le Framework SceneKit, et ses objets fondamentaux. Maintenant c’est à vous de jouer.

If you enjoyed this post, please leave some claps. 👏👏👏

You can clap up to 50 times, so get clicking/tapping! 😉

--

--

Emmanuel Orvain

French iOS / Android experimented developper from Toulouse in south of France. https://occirama.com/scanandfile/