Following my previous article on working with ARKit and SceneKit, we at Black Pixel have had the opportunity to dig into some of the less defined features of the SceneKit editor. If you’re working with 3D in iOS, these tips may save you from a headache or two.
There is no apparent way to lock a node’s position in the SceneKit editor. Since you’ll be navigating the viewport by clicking and dragging, it’s very easy to grab a node by accident and bump it out of position. You can make a mess of your scene very quickly just by panning around. One way to deal with this is to anchor every node at 0, 0, 0 with a rotation of 0, 0, 0. That way, if an object gets out of place, you’ll know exactly how to correct it. This requires positioning each object’s axis origin at the scene origin, and will have to be done in your modeling tool of choice.
For Cinema 4D, there are scripts that make this very easy. The one I use comes from Robert Gruenweiss.
Zeroing out every object’s rotation origin will be less than ideal if you want parts of your scene to animate independently of the rest; think of a car door that opens while the car remains stationary. If such is the case for you, leave the origin position alone where you must, but make a note somewhere of its correct values. You’ll need them.
It’s also a good idea to build your objects so they’re correctly sized when at a SceneKit scale of 1, 1, 1. (User interaction might affect an object’s scale, but the baseline should be 1, 1, 1 .) Again, if you accidentally resize a node in the editor, you’ll know what the correct scale should be.
Limiting Lights with Category Bit Masks
The default behavior for a SceneKit light is to illuminate all nodes within its range. This is usually what you want, but occasionally there’s a reason to tie a light to a specific node or group of nodes. You can do this with a thing called a “category bit mask.”
The concept of a bit mask is too arcane for our purposes here, but a category bit mask is actually pretty easy to understand. A category bit mask is a number that simply allows you to sort SceneKit objects into categories.
If you change a light’s category bit mask to something other than –1 (the default, unrestricted illumination) or 0 (illuminate nothing), you’ve assigned it a category. The light will now only illuminate nodes whose category bit mask match its own.
However, you can’t just use any arbitrary integer. To assign a category properly, you must use only powers of 2: 2, 4, 8, 16, etc. (1 is technically also a power of 2, being 2 to the 0th power, but SceneKit nodes default to a category bit mask of 1, so it’s best to use other values.)
The reason for this becomes more clear if you express the powers of 2 in binary.
00000001 // 1
00000010 // 2
00000100 // 4
00001000 // 8
00010000 // 16
00100000 // 32
01000000 // 64
10000000 // 128
As you can see, in these binary values the 1 bits never share the same columns. Using integers other than powers of 2 would produce binary values whose bits overlap, resulting in overlapping categories. You could technically use this quirk to your advantage, but you may find it harder to anticipate the results.
Unsurprisingly, limiting lights with category bit masks can improve performance, as explained by Apple:
The rendering performance cost of dynamic lighting increases with the number of lights affecting a node — you can reduce this performance cost by using category bit masks to limit the number of lights illuminating each node.
A light with category bit mask of –1 shines on everything.
A light with category bit mask of 0 shines on nothing.
A light with a category bit mask that’s a power of 2 shines only on objects that share its category bit mask.
Nodes default to a category bit mask of 1, so avoid setting a light’s category bit mask to 1.
Important: Objects’ category bit masks should be set in the Node inspector. Lights’ category bit masks should be set in the Attributes inspector. Lights also have a category bit mask in the Node inspector, but this does not control which objects the light illuminates.
Finally, ambient lights shine on everything regardless of the category bit mask you set.
Multiple UV Channels
3D apps make it possible to restrict materials to subsections of an object and pin materials to different points along the surface, known as UV mapping. Assuming you use the COLLADA 1.4 format (.dae) to import your scene to SceneKit, it will generally handle all of this correctly.
Object subsections will be represented in the Attributes inspector as “geometry elements,” and UV maps will be represented as “texture coordinates.” However, in the Materials inspector the latter is referred to as the “UV channel.”
The number of “geometry elements” line items seems to correspond exactly to the number of “texture coordinates” line items. To use multiple UV maps, you may need to assign each material to a subsection of your object’s polygons. In Cinema 4D, for example, this is done by saving a polygon selection with Set Selection and then assigning that selection to a material tag in its Selection field.
The subsection that each material affects is determined by the order of your materials. The first material in the list affects the first geometry element, the second affects the second, and so on. On the other hand, the texture coordinates used to map the material are controlled by the UV channel property. Each set of coordinates is assigned a numeric index, beginning with 0. Generally SceneKit will assign the UV channel correctly, but if your material isn’t lining up as expected, try changing this value.
One thing that SceneKit doesn’t handle so well: decals with alpha channels. You can assign an image with alpha channel to a material’s diffuse property, but the alpha channel will affect the visibility of the material as well as the underlying geometry. This is great for accomplishing certain effects, but it can be frustrating when trying to composite irregularly shaped images onto the same object.
It’s possible to solve this with shader code, but you have a couple workarounds available without going that far.
Work-around 1: Duplicate a peel of the object’s geometry in your 3D app, and shift it away from the main object by a hair. Apply the decal to this smaller subsection. The image’s alpha channel will hide the subsection edges, making it appear like a seamless part of the larger geometry.
Work-around 2: In an image editor, remove the alpha channel and fill the decal’s empty area with the color of the material underneath it. Make sure the decal material’s other properties match those of the underlying material as well.
In either case, you’ll want to set the WrapS and WrapT properties to Clamp, rather than Repeat, to avoid tiling the image.
(By the way, image Scale doesn’t work the way you’re probably thinking. Increasing either of these values will actually make the image smaller in that dimension. Think of the Scale setting as how many times you’d like the image to be able to repeat within the same unit of space.)
Download the example project to see examples of a restricted light source, multiple UV maps, and both decal methods.