Advertisement
  1. Code
  2. SceneKit

Una introducción a SceneKit: Fundamentos

Scroll to top
Read Time: 11 min

Spanish (Español) translation by Javier Salesi (you can also view the original English article)

Final product imageFinal product imageFinal product image
What You'll Be Creating

En éste tutorial, aprenderás cómo crear una escena básica 3D en SceneKit sin las complejidades de OpenGL. Ésto incluye geometría básica, cámaras, luces, materiales, y sombras.

Introducción

El framework SceneKit fue lanzado primero por Apple junto con OS X 10.8 Mountain Lion y posteriormente estuvo disponible en iOS con el lanzamiento de iOS 8. El propósito de éste framework es permitir a desarrolladores integrar fácilmente gráficos 3D en juegos y aplicaciones sin las complejidades de APIs para gráficos, tales como OpenGL y Metal.

SceneKit te permite simplemente proporcionar una descripción de los recursos que quieres en tu escena, con el mismo framework manejando por tí todo el código de renderizado de OpenGL. En éste primer tutorial, te enseñaré algunos de los fundamentos de trabajar con recursos 3D y las bases del framework SceneKit.

Éste tutorial requiere que estés ejecutando Xcode 6 o superior. Aunque no es necesario, recomiendo utilizar un dispositivo físico que ejecute iOS 8 en el cual probar tu código SceneKit. Puedes usar el simulador iOS, pero el desempeño no es magnífico si tu escena se vuelve más compleja. Nota que el testeo en un dispositivo físico iOS requiere que tengas una cuenta de desarrollador iOS registrada.

1. Fundamentos

Lo primero que necesitas saber de SceneKit es que recursos, representados por nodos, son arreglados en un árbol jerárquico llamado scene graph. Si estás familiarizado con el desarrollo iOS, éste árbol funciona muy similar a un view hierarchy regular en UIKit. Cada escena que creas tiene un solo nodo raíz al que añades subsecuentes nodos y que también proporciona una base para el sistema de coordenadas 3D de esa escena.

Cuando agregas un nodo a una escena, su posición se especifica por una serie de tres números, un vector de tres componentes representada por la estructura SCNVector en tu código. Cada uno de éstos tres componentes define la posición de un nodo en los ejes x, y y z, como se muestra en la imagen de abajo.

3D Coordinates Diagram3D Coordinates Diagram3D Coordinates Diagram
Crédito de la Imagen: Guía del Framework SceneKit de Apple

La posición del nodo raíz de tu escena se define como (0,0,0). En la imagen de arriba, ésta es la posición donde se intersectan los tres ejes. La cámara incluída en la imagen representa la dirección predeterminada a la que apunta una cámara cuando se añade a tu escena.

Ahora que sabes algunas de las bases de cómo se representan objetos en SceneKit, estás listo para comenzar a escribir código.

2. Configuración del Proyecto

Abre Xcode y crea una nueva iOS Application basada en la plantilla Single View Application. Mientras podrías fácilmente crear una aplicación de la plantilla Game utilizando SceneKit, para éste tutorial voy a mostrarte como comenzar a trabajar con SceneKit desde cero.

Choose application templateChoose application templateChoose application template

Ingresa un Product Name, establece Language en Swift, y Devices en Universal. Da click en Next para continuar.

App detailsApp detailsApp details

Después de crear tu proyecto, navega a ViewController.swift y agrega el siguiente comando import en la parte superior para importar el framework SceneKit:

1
import SceneKit

Luego, añade la siguiente implementación del método viewDidLoad en la clase ViewController:

1
override func viewDidLoad() {
2
    super.viewDidLoad()
3
    
4
    let sceneView = SCNView(frame: self.view.frame)
5
    self.view.addSubview(sceneView)
6
}

En el método viewDidLoad, creamos un objeto SCNView, pasando el marco de la view (vista) del view controller. Asignamos la instancia SCNView a una constante, sceneView, y agrégala como un subview (subvista) de la view de view controller.

La clase SCNView es una subclase de UIView y proporciona un outlet para tu contenido en SceneKit. Además de tener la funcionalidad de un regular view, un SCNView también tiene varias propiedades y métodos relacionados con el contenido de SceneKit.

