This is an excerpt taken from Chapter 9, “Materials and Lighting”, of our book ARKit by Tutorials. This book show you how to build five immersive, great-looking AR apps in ARKit, Apple’s augmented reality framework. Enjoy!
In the first and second parts of this three-part tutorial series on ARKit, you learned how to add 3D objects to your scene with SceneKit. Now it’s time to put that knowledge to use and build the full portal. In this tutorial, you will learn how to:
- Create walls, a ceiling and roof for your portal and adjust their position and rotation.
- Make the inside of the portal look more realistic with different textures.
- Add lighting to your scene.
Getting Started
Download the materials for this tutorial using the link at the top, then load up the starter project from the starter folder. Before you begin, you’ll need to know a little bit about how SceneKit works.
The SceneKit Coordinate System
As you saw in the previous part in this tutorial series, SceneKit can be used to add virtual 3D objects to your view. The SceneKit content view is comprised of a hierarchical tree structure of nodes, also known as the scene graph. A scene consists of a root node, which defines a coordinate space for the world of the scene, and other nodes that populate the world with visible content. Each node or 3D object that you render on screen is an object of type SCNNode
. An SCNNode
object defines the coordinate space transform (position, orientation and scale) relative to its parent node. It doesn’t have any visible content by itself.
The rootNode
object in a scene defines the coordinate system of the world rendered by SceneKit. Each child node you add to this root node creates its own coordinate system, which, in turn, is inherited by its own children.
SceneKit uses a right-handed coordinate system where (by default) the direction of view is along the negative z-axis, as illustrated below.
The position of the SCNNode
object is defined using an SCNVector3
which locates it within the coordinate system of its parent. The default position is the zero vector, indicating that the node is placed at the origin of the parent node’s coordinate system. In this case, SCNVector3
is a three component vector where each of the components is a Float
value representing the coordinate on each axis.
The SCNNode
object’s orientation, expressed as pitch, yaw, and roll angles is defined by its eulerAngles
property. This is also represented by an SCNVector3
struct where each vector component is an angle in radians.
Textures
The SCNNode
object by itself doesn’t have any visible content. You add 2D and 3D objects to a scene by attaching SCNGeometry
objects to nodes. Geometries have attached SCNMaterial
objects that determine their appearance.
An SCNMaterial
has several visual properties. Each visual property is an instance of the SCNMaterialProperty
class that provides a solid color, texture or other 2D content. There are a variety of visual properties for basic shading, physically based shading and special effects which can be used to make the material look more realistic.
The SceneKit asset catalog is designed specifically to help you manage your project’s assets separately from the code. In your starter project, open the Assets.scnassets folder. Notice that you already have images representing different visual properties for the ceiling, floor and walls.
With SceneKit, you can also use nodes with attached SCNLight
objects to shade the geometries in a scene with light and shadow effects.
Building the Portal
Let’s jump right in to creating the floor for the portal. Open SCNNodeHelpers.swift and add the following to the top of the file just below the import SceneKit
statement.
// 1
let SURFACE_LENGTH: CGFloat = 3.0
let SURFACE_HEIGHT: CGFloat = 0.2
let SURFACE_WIDTH: CGFloat = 3.0
// 2
let SCALEX: Float = 2.0
let SCALEY: Float = 2.0
// 3
let WALL_WIDTH:CGFloat = 0.2
let WALL_HEIGHT:CGFloat = 3.0
let WALL_LENGTH:CGFloat = 3.0
You’re doing a few things here:
- You define constants for the dimensions of the floor and ceiling of your portal. The height of the roof and ceiling corresponds to the thickness.
- These are constants to scale and repeat the textures over the surfaces.
- These define the width, height and length of the wall nodes.
Next, add the following method to SCNNodeHelpers
:
func repeatTextures(geometry: SCNGeometry, scaleX: Float, scaleY: Float) {
// 1
geometry.firstMaterial?.diffuse.wrapS = SCNWrapMode.repeat
geometry.firstMaterial?.selfIllumination.wrapS = SCNWrapMode.repeat
geometry.firstMaterial?.normal.wrapS = SCNWrapMode.repeat
geometry.firstMaterial?.specular.wrapS = SCNWrapMode.repeat
geometry.firstMaterial?.emission.wrapS = SCNWrapMode.repeat
geometry.firstMaterial?.roughness.wrapS = SCNWrapMode.repeat
// 2
geometry.firstMaterial?.diffuse.wrapT = SCNWrapMode.repeat
geometry.firstMaterial?.selfIllumination.wrapT = SCNWrapMode.repeat
geometry.firstMaterial?.normal.wrapT = SCNWrapMode.repeat
geometry.firstMaterial?.specular.wrapT = SCNWrapMode.repeat
geometry.firstMaterial?.emission.wrapT = SCNWrapMode.repeat
geometry.firstMaterial?.roughness.wrapT = SCNWrapMode.repeat
// 3
geometry.firstMaterial?.diffuse.contentsTransform =
SCNMatrix4MakeScale(scaleX, scaleY, 0)
geometry.firstMaterial?.selfIllumination.contentsTransform =
SCNMatrix4MakeScale(scaleX, scaleY, 0)
geometry.firstMaterial?.normal.contentsTransform =
SCNMatrix4MakeScale(scaleX, scaleY, 0)
geometry.firstMaterial?.specular.contentsTransform =
SCNMatrix4MakeScale(scaleX, scaleY, 0)
geometry.firstMaterial?.emission.contentsTransform =
SCNMatrix4MakeScale(scaleX, scaleY, 0)
geometry.firstMaterial?.roughness.contentsTransform =
SCNMatrix4MakeScale(scaleX, scaleY, 0)
}
This defines a method to repeat the texture images over the surface in the X and Y dimensions.
Here’s the breakdown:
-
The method takes an
SCNGeometry
object and the X and Y scaling factors as the input. Texture mapping uses theS
andT
coordinate system which is just another naming convention:S
corresponds toX
andT
corresponds toY
. Here you define the wrapping mode for theS
dimension asSCNWrapMode.repeat
for all the visual properties of your material. -
You define the wrapping mode for the
T
dimension asSCNWrapMode.repeat
as well for all visual properties. With therepeat
mode, texture sampling uses only the fractional part of texture coordinates. -
Here, each of the visual properties
contentsTransform
is set to a scale transform described by anSCNMatrix4
struct. You set the X and Y scaling factors toscaleX
andscaleY
respectively.
You only want to show the floor and ceiling nodes when the user is inside the portal; any other time, you need to hide them. To implement this, add the following method to SCNNodeHelpers
:
func makeOuterSurfaceNode(width: CGFloat,
height: CGFloat,
length: CGFloat) -> SCNNode {
// 1
let outerSurface = SCNBox(width: SURFACE_WIDTH,
height: SURFACE_HEIGHT,
length: SURFACE_LENGTH,
chamferRadius: 0)
// 2
outerSurface.firstMaterial?.diffuse.contents = UIColor.white
outerSurface.firstMaterial?.transparency = 0.000001
// 3
let outerSurfaceNode = SCNNode(geometry: outerSurface)
outerSurfaceNode.renderingOrder = 10
return outerSurfaceNode
}
Taking a look at each numbered comment:
-
Create an
outerSurface
scene box geometry object with the dimensions of the floor and ceiling. -
Add visible content to the box object’s diffuse property so it is rendered. You set the
transparency
to a very low value so the object is hidden from view.
-
Create an
SCNNode
object from theouterSurface
geometry. SetrenderingOrder
for the node to 10. Nodes with a larger rendering order are rendered last. To make the ceiling and floor invisible from outside the portal, you will make the rendering order of the inner ceiling and floor nodes much larger than 10.
Now add the following code to SCNNodeHelpers
to create the portal floor:
func makeFloorNode() -> SCNNode {
// 1
let outerFloorNode = makeOuterSurfaceNode(
width: SURFACE_WIDTH,
height: SURFACE_HEIGHT,
length: SURFACE_LENGTH)
// 2
outerFloorNode.position = SCNVector3(SURFACE_HEIGHT * 0.5,
-SURFACE_HEIGHT, 0)
let floorNode = SCNNode()
floorNode.addChildNode(outerFloorNode)
// 3
let innerFloor = SCNBox(width: SURFACE_WIDTH,
height: SURFACE_HEIGHT,
length: SURFACE_LENGTH,
chamferRadius: 0)
// 4
innerFloor.firstMaterial?.lightingModel = .physicallyBased
innerFloor.firstMaterial?.diffuse.contents =
UIImage(named:
"Assets.scnassets/floor/textures/Floor_Diffuse.png")
innerFloor.firstMaterial?.normal.contents =
UIImage(named:
"Assets.scnassets/floor/textures/Floor_Normal.png")
innerFloor.firstMaterial?.roughness.contents =
UIImage(named:
"Assets.scnassets/floor/textures/Floor_Roughness.png")
innerFloor.firstMaterial?.specular.contents =
UIImage(named:
"Assets.scnassets/floor/textures/Floor_Specular.png")
innerFloor.firstMaterial?.selfIllumination.contents =
UIImage(named:
"Assets.scnassets/floor/textures/Floor_Gloss.png")
// 5
repeatTextures(geometry: innerFloor,
scaleX: SCALEX, scaleY: SCALEY)
// 6
let innerFloorNode = SCNNode(geometry: innerFloor)
innerFloorNode.renderingOrder = 100
// 7
innerFloorNode.position = SCNVector3(SURFACE_HEIGHT * 0.5,
0, 0)
floorNode.addChildNode(innerFloorNode)
return floorNode
}
Breaking this down:
- Create the lower side of the floor node using the floor’s dimensions.
-
Position
outerFloorNode
such that it’s laid out on the bottom side of the floor node. Add the node to thefloorNode
which holds both the inner and outer surfaces of the floor. -
You make the geometry of the floor using the
SCNBox
object initialized with the constants declared previously for each dimension. -
The
lightingModel
of the material for the floor is set tophysicallyBased
. This type of shading incorporates a realistic abstraction of physical lights and materials. The contents for various visual properties for the material are set using texture images from thescnassets
catalog. -
The texture for the material is repeated over the X and Y dimensions using
repeatTextures()
, which you defined before. -
You create a node for the floor using the
innerFloor
geometry object and set the rendering order to higher than that of theouterFloorNode
. This ensures that when the user is outside the portal, the floor node will be invisible. -
Finally, set the position of
innerFloorNode
to sit above theouterFloorNode
and add it as a child tofloorNode
. Return the floor node object to the caller.
Open PortalViewController.swift and add the following constants:
let POSITION_Y: CGFloat = -WALL_HEIGHT*0.5
let POSITION_Z: CGFloat = -SURFACE_LENGTH*0.5
These constants represent the position offsets for nodes in the Y and Z dimensions.
Add the floor node to your portal by replacing makePortal()
.
func makePortal() -> SCNNode {
// 1
let portal = SCNNode()
// 2
let floorNode = makeFloorNode()
floorNode.position = SCNVector3(0, POSITION_Y, POSITION_Z)
// 3
portal.addChildNode(floorNode)
return portal
}
Fairly straightforward code:
-
You create a
SCNNode
object to hold the portal. -
You create the floor node using
makeFloorNode()
defined inSCNNodeHelpers
. You set the position offloorNode
using the constant offsets. The center of theSCNGeometry
is set to this location in the node’s parent’s coordinate system. -
Add the
floorNode
to the portal node and return the portal node. Note that the portal node is added to the node created at the anchor’s position when the user taps the view inrenderer(_ :, didAdd:, for:)
.
Build and run the app. You’ll notice the floor node is dark. That’s because you haven’t added a light source yet!
Now add the ceiling node. Open SCNNodeHelpers.swift and add the following method:
func makeCeilingNode() -> SCNNode {
// 1
let outerCeilingNode = makeOuterSurfaceNode(
width: SURFACE_WIDTH,
height: SURFACE_HEIGHT,
length: SURFACE_LENGTH)
// 2
outerCeilingNode.position = SCNVector3(SURFACE_HEIGHT * 0.5,
SURFACE_HEIGHT, 0)
let ceilingNode = SCNNode()
ceilingNode.addChildNode(outerCeilingNode)
// 3
let innerCeiling = SCNBox(width: SURFACE_WIDTH,
height: SURFACE_HEIGHT,
length: SURFACE_LENGTH,
chamferRadius: 0)
// 4
innerCeiling.firstMaterial?.lightingModel = .physicallyBased
innerCeiling.firstMaterial?.diffuse.contents =
UIImage(named:
"Assets.scnassets/ceiling/textures/Ceiling_Diffuse.png")
innerCeiling.firstMaterial?.emission.contents =
UIImage(named:
"Assets.scnassets/ceiling/textures/Ceiling_Emis.png")
innerCeiling.firstMaterial?.normal.contents =
UIImage(named:
"Assets.scnassets/ceiling/textures/Ceiling_Normal.png")
innerCeiling.firstMaterial?.specular.contents =
UIImage(named:
"Assets.scnassets/ceiling/textures/Ceiling_Specular.png")
innerCeiling.firstMaterial?.selfIllumination.contents =
UIImage(named:
"Assets.scnassets/ceiling/textures/Ceiling_Gloss.png")
// 5
repeatTextures(geometry: innerCeiling, scaleX:
SCALEX, scaleY: SCALEY)
// 6
let innerCeilingNode = SCNNode(geometry: innerCeiling)
innerCeilingNode.renderingOrder = 100
// 7
innerCeilingNode.position = SCNVector3(SURFACE_HEIGHT * 0.5,
0, 0)
ceilingNode.addChildNode(innerCeilingNode)
return ceilingNode
}
Here’s what’s happening:
-
Similar to the floor, you create an
outerCeilingNode
with the dimensions for the ceiling. -
Set the position of the outer ceiling node so that it goes on top of the ceiling. Create a node to hold the inner and outer sides of the ceiling. Add
outerCeilingNode
as a child of theceilingNode
. -
Make
innerCeiling
anSCNBox
object with the respective dimensions. -
Set the
lightingModel
tophysicallyBased
. Also set the contents of the visual properties that are defined by various texture images found in the assets catalog. -
repeatTextures()
wraps the texture images in both the X and Y dimensions to create a repeated pattern for the ceiling. -
Create
innerCeilingNode
using theinnerCeiling
geometry and set itsrenderingOrder
property to a high value so that it gets rendered after theouterCeilingNode
. -
Position
innerCeilingNode
within its parent node and add it as a child ofceilingNode
. ReturnceilingNode
to the caller.
Now to call this from somewhere. Open PortalViewController.swift and add the following block of code to makePortal()
just before the return
statement.
// 1
let ceilingNode = makeCeilingNode()
ceilingNode.position = SCNVector3(0,
POSITION_Y+WALL_HEIGHT,
POSITION_Z)
// 2
portal.addChildNode(ceilingNode)
-
Create the ceiling node using
makeCeilingNode()
which you just defined. Set the position of the center ofceilingNode
to theSCNVector3
struct. The Y coordinate of the center is offset by the Y position of the floor added to the height of the wall.You also subtract
SURFACE_HEIGHT
to account for the thickness of the ceiling. The Z coordinate is set to thePOSITION_Z
offset similar to the floor. This is how far away the center of the ceiling is from the camera along the Z axis. -
Add
ceilingNode
as a child of the portal.
Build and run the app. Here’s what you’ll see:
Time to add the walls!
Open SCNNodeHelpers.swift and add the following method.
func makeWallNode(length: CGFloat = WALL_LENGTH,
height: CGFloat = WALL_HEIGHT,
maskLowerSide:Bool = false) -> SCNNode {
// 1
let outerWall = SCNBox(width: WALL_WIDTH,
height: height,
length: length,
chamferRadius: 0)
// 2
outerWall.firstMaterial?.diffuse.contents = UIColor.white
outerWall.firstMaterial?.transparency = 0.000001
// 3
let outerWallNode = SCNNode(geometry: outerWall)
let multiplier: CGFloat = maskLowerSide ? -1 : 1
outerWallNode.position = SCNVector3(WALL_WIDTH*multiplier,0,0)
outerWallNode.renderingOrder = 10
// 4
let wallNode = SCNNode()
wallNode.addChildNode(outerWallNode)
// 5
let innerWall = SCNBox(width: WALL_WIDTH,
height: height,
length: length,
chamferRadius: 0)
// 6
innerWall.firstMaterial?.lightingModel = .physicallyBased
innerWall.firstMaterial?.diffuse.contents =
UIImage(named:
"Assets.scnassets/wall/textures/Walls_Diffuse.png")
innerWall.firstMaterial?.metalness.contents =
UIImage(named:
"Assets.scnassets/wall/textures/Walls_Metalness.png")
innerWall.firstMaterial?.roughness.contents =
UIImage(named:
"Assets.scnassets/wall/textures/Walls_Roughness.png")
innerWall.firstMaterial?.normal.contents =
UIImage(named:
"Assets.scnassets/wall/textures/Walls_Normal.png")
innerWall.firstMaterial?.specular.contents =
UIImage(named:
"Assets.scnassets/wall/textures/Walls_Spec.png")
innerWall.firstMaterial?.selfIllumination.contents =
UIImage(named:
"Assets.scnassets/wall/textures/Walls_Gloss.png")
// 7
let innerWallNode = SCNNode(geometry: innerWall)
wallNode.addChildNode(innerWallNode)
return wallNode
}
Going over the code step-by-step:
-
You create an
outerWall
node which will sit on the outside of the wall to make it appear transparent from the outside. You create anSCNBox
object matching the wall’s dimensions. -
You set the
diffuse
contents of the material to a monochrome white color and the transparency to a low number. This helps achieve the see-through effect if you look at the wall from outside the room. -
You create a node with the
outerWall
geometry. Themultiplier
is set based on which side of the wall the outer wall needs to be rendered. IfmaskLowerSide
is set totrue
, the outer wall is placed below the inner wall in the wall node’s coordinate system; otherwise, it’s placed above.You set the position of the node such that the outer wall is offset by the wall width in the X dimension. Set the rendering order for the outer wall to a low number so that it’s rendered first. This makes the walls invisible from the outside.
-
You also create a node to hold the wall and add the
outerWallNode
as its child node. -
You make
innerWall
anSCNBox
object with the respective wall dimensions. -
You set the
lightingModel
tophysicallyBased
. Similar to the ceiling and floor nodes, you set the contents of the visual properties that are defined by various texture images for the walls.
-
Finally, you create an
innerWallNode
object using theinnerWall
geometry. Add this node to the parentwallNode
object. By default,innerWallNode
is placed at the origin ofwallNode
. Return the node to the caller.
Now add the far wall for the portal. Open PortalViewController.swift and add the following to the end of makePortal()
just before the return
statement:
// 1
let farWallNode = makeWallNode()
// 2
farWallNode.eulerAngles = SCNVector3(0,
90.0.degreesToRadians, 0)
// 3
farWallNode.position = SCNVector3(0,
POSITION_Y+WALL_HEIGHT*0.5,
POSITION_Z-SURFACE_LENGTH*0.5)
portal.addChildNode(farWallNode)
This is fairly straightforward:
-
Create a node for the far wall.
farWallNode
needs the mask on the lower side. So the default value offalse
formaskLowerSide
will do. -
Add
eulerAngles
to the node. Since the wall is rotated along the Y axis and perpendicular to the camera, it has a rotation of90
degrees for the second component. The wall does not have a rotation angle for the X and Z axes. -
Set the position of the center of
farWallNode
such that its height is offset byPOSITION_Y
. Its depth is calculated by adding the depth of the center of the ceiling to the distance from the center of the ceiling to its far end.
Build and run the app, and you will see the far wall attached to the ceiling on top and attached to the floor on the bottom.
Next up you will add the right and left walls. In makePortal()
, add the following code just before the return portal
statement to create the right and left side walls:
// 1
let rightSideWallNode = makeWallNode(maskLowerSide: true)
// 2
rightSideWallNode.eulerAngles = SCNVector3(0, 180.0.degreesToRadians, 0)
// 3
rightSideWallNode.position = SCNVector3(WALL_LENGTH*0.5,
POSITION_Y+WALL_HEIGHT*0.5,
POSITION_Z)
portal.addChildNode(rightSideWallNode)
// 4
let leftSideWallNode = makeWallNode(maskLowerSide: true)
// 5
leftSideWallNode.position = SCNVector3(-WALL_LENGTH*0.5,
POSITION_Y+WALL_HEIGHT*0.5,
POSITION_Z)
portal.addChildNode(leftSideWallNode)
Going through this step-by-step:
-
Create a node for the right wall. You want to put the outer wall on the lower side of the node so you set
maskLowerSide
totrue
. - You set the rotation of the wall along the Y axis to 180 degrees. This ensures the wall has its inner side facing the right way.
-
Set the location of the wall so that it’s flush with the right edge of the far wall, ceiling and floor. Add
rightSideWallNode
as a child node ofportal
. -
Similar to the right wall node, create a node to represent the left wall with
maskLowerSide
set totrue
. - The left wall does not have any rotation applied to it, but you adjust its location so that it’s flush with the left edge of the far wall, floor and ceiling. You add the left wall node as a child node of the portal node.
Build and run the app, and your portal now has three walls. If you move out of the portal, none of the walls are visible.
Adding the Doorway
There’s one thing missing in your portal: an entrance! Currently, the portal does not have a fourth wall. Instead of adding another wall, you will add just the necessary parts of a wall to leave room for a doorway.
Open PortalViewController.swift and add these constants:
let DOOR_WIDTH:CGFloat = 1.0
let DOOR_HEIGHT:CGFloat = 2.4
As their names suggest, these define the width and height of the doorway.
Add the following to PortalViewController
:
func addDoorway(node: SCNNode) {
// 1
let halfWallLength: CGFloat = WALL_LENGTH * 0.5
let frontHalfWallLength: CGFloat =
(WALL_LENGTH - DOOR_WIDTH) * 0.5
// 2
let rightDoorSideNode = makeWallNode(length: frontHalfWallLength)
rightDoorSideNode.eulerAngles = SCNVector3(0,270.0.degreesToRadians, 0)
rightDoorSideNode.position = SCNVector3(halfWallLength - 0.5 * DOOR_WIDTH,
POSITION_Y+WALL_HEIGHT*0.5,
POSITION_Z+SURFACE_LENGTH*0.5)
node.addChildNode(rightDoorSideNode)
// 3
let leftDoorSideNode = makeWallNode(length: frontHalfWallLength)
leftDoorSideNode.eulerAngles = SCNVector3(0, 270.0.degreesToRadians, 0)
leftDoorSideNode.position = SCNVector3(-halfWallLength + 0.5 * frontHalfWallLength,
POSITION_Y+WALL_HEIGHT*0.5,
POSITION_Z+SURFACE_LENGTH*0.5)
node.addChildNode(leftDoorSideNode)
}
addDoorway(node:)
is a method that adds a wall with an entrance to the given node
.
Here’s what you’re doing:
- Define constants to store the half wall length and the length of the front wall on each side of the door.
-
Create a node to represent the wall on the right side of the entrance using the constants declared in the previous step. You also adjust the rotation and location of the node so that it’s attached to the front edge of the right wall, ceiling and floor. You then add
rightDoorSideNode
as a child of the givennode
. -
Similar to step 2, you create a node for the left side of the doorway, and set the rotation and location of
leftDoorSideNode
so that it is flush with the front edge of the left wall, ceiling and floor nodes. Finally, you useaddChildNode()
to add it as a child node tonode
.
In makePortalNode()
, add the following just before return portal
:
addDoorway(node: portal)
Here you add the doorway to the portal node.
Build and run the app. You’ll see the doorway on the portal, but the top of the door is currently touching the ceiling. You’ll need to add another piece of the wall to make the doorway span the pre-defined DOOR_HEIGHT
.
Add the following at the end of addDoorway(node:)
:
// 1
let aboveDoorNode = makeWallNode(length: DOOR_WIDTH,
height: WALL_HEIGHT - DOOR_HEIGHT)
// 2
aboveDoorNode.eulerAngles = SCNVector3(0, 270.0.degreesToRadians, 0)
// 3
aboveDoorNode.position =
SCNVector3(0,
POSITION_Y+(WALL_HEIGHT-DOOR_HEIGHT)*0.5+DOOR_HEIGHT,
POSITION_Z+SURFACE_LENGTH*0.5)
node.addChildNode(aboveDoorNode)
- Create a wall node with the respective dimensions to fit above the entrance of the portal.
-
Adjust the rotation of
aboveDoorNode
so that it’s at the front of the portal. The masked side is placed on the outside. -
Set the position of the node so that it’s placed on top of the doorway that you just built. Add it as a child node of
node
.
Build and run. This time you’ll notice the doorway is now complete with a proper wall.
Placing Lights
That portal doesn’t look too inviting. In fact, it’s rather dark and gloomy. You can add a light source to brighten it up!
Add the following method to PortalViewController
:
func placeLightSource(rootNode: SCNNode) {
// 1
let light = SCNLight()
light.intensity = 10
// 2
light.type = .omni
// 3
let lightNode = SCNNode()
lightNode.light = light
// 4
lightNode.position = SCNVector3(0,
POSITION_Y+WALL_HEIGHT,
POSITION_Z)
rootNode.addChildNode(lightNode)
}
Here’s how it works:
-
Create an
SCNLight
object and set its intensity. Since you’re using thephysicallyBased
lighting model, this value is the luminous flux of the light source. The default value is 1000 lumens, but you want an intensity which is much lower, giving it a slightly darker look. - A light’s type determines the shape and direction of illumination provided by the light, as well as the set of attributes available for modifying the light’s behavior. Here, you set the type of the light to omnidirectional, also known as a point light. An omnidirectional light has constant intensity and a direction. The light’s position relative to other objects in your scene determines its direction.
-
You create a node to hold the light and attach the
light
object to the node using itslight
property. -
Place the light at the center of the ceiling using the Y and Z offsets and then add
lightNode
as a child of therootNode
.
In makePortal()
, add the following just before return portal
.
placeLightSource(rootNode: portal)
This places the light source inside the portal.
Build and run the app, and you’ll see a brighter, more inviting doorway to your virtual world!
Where to Go From Here?
The portal is complete! You have learned a lot through creating this sci-fi portal. Let’s take a quick look at all the things you covered in this tutorial series.
- You have a basic understanding of SceneKit’s coordinate system and materials.
-
You learned how to create
SCNNode
objects with different geometries and attach textures to them. - You also placed light sources in your scene so that the portal looked more realistic.
Going forward, there are many changes you can make to the portal project. You can:
- Make a door that opens or shuts when the user taps on the screen.
- Explore various geometries to create a room that spans infinitely.
- Experiment with different shapes for the doorway.
But don’t stop here. Let your sci-fi imagination run wild!
If you enjoyed what you learned in this tutorial, why not check out our complete book, ARKit by Tutorials, available on our online store?
ARKit is Apple’s mobile AR development framework. With it, you can create an immersive, engaging experience, mixing virtual 2D and 3D content with the live camera feed of the world around you.
If you’ve worked with any of Apple’s other frameworks, you’re probably expecting that it will take a long time to get things working. But with ARKit, it only takes a few lines of code — ARKit does most of the the heavy lifting for you, so you can focus on what’s important: creating an immersive and engaging AR experience.
In this book, you’ll create five immersive and engaging apps: a tabletop poker dice game, an immersive sci-fi portal, a 3D face-tracking mask app, a location-based AR ad network, and monster truck simulation with realistic vehicle physics.
To celebrate the launch of the book, it’s currently on sale as part of our Game On book launch event. But don’t wait too long, as this deal is only good until Friday, June 8th!
If you have any questions or comments on this tutorial, feel free to join the discussion below!
The post Building a Portal App in ARKit: Materials and Lighting appeared first on Ray Wenderlich.