Featured image of post Houdini to Blender: Custom Attributes

Houdini to Blender: Custom Attributes

Hacking Alembic to work around Blender's limitations on custom attributes

The Problem

As much as I love Blender, there’s no denying it’s somewhat lacking in features when it comes to Alembic files and working nicely with other softwares. Blender’s alembic import has a few major issues, but by far the most annoying is that it cannot bring in arbitrary geometry attributes.

Existing Solutions

If you have come up against this issue before, there is a decent chance you have come across this video by Entagma:

While this method is certainly one way of working around this, There are a few things that bother me:

  • Putting everything in to vertex color limits precision to single byte values (0-255)

  • We need to export an alembic file per attribute (Yuck!)

Surely we can do better than this…!

Solution

UV Sets

While Blender now has support for arbitrary attributes on its own meshes, if you export this mesh to alembic and try to bring it back in, the data will be lost. This is true for any additional vertex color sets, as well as arbitrary attributes.

However, I noticed that the alembic importer is able to bring in additional UV sets with no issues! I guess UVs could be considered more important than arbitrary attributes, so thats why they got more focus? This is when I had the devilish idea to just slam all attributes in as UVs. I actually really like this method, however it has a few small issues:

  • All attributes need to be stored as Vector2. So Vector3 attributes of course wont fit in to a single set, and single float attributes will take up a bit of extra space.

  • UV coordinates dont interpolate on subframes

  • UV coordinates wont update per frame if the mesh doesnt change

But all in all, I think this is the best solution compared to other hacky ways I’ve considered!

Converting Attributes to UVs

When we are converting attribs, we need to keep in mind that we only have space for a Vector2. Due to this, we will be checking the size of a given attribute, and if it is too big, we will split it up in to multiple UV sets.

For the sake of this example, I wrote some vex that can do this conversion in a single node, if you want something to just copy and paste! Just put it in a detail wrangle and change the attribute names

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
string attributes[] = split("my_custom_attribute my_vector_attribute", " ");
foreach(string attribute; attributes){
    int size = attribsize(0, "point", attribute);
    int iterations = 1;
    
    // If the size of this attrib wont fit in Vector2 we will need to seperate its components
    if(size > 2) {
        iterations = size;
    }
    
   
    for(int axis = 0; axis < iterations; axis++){
    
        string destination_attr_name = attribute;
        
        // If we are seperating components, add a suffix to the attr name
        if(iterations != 1) {
            destination_attr_name = destination_attr_name + "_" + itoa(axis);
        }

        for(int i = 0; i < npoints(0); i++) {
    
            int verts[] = pointvertices(0, i);

            foreach(int vert; verts) {
  
                vector2 value = point(0, attribute, i);
                
                // if seperating components, grab the current component value
                if(iterations != 1) {
                    float values[] = point(0, attribute, i);
                    value = values[axis];
                }
                
                // zero out the extra vec2 part if it is not necessary - maybe helps with file compression?
                if(size == 1 || iterations != 1) {
                    value[1] = 0;
                }
                
                setvertexattrib(0, destination_attr_name, vert, -1, value);
            }
        }
        
        // Make sure the vertex attrib type is 'texturecoord' or it wont work!
        setattribtypeinfo(0, "vertex", destination_attr_name, "texturecoord");
    }
}

This vex isn’t super fast though, and if we are okay to use a few more nodes, we can do better!

Optimising

We can make this a lot faster mainly by reducing the amount of iterations we do in that loop with some extra nodes

Optimised Setup

Note that I am merging in some constantly moving geo. This is to workaround the fact that Blender wont have animated UV sets if the geo itself isnt deforming!

First we just promote the point attributes to vertex attributes, so we can avoid doing that in vex.

Then we delete the attributes we just promoted, so we can create them again in the next wrangle (This might seem weird but its just so we can ensure that the attributes are of type Vector2). We are grabbing the vertex attribute again from the second input on the wrangle

Next we have a vertex wrangle with the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//
// "convert_attrs_to_vector2" node's code:
//

// get list of attributes
string attrs[] = split(chs("attr_names"), " ");

foreach(string attr; attrs) {

    int size = attribsize(1, "vertex", attr);
    
    int iterations = 1;
    
    // If the size of this attrib wont fit in Vector2 we will need to seperate its components
    if(size >= 3) {
        iterations = size;
    }
    
    for(int axis = 0; axis < iterations; axis++){
        string destination_attr_name = attr;
        // If we are seperating components, add a suffix to the attr name
        if(iterations != 1) {
            destination_attr_name = destination_attr_name + "_" + itoa(axis);
        }
        
        vector2 value = vertex(1, attr, @vtxnum);
         
        // if seperating components, grab the current component value
        if(iterations != 1) {
            float values[] = vertex(1, attr, @vtxnum);
            value = values[axis];
        }
    
        // zero out the extra vec2 part if it is not necessary - maybe helps with file compression?
        if(size == 1 || iterations != 1) {
            value[1] = 0;
        }
        
        setvertexattrib(0, destination_attr_name, @vtxnum, -1, value);
        setattribtypeinfo(0, "vertex", destination_attr_name, "texturecoord");
    }
}

If you’ve followed that correctly, you should end up with the same results as you would with that original vex, but should be more responsive now ^-^

Exporting Alembic

The only thing left to do now is to export the geo to alembic file. This is pretty much the same as usual, but you need to make sure you specify Additional UV Attributes

Alembic ROP Output

Example

Lets show it off!

Here is a basic Houdini setup which will output a mesh with a single float attribute, and a vector3 attribute.

Example setup

Here you can see how the vertex attributes should show up

Vertex Attributes

Lets export and bring this bad boy in to Blender!

Blender Import

Beautiful! We have all the UV sets and can bring them easily in to the shader without any further processing!

Built with Hugo
Theme Stack designed by Jimmy