Para revisar que todo está funcionando correctamente, compila y ejecuta tu aplicación. Verás que acabas de tener un view (vista) en blanco.

Initial app viewInitial app viewInitial app view

3. Configuración de la Scene (Escena)

Para renderizar contenido en un SCNView, primero necesitas crear un SCNScene y asignarla al view. En ésta escena, entonces necesitas añadir una cámara y al menos una luz. Para éste ejemplo, también vas a agregar un cubo para que se renderice en SceneKit. Agrega el siguiente código al método viewDidLoad:

1
override func viewDidLoad() {
2
    super.viewDidLoad()
3
    
4
    let sceneView = SCNView(frame: self.view.frame)
5
    self.view.addSubview(sceneView)
6
        
7
    let scene = SCNScene()
8
    sceneView.scene = scene
9
10
    let camera = SCNCamera()
11
    let cameraNode = SCNNode()
12
    cameraNode.camera = camera
13
    cameraNode.position = SCNVector3(x: 0.0, y: 0.0, z: 3.0)
14
15
    let light = SCNLight()
16
    light.type = SCNLightTypeOmni
17
    let lightNode = SCNNode()
18
    lightNode.light = light
19
    lightNode.position = SCNVector3(x: 1.5, y: 1.5, z: 1.5)
20
21
    let cubeGeometry = SCNBox(width: 1.0, height: 1.0, length: 1.0, chamferRadius: 0.0)
22
    let cubeNode = SCNNode(geometry: cubeGeometry)
23
24
    scene.rootNode.addChildNode(lightNode)
25
    scene.rootNode.addChildNode(cameraNode)
26
    scene.rootNode.addChildNode(cubeNode)
27
}

Vamos a analizar el método viewDidLoad paso a paso:

  • Primero creas la escena para tu view al invocar el método init. A menos que estés cargando una escena preparada de un archivo externo, es el inicializador que siempre usarás.
  • Después, creas un objeto SCNCamera y una instancia SCNNode para la cámara. Luego asignas el objeto SCNCamera a la propiedad camera de cameraNode y mueves éste nodo en el eje z para ver el cubo que crearás un poco más tarde.
  • En el próximo paso, creas un objeto SCNLight y un SCNNode llamado lightNode. La instancia SCNLight es asignada a la propiedad light del nodo light (luz). La propiedad type de SCNLight se establece en SCNLightTypeOmni. Éste tipo de light distribuye la luz uniformemente en todas las direcciones desde un punto en el espacio 3D. Puedes pensar en éste tipo de light como un foco regular.
  • Finalmente, creas un cubo al usar la clase SCNBox, haciendo la anchura, la altura y la longitud todas del mismo tamaño. La clase SCNBox es una subclase de SCNGeometry y es una de las formas primitivas que puedes crear. Otras formas inclulyen esferas, pirámides y toros. También creas un nodo que pasa al cubo en el parámetro de geometry.
  • Para establecer la escena, agregas los tres nodos (cámara, luz, y cubo) a la scene graph de la escena. No es necesaria la configuración adicional pues un objeto SCNScene automáticamente detecta cuando un nodo contiene un objeto cámara o luz, renderizando la escena como corresponde.

Compila y ejecuta tu aplicación, y verás que ahora tienes un cubo negro siendo iluminado por tu luz desde la esquina superior derecha.

First SceneKit renderFirst SceneKit renderFirst SceneKit render

Desafortunadamente, el cubo no parece tridimensional por el momento. Ésto es porque la cámara está posicionada directamente frente a él. Lo que vas a hacer ahora es cambiar la posición de la cámara para que tenga una mejor vista del cubo.

Para que la cámara continúe apuntando directamente al cubo, sin embargo, también vas a agregar un SCNLookAtConstraint a la cámara. Comienza por actualizar la posición de la cámara como se muestra debajo.

1
cameraNode.position = SCNVector3(x: -3.0, y: 3.0, z: 3.0)

Luego, añade el siguiente fragmento de código al método viewDidLoad despues de instanciar el nodo para el cubo:

