Hardcoded Attribute Names Removal [Proposal]

This proposal explores one option for the future of explicitly named attributes in geometry nodes. It is part of T90864.

Background

In the fields proposal the need for typing explicit attribute names is significantly reduced compared to what we have in master. However, explicit attribute names are still required for interoperability with different systems.

  • Access attributes that are available on the original geometry (e.g. vertex groups and uv maps).
  • Access attributes on “external” geometries (e.g. other objects or geometries imported from files).
  • Make attributes available for other systems (e.g. other objects, render engines or external software)

The main question is: Where should users specify attribute names? For reference, in Blender 2.93 attribute names had to be specified in many nodes. In the current fields prototype, one would mainly access named attributes with the Attribute and Store Persistent Attribute node (which have a string input). In both cases, attribute names could be specified everywhere in geometry nodes.

A Different Answer

This proposal has a different answer to the question: Attribute names should only be specified where geometry enters geometry nodes and where it exits geometry nodes.

The main advantage of not allowing nodes to access named attributes everywhere is that node groups become easier to compose. One does not have to fear name collisions inside of geometry nodes.

Geometry can enter geometry nodes in a few places:

  • Geometry that is passed in by a modifier.
  • Object Info node.
  • Collection Info node.
  • Nodes that read a geometry from a files (in the future).

Currently, geometry only exits geometry nodes only through the modifier. In the future we might be able to write geometries to a file on disk with a node.

Implications

This different approach to attribute names has a few implications for the design of geometry nodes.

Object/Collection Info

The Object Info and Collection Info import new geometry into geometry nodes. Therefore, both nodes also have to allow the user to specify attribute names. Besides the already existing outputs, users have to be able to dynamically add field outputs. Each of these outputs would have an editable attribute name and socket type.

Object/Collection Socket

The object/collection input sockets in these nodes have to be removed as well. They allowed separating the place where the geometry source is specified from where the attribute names are defined. Instead, the nodes get an object/collection input directly in the node, without a socket.

image

Exposing Objects and Collections

Obviously, now we’ve lost the ability to expose the object to the parent node group or modifier. So that functionality has to be added back. This can be done by adding an “Expose” operator to the Info nodes. Executing this operator would do a few things:

  1. Copy the Info node right before every Group node that calls the current node group.
  2. Create a new group input for every output of the Info node that is used.
  3. Remove the Info node from the current node group.
  4. Link the newly created sockets in all modified node groups.
  5. If the node group was used by a modifier, also update the modifier inputs accordingly. More about that below.

Note, the current node group will not have an object input, it will have a geometry input instead. The conversion from object to geometry happens one level higher now.

Point Instance

Besides the Info nodes, the Point Instance node is the only one that also uses object and collection sockets. Since those sockets are removed, the node has to get a second geometry input instead. One geometry determines where to instance and one determines what to instance.

Just this change would not be enough to get all functionality back though. What is missing is the ability to instance a different geometry on every point. Fortunately, there is a rather nice solution to that, which would also solve an additional problem. The solution is to allow instancing the instances in the second geometry separately.

The nice benefit of this solution is that instances in a geometry are actually ordered properly and therefore they can be identified by an index. Objects and sub-collections inside a collection do not have a well defined order that this node could depend on. Note, the separate ID input is still necessary, but maybe it should be renamed to “Stable ID”. It’s important for proper motion blur etc.

image

Both the “Use Instance Index” and “Instance Index” inputs can also be fields. Instead of having two separate inputs, one could also say that an instance index of -1 means that the entire geometry should be instanced (which would be the default then).

In order to use this, the collection info node should have a new boolean input that specifies whether the collection becomes a single instance in the output geometry, or whether all sub-elements in the collection should become their own instance.

Modifier

The modifier ui has to become a bit more complex/flexible as well.

  • Every time one had exposed an object to the modifier before, one now has to expose a geometry socket. Therefore, the modifier has to be able to provide multiple geometries to the node group. Furthermore, one would have to choose between original/relative in the modifier (same as in the Info nodes).
  • Also it has to be possible to pass the position of another object to the modifier. Again, either in original or relative space.
  • The modifier is a place where geometry enters and exits geometry nodes. So one also has to be able to specify attribute names for inputs and outputs in the modifier.

Evaluation

As can be seen, it is possible to only specify attribute names where geometry enters and exits geometry nodes. This design would make attribute name collisions practically impossible and therefore forces users to create node groups that are more reuseable and composable.

There are also some downsides to this approach, because it does add a new limitation to enforce the guarantee mentioned above.

  • Doing versioning for geometry nodes node groups created in Blender 2.93 becomes significantly harder and maybe impossible to do well. We could add some special nodes that deal with named attributes, that can only be added by versioning code and not by users. However, these nodes break the guarantee, even if they are just created by versioning code.
  • This proposal might lead to more attribute name duplication in a .blend file, because the same attribute name might have to be specified in multiple modifiers instead of once in a node group. I makes assumptions about where it makes sense to specify attribute names that might not be correct for all use cases.
  • Exposing objects and collections to the parent node group becomes different from exposing other data.
  • It becomes impossible to remove named attributes in geometry, which might be important for optimization purposes (since all named attributes on a geometry are propagated in most nodes).

