Spanish (Español) translation by Javier Salesi (you can also view the original English article)
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.



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.



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



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.
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 instanciaSCNNode
para la cámara. Luego asignas el objetoSCNCamera
a la propiedadcamera
decameraNode
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 unSCNNode
llamadolightNode
. La instanciaSCNLight
es asignada a la propiedadlight
del nodo light (luz). La propiedadtype
deSCNLight
se establece enSCNLightTypeOmni
. É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 claseSCNBox
es una subclase deSCNGeometry
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 degeometry
. - 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.
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.
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.
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.
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.
Compila y ejecuta tu aplicación por última vez para ver el resultado final.
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 unSCNScene
- 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!