1
let constraint = SCNLookAtConstraint(target: cubeNode)
2
constraint.gimbalLockEnabled = true
3
cameraNode.constraints = [constraint]

El cambio de posición mueve la cámara a la izquierda y hacia arriba. Al agregar un constraint o restricción, con el cubo como su objetivo y gimbalLockEnabled establecido en true, aseguras que la cámara quedará paralela con el horizonte y la ventana gráfica, la pantalla de tu dispositivo en éste caso. Ésto se hace al deshabilitar la rotación en el eje de rotación propia, el eje que apunta desde la cámara hasta el target (objetivo) del constraint.

Ejecuta y compila otra vez tu aplicación, y verás tu cubo en una fantástica 3D.

3D cube3D cube3D cube

4. Materiales y Sombras

Es momento de agregar más realismo a la escena con materiales y sombras. Primero vas a necesitar otro objeto en el que proyectar una sombra. Usa el siguiente fragmento de código para crear un plano, un rectángulo y posicionarlo debajo del cubo. No olvides agregar el nuevo código como un nodo hijo al nodo raíz de la escena.

1
override func viewDidLoad() {
2
    ...
3
    let cubeGeometry = SCNBox(width: 1.0, height: 1.0, length: 1.0, chamferRadius: 0.0)
4
    let cubeNode = SCNNode(geometry: cubeGeometry)
5
    
6
    let planeGeometry = SCNPlane(width: 50.0, height: 50.0)
7
    let planeNode = SCNNode(geometry: planeGeometry)
8
    planeNode.eulerAngles = SCNVector3(x: GLKMathDegreesToRadians(-90), y: 0, z: 0)
9
    planeNode.position = SCNVector3(x: 0, y: -0.5, z: 0)
10
    ...
11
    scene.rootNode.addChildNode(lightNode)
12
    scene.rootNode.addChildNode(cameraNode)
13
    scene.rootNode.addChildNode(cubeNode)
14
    scene.rootNode.addChildNode(planeNode)
15
}

Al cambiar la propiedad eulerAngles del nodo, rotas el plano hacia atrás 90 grados en el eje x. Necesitamos hacer ésto, porque los planos son creados verticalmente por defecto. En SceneKit, los ángulos de rotación son calculados en radianes en lugar de grados, pero éstos valores pueden convertirse fácilmente utilizando las funciones GLKMathDegreesToRadians(_:) y GLKMathsRadiansToDegrees(_:). GLK se refiere a GLKit, el framework OpenGL de Apple.

Después, agrega un material al cubo y al plano. Para éste ejemplo, vas a dar al cubo y al plano un color sólido, rojo y verde respectivamente. Agrega las siguientes líneas al método viewDidLoad para crear éstos materiales.

1
override func viewDidLoad() {
2
    ...
3
    planeNode.position = SCNVector3(x: 0, y: -0.5, z: 0)
4
    
5
    let redMaterial = SCNMaterial()
6
    redMaterial.diffuse.contents = UIColor.redColor()
7
    cubeGeometry.materials = [redMaterial]
8
    
9
    let greenMaterial = SCNMaterial()
10
    greenMaterial.diffuse.contents = UIColor.greenColor()
11
    planeGeometry.materials = [greenMaterial]
12
    
13
    let constraint = SCNLookAtConstraint(target: cubeNode)
14
    ...
15
}

Para cada objeto SCNMaterial, asignas a sus diffuse contents (contenido difuso) un valor UIColor. La propiedad diffuse (difuso) de un material determina cómo aparece cuando está bajo la luz directa. Nota que el valor asignado no tiene que ser un objeto UIColor. Hay muchos otros tipos de objetos aceptables para asignar a ésta propiedad, tales como UIImage, CALayer, e incluso una textura de SpriteKit (SKTexture).

Compila y ejecuta tu aplicación otra vez no sólo para ver el plano por primera vez, sino también los materiales que creaste.

Red cube and green planeRed cube and green planeRed cube and green plane

Ahora es momento de agregar algunas sombras a tu escena. De las cuatro tipos de luz disponibles en SceneKit, sólo spot lights (focos) pueden crear sombras. Para éste ejemplo, vas a convertir tu existente luz omni en un foco, apuntando hacia el cubo. Agrega el siguiente código al método viewDidLoad:

