Instance Level Transforms

There are a few attributes, on both RdlInstanceGeometry and TransformSpaceMap, that allow you to transform to and from instance spaces. This guide goes over why and how you would do this.

What is a Space?

A space is a frame of reference some point or vector is defined in. It is a coordinate system composed of x, y, and z basis vectors, and coordinates are defined relative to that space.

See the example below, where we’re animating the position of the sphere. The texture coordinates are defined relative to world space. Notice that the object swims through the texture.

In object space, coordinates are relative to some object’s local XYZ axes. Below you will see the same example, but where texture coordinates are defined relative to the geometry node’s node_xform. Notice that the texture moves with the object.

Object space does not work with instances, since instances have their own specific transforms. See below, where we have a number of sphere instances, but where the texture coordinates are defined relative to object space.

Set Instance Levels on Geometry

On RdlInstancerGeometry, you will notice that we have an attribute called instance_level, which has the following options:

  • “instance level 0”
  • “instance level 1”
  • “instance level 2”
  • “instance level 3”
  • “instance level 4”

This allows you to mark the level or depth of the instance geometry.

Render to Instance Space

In order to transform coordinates to instance level space, we can use a TransformSpaceMap, which allows you to transform a point or vector to/from the desired spaces. We can then take the transformed points and connect the output to a texture coordinates attribute.

instancer = InstanceGeometry("instancer") {
    ["instance_level"] = "instance level 0"

amP = AttributeMap("amP") {
    -- position is in render space
    ["map_type"] = "position"

-- transform the points from the AttributeMap to instance level 0 space
tsmSpheres = TransformSpaceMap("tsmSpheres") {
    ["input"] = bind(amP),
    ["input_type"] = "point",
    ["from_space"] = "render",
    ["to_space"] = "instance level 1"

-- use these transformed points as the texture coordinates
noiseMapColor = NoiseMap("noiseMapColor") {
    ["space"] = "input texture coordinates",
    ["input_texture_coordinates"] = bind(tsmSpheres),

    ["frequency_multiplier"] = 4,
    ["max_level"] = 5,
    ["use_smoothstep"] = true,
    ["smoothstep"] = Vec2(0.2, 0.8)

If we look at the example from before, we’ll notice that transforming the space to “instance level 1” prevents the texture from “swimming” like it was in object space.

Maps with Bindable Input

The following maps have bindable input positions and/or normals:

  • NoiseMap_v2 and NoiseWorleyMap_v2
      ["space"] = "input texture coordinates",
      ["input_texture_coordinates"] = bind()
  • ProjectTriplanarMap_v2 and ProjectTriplanarNormalMap_v2
      ["input_position_source"] = "input_position/input_normal",
      ["input_position"] = bind(),
      ["input_normal"] = bind()

Instance Level 1 and Beyond

When adding a second level of instancing (“instance level 1”), the texture on the spheres still sticks. Why does this happen?

TransformSpaceMap concatenates multiple instance levels by default.

  • to_space concatenates all of the levels above the specified level. For example, if to_space is “instance level 0”, it would concatenate levels 0, 1, 2, 3, and 4.
  • from_space concatenates all of the levels below the specified level. For example, if from_space is “instance level 4”, it would concatenate levels 4, 3, 2, 1, and 0.
  • concatenate_instance_level_transforms allows you to enable/disable this behavior (on by default)