It took me a while to get started on my second blog post, blame the bugs! After fixing the showstoppers now, i was able to create a new demo video for you to enjoy. I will explain the new features below. Forgive my unorganized talk and my funny german accent ;)
The most important new feature i implemented is the ability to handle data sets as opposed to single data elements (subsequently called “singletons”). To give you an idea of what this means, let’s first take a brief look at how current node trees work.
Shader- and texture node trees are used to calculate the values of a render- or texture sample respectively. They are basically functional expressions used to calculate a set of singleton values. The input to these trees (e.g. the “Geometry” node in shader trees) consists of exactly one value per socket:
- the UV coordinates of the shader sample
- the camera distance for that sample
- the coordinates of a texture sample
All nodes in these trees consequently also calculate exactly one value per output socket. The important thing to note here is that although the tree is evaluated for a large number of shader/texture samples, this is not part of the tree itself, but of the underlying render system.
For compositor trees, the data that moves around the tree is a little more sophisticated: Each socket basically holds a full image buffer. The type of the sockets (value, vector or color) just tells the compositor nodes how to interpret the pixel values. Most simple nodes (like color mixing) work on the individual pixels, but some also operate on neighbouring pixels (e.g. Blur) or even the full set of pixels (e.g. Defocus). However, the data set for all nodes in a compositor tree is basically the same: they all operate on an image the size of the render layer. In a sense the compositor data can be seen as singleton data, just as the single values, vectors and colors in shader- and texture trees.
For simulation trees, the situation is different: A simulation node tree should be able to act on very different types of objects. A node socket can hold singleton data, such as the position of an object, but also “collection” data, e.g. vertex locations, face normals, particle mass, etc. This requires a much more generic system of data storage and reference than the other node tree types with their very specific purposes. For this reason, each socket now has, in addition to its data type, a context type and context source. The type can be a singleton, vertex, edge, face, particle or any other kind of collection used in Blender. A node will generally perform an operation for all elements in the data set plugged into its inputs: an “Add” node can not only add two singletons, but also two larger sets of values. The only restriction is that data from different contexts can not be combined: you can not plainly add the face normals of a mesh to the positions of particles, because in general the data sets have different sizes.
I can see this question coming up more often, so i’d like to answer it here:
Yes, there are ways to combine data from different sets, it’s just not as simple as an add node with particle locations on one input and face data on the other. If you want to, lets say, assign the color of a texture to a particle, you’d use a special “GetClosestSurfacePoint” node (or similar), which takes the particle location and finds a point on the surface, then calculates that points texture color and outputs it (in the particle context!). Similar problem is finding “neighbouring” particles, which also gets a special node (that would use a BVH-Tree internally).
There are two extremes when it comes to copying data from node to node and processing it:
- Process each element one-by-one (for 1000 particles, execute the tree 1000 times)
- Process all elements in one go (execute the tree 1 time, but with the full data set)
Both of these methods have disadvantages:
- Executing the tree has an overhead, which accumulates to significant delays when using single-element processing. Also this prevents us from using SIMD instructions (more on that later)
- Storing the full data set multiple times can easily fill your memory for larger simulations (as currently happens with compositor trees)
The solution to this problem is the use of a batch processing system: Each data set is split into a couple of fixed-size batches. The number of batches is much smaller than the number of elements in the data set, while the size of a batch still avoids memory problems. Another advantage is that this allows the efficient use of multiple threads. Each thread processes one batch of data at a time for a particular node.
I will stop here to finally get this post online and continue in future posts.