1
override func viewDidLoad() {
2
    ...
3
    let light = SCNLight()
4
    light.type = SCNLightTypeSpot
5
    light.spotInnerAngle = 30.0
6
    light.spotOuterAngle = 80.0
7
    light.castsShadow = true
8
    let lightNode = SCNNode()
9
    lightNode.light = light
10
    lightNode.position = SCNVector3(x: 1.5, y: 1.5, z: 1.5)
11
    ...
12
    let constraint = SCNLookAtConstraint(target: cubeNode)
13
    constraint.gimbalLockEnabled = true
14
    cameraNode.constraints = [constraint]
15
    lightNode.constraints = [constraint]
16
    ...
17
}

Para crear el foco, primero establece el tipo de luz en SCNLightTypeSpot. Luego especifica los angulos interiores y exteriores del foco en grados. Los valores predeterminados son 0 y 45 respectivamente. El ángulo interno determina cuánta area cubre la luz en luz directa mientras el ángulo externo decide cuánta área es parcialmente iluminada. La diferencia entre éstos ángulos será más clara una vez que veas la escena resultante. Posteriormente explícitamente dile a la luz que proyecte sombras y también agregas el mismo SCNLookAtConstraint que creaste para tu cámara previamente.

Ejecuta y compila tu aplicación para ver la escena resultante. El ángulo interno que especificaste en tu código se muestra donde el plano es un verde sólido, directamente debajo del cubo. El ángulo externo se muestra por el gradiente de luz que se desvanece a negro mientras se aleja del objetivo de la luz.

Cube and plane with shadowCube and plane with shadowCube and plane with shadow

Verás que ahora tu cubo tiene una sombra proyectada correctamente. El foco, sin embargo, sólo ilumina parte del plano. Ésto es porque no hay luz ambiente en tu escena.

Una luz ambiente es una fuente de luz que ilumina todo con una distribución igual de luz. Debido a que una luz ambiente ilumina la escena entera, su posición no importa y puedes agregarla a cualquier nodo que quieras, incluso al mismo nodo de tu cámara. Usa el siguiente fragmento de código para crear una luz ambiente a tu escena.

1
override func viewDidLoad() {
2
    ...
3
    let camera = SCNCamera()
4
    let cameraNode = SCNNode()
5
    cameraNode.camera = camera
6
    cameraNode.position = SCNVector3(x: -3.0, y: 3.0, z: 3.0)
7
    
8
    let ambientLight = SCNLight()
9
    ambientLight.type = SCNLightTypeAmbient
10
    ambientLight.color = UIColor(red: 0.2, green: 0.2, blue: 0.2, alpha: 1.0)
11
    cameraNode.light = ambientLight
12
    ...
13
}

El fragmento de código crea un SCNLight, tal y como lo hiciste antes. La principal diferencia es la propiedad type de la luz, que se establece en SCNLightTypeAmbient. También estableces su color a un gris oscuro para que no sature tu escena. El color predeterminado para una luz es blanco (valor RGB de 1,1,1) y tener éste color en una luz ambiente causa que toda la escena esté completamente iluminada como se muestra en la captura de abajo.

Fully illuminated sceneFully illuminated sceneFully illuminated scene

Compila y ejecuta tu aplicación por última vez para ver el resultado final.

Final productFinal productFinal product

Conclusión

Si has seguido de principio a fin éste tutorial, ahora deberías encontrarte cómodo con los siguientes tópicos:

  • el sistema de coordenadas 3D y scene graph usados por SceneKit
  • configurar un SCNView con un SCNScene
  • agregar cámaras, luces y nodos a una escena
  • asignar materiales a geometrías
  • trabajar con luces para iluminar una escena y proyectar sombras

En el próximo tutorial de ésta serie, aprenderás algunos conceptos más avanzados del framework SceneKit, incluyendo animación, interacción del usuario, sistemas de partículas, y simular física.

¡Sé el primero en conocer las nuevas traducciones–sigue @tutsplus_es en Twitter!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.