I should note that we can get the nice aspects of this proposal (except for the guarantee that no node group will use hardcoded attribute names on geometry passed in from somewhere else), without any of the downsides. It is possible to statically analyze a node group to see if it uses nodes that might lead to attribute name collisions. We could somehow show in the ui when a specific node group is guaranteed not to use hardcoded attribute names (i.e. it is “side effect free” in some sense).

5 Likes

This would lead to many possibilities

4 Likes

I have mixed feelings about this at best. I don’t think that benefits of thins like “One does not have to fear name collisions inside of geometry nodes.” outweigh the significant drawbacks of limitations such as “Obviously, now we’ve lost the ability to expose the object to the parent node group or modifier.”.

Geometry nodes, as the name already implies, are mainly node based solution, so I think that sacrificing the consistency of node based workflow, where even object references can be passed though node links, and therefore easily instanced, with obvious visual representation of such instancing, is not worth it.

Furthermore, I personally think that being able to use named attributes as solution to long node links is actually a good idea. Being forced to only use them like we have to in 2.93 is horrible of course, but being able to use them optionally works.

In Unreal Engine for example, one is able to promote any data to a variable anywhere in the Blueprint execution flow and refer to that data later by getting that variable in form of a simple, one-socket input node. It allows for extremely clean node networks even if those node networks do extremely complex things with many nested layers of node groups/functions. And it’s been a production proven design for almost a decade now.

I don’t see why this could not work the same way in GN. Yes, one has to be careful with the naming, but let’s be honest, how many times will you ever want to dig deep into someone’s else node network and add your own functionality without actually knowing how it works. It’s hard to imagine. And by having to know how it works you will most likely familiarize yourself with attribute names which are already in use, and will only rarely run into an error.

In general, I am just afraid this introduces an ugly precedent of making geometry nodes less node based.

What’s even more scary is to imagine increasing amount of complex operators that would be doing multiple obscure steps under the hood, such as:

  1. Copy the Info node right before every Group node that calls the current node group.
  2. Create a new group input for every output of the Info node that is used.
  3. Remove the Info node from the current node group.
  4. Link the newly created sockets in all modified node groups.
  5. If the node group was used by a modifier, also update the modifier inputs accordingly. More about that below.

The more the complexity of these node networks grows, the less predictable and more error prone will such operators be.

There are two possible outcomes:

  1. There will be more wonky operators like this in future, after this precedent is set. In that case, if users have to rely on them, they will more often than not keep wrecking their already neatly cleaned up node graphs by these kinds of operators. Getting used to the fact that if you lay out your node network neatly, sooner or later you will have to use one of this kind of operator and have to do the whole clean up again will become a frustrating norm.

  2. This one operator will be it, and no more like this one will appear in future. In that case, everyone will be asking for the rest of eternity “Why is there this one weird exception? Why can’t we pass objects and collections like literally everything else?” And the answer will be the usual, frustrating, C++ style “It’s because of legacy reasons, because someone made this one weird decision long time ago”.

7 Likes

I completely agree, it doesn’t seem to me like the drawbacks are worth it. It complicates the UI for the sake of avoiding name collisions, users “just” have to observe “good practices” to avoid those -adding namespaces themselves or so- and it won’t be a problem.

4 Likes

I don’t see inside. I am not C++ level Blender developer. But somehow I still feel that some mechanism for user to find out whether given name is already used somewhere in the GN node tree should not be that hard to realize.

1 Like

Ok, I need to wrap my head around the whole proposal, but I particularly agree with this point:

For the same reasons given in the UE example. Never used that, but with geo nodes I can already
Guess that the 2.93 way of handling attributes should be not encouraged ( conversely using fields and anonymous sockets should be encouraged), but should stay and be used when convenient.

2 Likes

@jacqueslucke @LudvikKoutny I’m trying to wrap my head around the names collisions issue. I’m not sure I’ve clear in my mind how and when names collision would happen, and what consequences they could have (my fault of course, lack of experience with nodes). I’m trying to create a scenario with field branches, but I’m not sure this is what you mean.

Let me see if I understood the problem with an example:

Let’s say i add modifier that creates a “density” attribute to scatter mushrooms on the terrain mesh, maybe I didn’t create the modifier myself, so I don’t even bother what happens inside, I just apply it:

Then I add another modifier made by another guy that creates the “density” attribute again:

Is that a collision scenario? If I edit the ramp in the first modifier, I can clearly see that the trees glitch, I guess because of the same attribute name being used.

