SceneKit: écrire un mot sur chaque face d'un cube


Je peux t'aider avec Scene Kit, j'en ai une grosse expérience.



 


Je vais profiter de cette magnifique expérience pour poser une petite question que tu vas surement trouvé trivial.


 


Est il possible sur un cube (par exemple), d'écrire un mot (genre de coller un label) sur chaque face ? ::)

«1

Réponses

  • CéroceCéroce Membre, Modérateur
    février 2017 modifié #2
    Pas vraiment testé (SceneKit avec les Playgrounds, c'est toujours pas ça), mais ça doit fonctionner:
     
    import SceneKit
     
    // sceneView est une IBOutlet SCNView
    sceneView.autoenablesDefaultLighting = true
    sceneView.allowsCameraControl = true
     
    let scene = SCNScene()
    sceneView.scene = scene
     
    let boxGeo = SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0)
     
    // Les images sont des UIImages.
    // On peut aussi utiliser une seule image avec les 6 faces, voir la doc de SCNMaterialProperty
    boxGeo.firstMaterial?.diffuse.contents = [imagePlusX, imageMinusX, imagePlusY, imageMinusY, imagePlusZ, imageMinusZ]
     
    let cubeNode = SCNNode(geometry: boxGeo)
    scene.rootNode.addChildNode(cubeNode)
  • DrakenDraken Membre
    février 2017 modifié #3

    C'est plutôt simple. J'arrive à  comprendre, sans avoir jamais fait de SceneKit, grâce à  ma petite expérience de SpriteKit.


    Et avec Metal ? 

  • CéroceCéroce Membre, Modérateur
    Scene Kit utilise OpenGL ou Metal sous le capot, au choix. Enfin, de moins en moins au choix, les dernières évolutions, notamment le Physically-Based Rendering, étant réservées à  Metal.


  • Pas vraiment testé (SceneKit avec les Playgrounds, c'est toujours pas ça), mais ça doit fonctionner:

     



    import SceneKit
     
    // sceneView est une IBOutlet SCNView
    sceneView.autoenablesDefaultLighting = true
    sceneView.allowsCameraControl = true
     
    let scene = SCNScene()
    sceneView.scene = scene
     
    let boxGeo = SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0)
     
    // Les images sont des UIImages.
    // On peut aussi utiliser une seule image avec les 6 faces, voir la doc de SCNMaterialProperty
    boxGeo.firstMaterial?.diffuse.contents = [imagePlusX, imageMinusX, imagePlusY, imageMinusY, imagePlusZ, imageMinusZ]
     
    let cubeNode = SCNNode(geometry: boxGeo)
    scene.rootNode.addChildNode(cubeNode)



    Cela ne répond pas vraiment à  la question de Jérémy, qui veut coller des labels sur le cube. Enfin il peut toujours dessiner le texte dans un contexte graphique, pour générer une UIImage et l'utiliser comme texture.

  • CéroceCéroce Membre, Modérateur
    février 2017 modifié #6
    Tu n'as pas tort.
    On peut aussi mettre dans la propriété contents:
    - une CALayer
    - une CGImage
    - une SKScene
    - une autre SCNScene rendue dans une SCNLayer

    Mais ce n'est pas classique comme manière de faire en 3D, habituellement les textures sont toutes prêtes.
  • Oui, mais on a besoin d'afficher du texte à  la volée dans un jeu, ne serais-ce que "Score : 12356" 

  • CéroceCéroce Membre, Modérateur
    février 2017 modifié #8
    ça c'est une autre question.
    Le protocole SCNSceneRenderer auquel se conforme SCNView a une propriété overlaySKScene. C'est la manière idéale d'afficher un score.


  • Le protocole SCNSceneRenderer auquel se conforme SCNView a une propriété overlaySKScene. C'est la manière idéale d'afficher un score.




    Ah oui, c'est intéressant. On peut donc placer une couche SpriteKit (affichage de textes et interface 2D) au dessus la scène 3D. Très utile ..

  • JérémyJérémy Membre
    février 2017 modifié #10


    Scene Kit utilise OpenGL ou Metal sous le capot, au choix. Enfin, de moins en moins au choix, les dernières évolutions, notamment le Physically-Based Rendering, étant réservées à  Metal.




     


    Tu peux choisir le moteur ? Si oui, tu fais comment ?


     




    Oui, mais on a besoin d'afficher du texte à  la volée dans un jeu, ne serais-ce que "Score : 12356" 




     


    +1


     




    Le protocole SCNSceneRenderer auquel se conforme SCNView a une propriété overlaySKScene. C'est la manière idéale d'afficher un score.




     


    Si je comprends bien je crée un rendu avec SpriteKit que je viens coller sur mon cube ? Et du coup je vois pouvoir changer le texte à  la volée ?


  • CéroceCéroce Membre, Modérateur
    février 2017 modifié #11

    Tu peux choisir le moteur ? Si oui, tu fais comment ?

    SCNSceneRenderer.renderingAPI
     

    Si je comprends bien je crée un rendu avec SpriteKit que je viens coller sur mon cube ? Et du coup je vois pouvoir changer le texte à  la volée ?

    Oui, c'est cela, mais je parle de deux choses différentes. overlaySKScene recouvre forcément toute la vue. ça sert typiquement à  afficher un score, ou une barre de vie.
    Pour changer l'image sur le cube, c'est la propriété .contents où tu peux utiliser une SKScene (parmi d'autres possibilités).


  • Pour changer l'image sur le cube, c'est la propriété .contents où tu peux utiliser une SKScene (parmi d'autres possibilités).




    Il suffit donc de placer un SKLabel sur la SKScene pour afficher un texte directement sur le cube. Je ferais un essai ce soir pour voir.

  • JérémyJérémy Membre
    février 2017 modifié #13


    Scene Kit utilise OpenGL ou Metal sous le capot, au choix. Enfin, de moins en moins au choix, les dernières évolutions, notamment le Physically-Based Rendering, étant réservées à  Metal.




     


    Tu as trouvé qu'il y en avait de mieux que l'autre ? Si tu fais le ration qualité graphique sur consommation énergétique.


     




    Il suffit donc de placer un SKLabel sur la SKScene pour afficher un texte directement sur le cube. Je ferais un essai ce soir pour voir.




     


    Playground permet il de faire des protos de Scene/Sprite Kit ?


     


    Si tu pouvais poster ta solution ce serait super sympa.


  • CéroceCéroce Membre, Modérateur

    Tu as trouvé qu'il y en avait de mieux que l'autre ? Si tu fais le ration qualité graphique sur consommation énergétique.

    Metal tourne mieux, mais j'ai d'autres critères, comme l'ouverture qui me font préférer OpenGL. Par exemple, va écrire des shaders Metal, sachant que la seule doc qui existe est celle d'Apple.
  • JérémyJérémy Membre
    février 2017 modifié #15


    Metal tourne mieux, mais j'ai d'autres critères, comme l'ouverture qui me font préférer OpenGL. Par exemple, va écrire des shaders Metal, sachant que la seule doc qui existe est celle d'Apple.




     


    D'accord ! Ouai j'imagine que les retours d'expérience de Metal ne doivent pas courir les rues... Mise à  part ça, elle te semble mature comme techno ?


  • CéroceCéroce Membre, Modérateur
    Je ne peux pas trop dire, n'en ayant pas l'expérience.

    C'était inutilisable il y a seulement un an.
    Pour l'instant, il n'y a pas encore de gros jeux qui l'exploitent, ni d'autres gros logiciels. ça devrait arriver bientôt. De ce que j'en ai lu, il y a encore un tas de problèmes, qui ne nous concernent pas forcément à  notre petit niveau.
  • DrakenDraken Membre
    février 2017 modifié #17


    Il suffit donc de placer un SKLabel sur la SKScene pour afficher un texte directement sur le cube. Je ferais un essai ce soir pour voir.




     


    Pour réaliser cet essai, j'ai utilisé le template SceneKit d'Xcode en le simplifiant. Je n'ai modifié que la méthode viewDidLoad() du GameViewController. Et importé le framework SpriteKit.


     


    La seule chose que j'ai reprise du viewDidLoad() du template, c'est la gesture de la caméra et sa gestion (je ne comprend rien à  SCNTransaction et compagnie).


    a



    //
    // GameViewController.swift
    // scKit
    //

    import UIKit
    import QuartzCore
    import SceneKit
    import SpriteKit

    class GameViewController: UIViewController {

    private var skScene : SKScene?

    override func viewDidLoad() {
    super.viewDidLoad()

    // DEBUT DE MON CODE

    // Préparation de la Scène
    let scene = SCNScene()
    let sceneView = self.view as! SCNView
    sceneView.autoenablesDefaultLighting = true
    sceneView.allowsCameraControl = true
    sceneView.scene = scene

    // Création SKLabel
    let sceneLabel = SKScene(size: CGSize(width: 100, height: 100))
    sceneLabel.backgroundColor = UIColor.gray

    let skLabel = SKLabelNode(text: "SUSHI")
    skLabel.fontName = "ChalkboardSE-Bold"
    skLabel.fontColor = UIColor.red
    skLabel.verticalAlignmentMode = .center

    skLabel.position = CGPoint(x: sceneLabel.size.width/2.0,
    y: sceneLabel.size.height/2.0)
    sceneLabel.addChild(skLabel)

    let boxGeo = SCNBox(width: 1, height: 1, length: 1, chamferRadius: 0)

    // ASSOCIATION Cube et SKLabel
    boxGeo.firstMaterial?.diffuse.contents = sceneLabel

    let cubeNode = SCNNode(geometry: boxGeo)
    scene.rootNode.addChildNode(cubeNode)

    // FIN DE MON CODE

    // create and add a camera to the scene
    let cameraNode = SCNNode()
    cameraNode.camera = SCNCamera()
    scene.rootNode.addChildNode(cameraNode)

    // place the camera
    cameraNode.position = SCNVector3(x: 0, y: 0, z: 5)

    // add a tap gesture recognizer
    let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
    sceneView.addGestureRecognizer(tapGesture)
    }

    func handleTap(_ gestureRecognize: UIGestureRecognizer) {
    // retrieve the SCNView
    let scnView = self.view as! SCNView

    // check what nodes are tapped
    let p = gestureRecognize.location(in: scnView)
    let hitResults = scnView.hitTest(p, options: [:])
    // check that we clicked on at least one object
    if hitResults.count > 0 {
    // retrieved the first clicked object
    let result: AnyObject = hitResults[0]

    // get its material
    let material = result.node!.geometry!.firstMaterial!

    // highlight it
    SCNTransaction.begin()
    SCNTransaction.animationDuration = 0.5

    // on completion - unhighlight
    SCNTransaction.completionBlock = {
    SCNTransaction.begin()
    SCNTransaction.animationDuration = 0.5

    material.emission.contents = UIColor.black

    SCNTransaction.commit()
    }

    material.emission.contents = UIColor.red

    SCNTransaction.commit()
    }
    }

    override var shouldAutorotate: Bool {
    return true
    }

    override var prefersStatusBarHidden: Bool {
    return true
    }

    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
    if UIDevice.current.userInterfaceIdiom == .phone {
    return .allButUpsideDown
    } else {
    return .all
    }
    }

    override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Release any cached data, images, etc that aren't in use.
    }

    }


    a


    J'ai ... presque réussi ! Grrrr ..


    Pour une raison qui m'échappe, le texte du SKLabel est inversé !


    Enfin, pour la première fois que je touche SceneKit c'est pas si mal.


  • DrakenDraken Membre
    février 2017 modifié #18

    Bon, j'ai réglé le problème en inversant verticalement le label. 



    let skLabel = SKLabelNode(text: "SUSHI")
    skLabel.fontName = "ChalkboardSE-Bold"
    skLabel.fontColor = UIColor.red
    skLabel.verticalAlignmentMode = .center
    // INVERSION VERTICALE DU LABEL
    skLabel.yScale *= -1


    Ouf !


     


  • DrakenDraken Membre
    février 2017 modifié #19

    Etape suivante : afficher un texte différent sur chaque face.


     


    Pour ce faire, il faut créer des objets matériaux (SCNMaterial) et les associer à  chaque face du cube.


     


    Pour me simplifier la vie, j'ai écrit une fonction créant automatiquement un matériau à  partir d'un texte.


    a



    func creerMateriau(texte:String) -> SCNMaterial {
    let sceneLabel = SKScene(size: CGSize(width: 100, height: 100))
    sceneLabel.backgroundColor = UIColor.gray

    let skLabel = SKLabelNode(text: texte)
    skLabel.fontName = "ChalkboardSE-Bold"
    skLabel.fontColor = UIColor.red
    skLabel.verticalAlignmentMode = .center
    // INVERSION VERTICALE DU LABEL
    skLabel.yScale *= -1

    skLabel.position = CGPoint(x: sceneLabel.size.width/2.0,
    y: sceneLabel.size.height/2.0)
    sceneLabel.addChild(skLabel)

    let materiau = SCNMaterial()
    materiau.diffuse.contents = sceneLabel

    return materiau
    }


    Création des 6 matériaux



    let face1 = self.creerMateriau(texte: "SUSHI")
    let face2 = self.creerMateriau(texte: "ALGUE")
    let face3 = self.creerMateriau(texte: "WASABI")
    let face4 = self.creerMateriau(texte: "SAUMON")
    let face5 = self.creerMateriau(texte: "THON")
    let face6 = self.creerMateriau(texte: "RIZ")

    let tableauMateriaux = [face1, face2, face3, face4, face5, face6]


    Dernière étape : donner la liste des matériaux au cube



    // AVANT, UN SEUL MATERIAU
    // boxGeo.firstMaterial?.diffuse.contents = sceneLabel

    // MAINTENANT UN TABLEAU DE 6 MATERIAU
    boxGeo.materials = tableauMateriaux



    Le résultat n'est pas encore parfait. Les textes trop longs comme "SAUMON" et "WASABI" débordent sur les bords. Mais c'est facile à  régler, en ajustant la taille de la police pour chaque face.


     


    Je ne suis pas mécontent de ces petits essais. Finalement SceneKit n'est pas aussi compliqué que je l'avais imaginé.

  • CéroceCéroce Membre, Modérateur

    (je ne comprend rien à  SCNTransaction et compagnie).

    En pratique, les transactions fonctionnent mal (en particulier quand elles sont interrompues) et sont compliquées à  utiliser. Aussi relou que les CATransactions, pour ceux qui connaissent. En gros, le seul cas où on les utilise c'est quand elles sont définies dans le fichier Collada.

    Il existe les SCNActions qui font la même chose, mais bien plus simplement, et avec lesquelles je n'ai jamais eu le moindre problème. ça fonctionne comme les SKActions, donc tu seras en terrain connu.
  • CéroceCéroce Membre, Modérateur
    Un détail important: si tu t'amuses à  mettre à  jour les SKScenes, tu verras qu'elle ne sont pas mises à  jour dans la scène 3D sauf quand on manipule le cube. C'est parce que quand il n'y a pas de changement de la scène 3D, alors le rendu n'est pas effectué, pour économiser la batterie.

    On peut écrire scene.playing = true, ou un truc comme ça pour forcer le rafraà®chissement.


  • Il existe les SCNActions qui font la même chose, mais bien plus simplement, et avec lesquelles je n'ai jamais eu le moindre problème. ça fonctionne comme les SKActions, donc tu seras en terrain connu.




    Parfait, donc SCNTransaction.  


    J'avais vu les SCNActions dans le template SceneKit. C'est super d'avoir l'équivalent des SKActions en 3D.  



  • Un détail important: si tu t'amuses à  mettre à  jour les SKScenes, tu verras qu'elle ne sont pas mises à  jour dans la scène 3D sauf quand on manipule le cube. C'est parce que quand il n'y a pas de changement de la scène 3D, alors le rendu n'est pas effectué, pour économiser la batterie.


    On peut écrire scene.playing = true, ou un truc comme ça pour forcer le rafraà®chissement.




    Il faut donc activer le rafraà®chissement avant de lancer une SKAction sur une SKScene de l'objet. 

  • CéroceCéroce Membre, Modérateur
    février 2017 modifié #24

    Je ne suis pas mécontent de ces petits essais. Finalement SceneKit n'est pas aussi compliqué que je l'avais imaginé.

    SceneKit n'est pas compliquée. Pas guère plus que Sprite Kit. Je trouve justement que c'est une super introduction à  la programmation 3D.

    Pour moi, le problème c'est qu'il y a beaucoup de limitations, qu'elles ne sont pas clairement documentées, et le tout est très loin de la magie revendiquée par Apple. Technologiquement parlant, Scene Kit sera toujours cinq ans derrière Unity ou Unreal. Je ne comprends pas bien la volonté de leur courir après.

    Pour une représentation 3D simple, c'est très bien, mais vouloir faire un jeu avec " un tant soit peu complexe " est stupide.
  • DrakenDraken Membre
    février 2017 modifié #25
  • Quelques émois, et hop .. voici un dés magique pour la Saint Valentin :


    (attention de ne pas tomber sur la dernière face !)



    let face1 = self.creerMateriau(texte: "❤️")
    let face2 = self.creerMateriau(texte: "💞")
    let face3 = self.creerMateriau(texte: "💖")
    let face4 = self.creerMateriau(texte: "💝")
    let face5 = self.creerMateriau(texte: "💙")
    let face6 = self.creerMateriau(texte: "💔")


  • JérémyJérémy Membre
    février 2017 modifié #27

    Beau travail Draken ! 


     


    Mais tu coup est il possible de changer le texte à  la volé si le cube est en mouvement (exemple, afficher l'heure en temps réel sur une des faces) ?  :)


  • DrakenDraken Membre
    février 2017 modifié #28

    Je pense (à  tester) qu'on peut actualiser le cube toutes les secondes en créant un nouveau matériau avec l'heure courante, pour l'affecter à  la bonne face.


     


    Un truc comme :



    let nouveauMateriau = creerMateriau(heureCourante)
    // 3 = Numéro de la face contenant l'heure (exemple)
    cube.materials[3] = nouveauMateriau



    En fait le cube peut actualiser lui même son contenu, grâce à  l'action SCNAction.customAction() permettant d'exécuter régulièrement un bloc d'instructions, avec un délai variable.



    let actualisationHeure = SCNAction.customAction(duration: 1.0) {
    node, time in

    // node = node SceneKit sur lequel porte l'action
    // time = interval de temps depuis le dernier appel de l'Action

    // Séquence d'instruction à  répéter
    // .....

    }


    Je ferai un essai demain, là  dodo ..


     


    EDIT : Oups, je me suis trompé, l'action SCNAction.customAction() ne fonctionne pas comme je le pensais, à  la première lecture. En fait, elle répète les mêmes instructions pendant une durée de temps précise.

  • DrakenDraken Membre
    février 2017 modifié #29

    Et voilà , j'ai modifié le code pour afficher en permanence la valeur d'un compteur s'incrémentant une fois par seconde.


    a



    private var compteur = 0

    a


    Le compteur est une variable du GameViewControleur.


    a



    // Attente 1 seconde
    let attente = SCNAction.wait(duration: 1.0)

    // Inçrémentatà®on du compteur et actualisation du cube
    let incrementerCompteur = SCNAction.run {
    node in
    self.compteur += 1
    let materiau = self.creerMateriau(texte: String(self.compteur))
    node.geometry?.materials[0] = materiau
    }

    // Combinaisons des actions pour créer une action personnalisée
    let gestionCompteur = SCNAction.sequence([attente,
    incrementerCompteur])
    // Le Node du cube reçoit l'action à  répéter sans cesse
    cubeNode.runAction(SCNAction.repeatForever(gestionCompteur))


    a


    L'idée est d'associer une action personnalisé au cube. Celle-ci se compose d'une répétition sans fin, d'une attente d'une seconde et de quelques instructions pour incrémenter le compteur, créer un nouveau matériau et actualiser une face du cube.


    a


    La preuve en image :

  • Ah chouette !


     


    Si souhaites intégrer ton cube dans une application existante et que tu veuilles qu'il n'y ait pas de fond noir (pas de fond du tout), j'imagine que c'est possible ?


  • Tu veux placer une vue classique sous la sceneView, pour avoir une superposition ? Je présume que c'est réalisable avec remplissant le fond de la scène avec la couleur transparente. Je dois filer, je ferai un essai plus tard.

Connectez-vous ou Inscrivez-vous pour répondre.