If it’s the case, to solve the Issue, may be a visibility scope rule system a solution? Similarly to C language visibility scope for variables inside functions or code blocks.

For example, the name of the attribute “density” only has meaning inside the current node tree or group, and if one wants to import an external attribute, the attribute needs to be explicitly passed as an argument and even have new local name being assigned to it.

Let me know what you think about this Idea, and If I’m overseeing important aspects that makes this not practical.

I read your proposal again @jacqueslucke , I finally figured out what the object and collection sockets are used for (I didn’t realize before), and so far, I completely agree with the first @LudvikKoutny answer. Losing the ability to expose the collection or object to the parent group is really not worth it, and the operator idea sounds really cumbersome and a sort of a hack to me.

1 Like

We talked about such scoping rules in the past. However, that is not really necessary anymore due to anonymous attributes. When named attributes are used, we generally do want that they can be used outside of the current scope. That is why we name them in the first place.

In your example, you indeed have a name collision: A node group created by someone else uses named attributes and is accidentally overwriting your attribute. In that specific case, the node group should just have used anonymous attributes internally. If it wanted to expose an attribute to you, it should have allowed you to choose the name.

So, generally speaking, when you build a node group that others should be able to use without worrying about attribute name collisions, then you should not use named attributes inside the node group. However, when working at a higher level that is specific to your current project (or when you just want to get something to work and care about sharability later), it seems perfectly reasonable to assign the names you want to use with nodes (e.g. when preparing geometry for export). Named attributes are just a fundamental part of geometry to me, not being able to work with them in geometry nodes feels very limiting. As mentioned before, it would be possible to determine if a node group might be less sharable just by checking if it uses any generic set/get nodes for named attributes.

6 Likes

The object/collection input sockets in these nodes have to be removed as well

I understand the problem. There would be no way to know from within the GN nodes which attributes are available inside an object. Each object could have a different set of attributes.

Would there be a way to have ‘attribute classes’ or ‘attribute traits’ (a set of attributes, associated with a label)?
An example of a class could be something like “Basic”, which associates with attributes (uv map | position ).

Any object that fulfills “Basic” does have attributes ‘uv map’ and ‘position’. (An unwrapped mesh would fulfill “Basic”. An empty object or a text object won’t fulfill “Basic”.)

Given a class system like that …
If an ‘object info’ node could specify a list of required classes (an error for any input object which does not conform to required classes), there might not be the need anymore to select specific objects from within GN nodes.

Thanks for answering!

That makes sense now that I think about it.

Yeah, of course I created the nodes like that on purpose to have a collision scenario, I couldn’t figure out a more realistic/plausible situation to simulate a collision, sorry :slight_smile:

Anyway I’ll try to better grasp the whole problem and give my feedback if I can.
Do you think It will be feasible to have quick prototypes (like you guys did for fields and expandable sockets) for users to figure out the pros and cons of a possible solution?

BTW… great job so far! I can’t wait to see fields merged in master with the new nodes from GSOC and the latest goodies!

I’m not sure if I understand the proposal, but has this been forgotten?
https://developer.blender.org/D11276
Or is it no longer worth improving?

I am not completely understanding the implications of this proposal. Does this mean that there will be no named persistent attributes at all? So for instance, if I used an attribute sample texture node to create a “mynoise” attribute at the start of a node graph. How would I get access to this attribute at the very end of a long chain for reuse of this data? Would I have to have a giant noodle stretched out to the end of the graph to achieve this? This can get messy fast.

I feel that the current fields prototype (using the set persistent attribute node) delivers that nice middle ground between the old 2.93 way and the new fields approach, and is very flexible. It feels like a mashup of sops and vops in the same graph, which could potentially get very messy, but is definitely a fresh take on nodal workflow. Would be sad to see this go just because of name collisions, which is really the responsibility of the artist.

Can’t there just be an node error that shows up on the node, indicating that this attribute name has already been taken, as would happen if you were coding the traditional way?

2 Likes

I don’t know if I like or dislike this proposal, but I do have something I want to say.
A friend of mine discussed with me his idea of making Fields nodes backward compatible (besides my previous idea about just let the old nodes working without showing in Shift A menu), is to automatically have the old nodes convert to some equivalent node groups. I also experimented a little bit and it’s totally doable:

But with this proposal,

I think this means this idea of Node Group for backward capabitilty just not possible anymore?

5 Likes

Is the attribute name collision issue regarding multiple GN modifiers on the same object? Are there any other cases where this might be an issue?

Yeah, I am not sure I understand as well. Would like to know how does this actually impact the workflow from a user point of view.

Isn’t there a way to have attributes created from a geometry modifier to live in a correspondingly named namespace ? referring to them down the line would necessitate writing out the namespace but only if there actually is a naming conflict

Touché
:zipper_mouth_face:

1 Like