# Coding Blender Materials With Nodes & Python

This tutorial, a follow-up to Creative Coding in Blender, focuses on creating patterns on materials in Blender’s Cycles renderer. There are a few reasons why patterning can be challenging at first. Tutorials for OpenGL Shading Language (GLSL) abound, but the node-based visual programming in Blender has a slightly different toolset. While Open Shading Language provides an alternative, it has a few drawbacks:

- OSL requires that a render be done on the CPU, not the GPU.
- There’s a potential dependency, where .
`osl`

files may have to be tracked along with the`.blend`

file. - Documentation is segmented — and inconsistent — between the OSL language specification and Blender’s documentation.
- OSL may encourage a ‘kitchen sink’ or ‘black box’ style, in which we don’t see how the logic of an OSL script node works and cannot quickly revise it. We have to switch back and forth between the node and text editor (internal or external) to change the pattern.
- OSL can be problematic in collaborative scenarios, where one or more collaborators focus less on the scripting and more on aesthetics.

For these reasons, we’ll group built-in nodes into utility functions that facilitate translating shader patterns from other languages into Blender. There are several add-ons, such as Jacques Lucke’s Animation Nodes and Skorupa & Zaal’s Node Wrangler, which include functionality developed here. This article aims not provide a recipe to simulate a realistic texture or be comprehensive, but to illustrate how a tool set can be developed.

Knowledge of OSL is not necessary to follow along, but code snippets are included to bridge the gap between visual and script-based programming. After we create a node-based tool set, we’ll look into how to generate nodes and materials with Python, then append them to generated objects.

*This was written for Blender v. 2.79.*

# Creating Node Groups

We create the node groups below by selecting nodes in the node editor and pressing `Ctrl-G`

. To edit a group that has already been created we either click on the linked white boxes in the upper right corner of a node group instance or press `Tab`

. To return to the main material from editing the group, we either press `Tab`

again or click on the bent arrow in the node editor header, labeled ‘Go to parent node tree.’

The inputs on the left side of a group node viewed from the outside are represented by a `Group Input`

node inside the group; the outputs on the right, by the `Group Output`

node. The interface between inside and outside is in the properties region, which can be opened by going to `View > Properties`

or by pressing `N`

. To add new inputs or output sockets, we link nodes to the clear unlinked socket at the bottom of either the `Group Input`

or `Group Output`

node. The `+`

buttons on the bottom of the interface do the same.

The interface will let us delete sockets, switch their order with the down and up arrows, change their names and default values. Depending on the data type of the input or output, the minimum and maximum bound of the value can also be set. Take care when adding new values that minimums and maximums are suitable.

We can assign a fake user to the node group by clicking on the `F`

on the right side of the drop-down menu. The number to the left will tell us the number of users the group node has. If we create these groups in the startup Blender file (to which we can return with `Ctrl-N`

), then press `Ctrl-U`

to update it, we can store custom group nodes on a long-term basis.

# Comparisons

The comparisons ≥ and ≤ are not included in the math node, but can be derived by subtracting the return value of > or < from 1. The `step`

function common to shader languages is, in effect, a less-than comparison.

## Not Equals (neq)

## In-Bounds

We can use `v >= lower`

if we want the lower bound to be inclusive; `v <= upper`

if we want the upper bound to be inclusive.

## Out-of-Bounds

## Approximates (approx)

Because very large and very small input numbers are not anticipated, this simplifies a more robust approximation function, which can be found here.

# Logic Gates

These logic gates follow Python’s example when converting from a real number to a boolean: any non-zero value is true.

The same holds in JavaScript. There are more gates than those depicted below, such as imply.

## And

`and`

depends on `neq`

.

## Inclusive Or

`or`

depends on `neq`

.

## Not And (nand)

`nand`

depends on `approx`

.

## Not Or (nor)

`nor`

depends on `approx`

.

## Exclusive Or (xor)

`xor`

depends on `or`

and `nand`

.

# Real Number Operations

## Clamp

## CosineSine

This could be parlayed into a `CosSinTan`

function.

## Exponent (exp)

`2.71828`

approximates the constant *e*. `exp`

allows us to derive hyperbolic functions.

## Hyperbolic Cosine (cosh)

`cosh`

depends on `exp`

.

The hyperbolic secant, `sech(v)`

, is `div(1.0, cosh(v))`

.

## Hyperbolic Sine (sinh)

`sinh`

depends on `exp`

.

The hyperbolic cosecant, `csch(v)`

, is `div(1.0, sinh(v))`

.

## Hyperbolic Tangent (tanh)

`tanh`

depends on `cosh`

and `sinh`

.

The hyperbolic cotangent, `coth(v)`

, is `div(cosh(v), sinh(v))`

.

## Map

While not a function in shader languages, `map`

allows a value in one range to be converted to to the equivalent value in another range.

## Mix

## Mod / Floor Mod

`mod`

depends on `mix`

.

The built-in `modulo`

function is `fmod`

, represented in some programming languages by the `%`

operator. The distinction between a truncation modulo and floor modulo is relevant when working with periodic ranges: when `b`

is positive and `a`

is negative, `fmod`

will return a negative value; `mod`

, a positive value. For example, `mod`

is handy when traveling counter-clockwise on the color wheel from orange to magenta.

## Quantize

`quantize`

depends on `floor`

.

Quantizing a value reduces it from a larger to a smaller set, in effect converting smoother transitions to sharper, more distinct ones.

The above example illustrates, using Vermeer’s *View of Delft*, the outcome of applying `quantize`

to the components of a `color`

and `vector`

. A quantized color has a reduced color palette (seen on the top row). A quantized texture coordinate crenelates the image; not pictured, a vector could also be treated as a direction, where its polar or spherical coordinates are quantized.

## Radians

This multiplies the input degrees by `pi / 180.0`

(`0.017`

).

## Sign

## Fraction (fract)

`fract`

depends on `sign`

.

Returns the fractional part of a real number. In OSL, `fract`

is derived from `trunc`

.

## SmoothStep

## Threshold

`threshold`

depends on `inbounds`

.

Compared to `clamp`

, this returns `0`

when the value is out-of-bounds.

# Integer Conversions

## Ceil

`ceil`

depends on `mix`

.

`ceil`

biases toward the greater value, so `ceil(-4.25)`

equals `-4`

, while `ceil(4.25)`

equals `5`

.

## Floor

`floor`

depends on `mix`

.

`floor`

biases toward the lesser value, so `floor(-4.25)`

equals `-5`

, while `floor(4.25)`

equals `4`

.

## Truncate (trunc)

`trunc`

depends on `fract`

.

# Vector Operations

There are many useful vector operations — such as `step`

, `mix`

and `fract `

— which can be described as the separation of a vector into its components, application of real number operations above, and recombination. We omit most of them below, save for uniform and nonuniform scaling.

## Magnitude (mag)

## Minkowski Distance

Minkowski distance is a generalization of Euclidean and Manhattan distance, where `minkowski(a, b, 2.0) = euclidean(a, b)`

and `minkowski(a, b, 1.0) = manhattan(a, b)`

. (The result of raising a negative base to an even exponent is positive, so the Pythagorean theorem can dispense with `abs`

.)

## Multiply by a Uniform Scale (mult)

## Multiply by a Nonuniform Scale (scale)

Multiplying two vectors component-wise is undefined, but is often supported as a shortcut for multiplying a scale matrix and a spatial coordinate. In the above, height is assigned to the *y *axis, but could be assigned to *z* instead.

## Reflect

`reflect`

depends on `mult`

.

## Refract

`refract`

depends on `mult`

.

## Rescale

`rescale`

depends on `mult`

.

## Rotate

`rotate`

depends on `cossin`

.

Vector rotation is handled by the vector mapping node; that node, however, is unwieldy to use, as `translation`

, `rotation`

and `scale`

are not input sockets. Furthermore, we may need to rotate texture coordinates in the range `(0, 0)`

.. `(1, 1)`

around a pivot `(0.5, 0.5)`

.

## Tile

`tile`

depends on `mult`

.

# Shader Operations

With volume shader nodes in particular, we don’t always need the entire volume of an object to be filled with scattering and/or absorption. For this reason we mix the `volume`

shader with a `holdout `

by a `factor`

.

## Shaped Absorb Volume

## Shaped Light

## Shaped Scatter Volume

# Scripting Nodes With Python

Python is not needed to create the above, but if we invest the time to write some utility functions, they can help automate the process. A first step is to create an empty group with input and output nodes. (We can reference the info editor’s report console any time we want to find out the name of a property in Python.)

Appending a group to `bpy.data.node_groups`

is a different procedure than appending an instance of a group node to the node tree of a particular material. Node groups can be thought of as a repository for custom function definitions, which are then called by various materials.

Courtesy the NodeSocketStandard class, scripted nodes may use a greater variety of data types than are seen when creating nodes manually. These include integer and Boolean data types. To maintain consistency with out-of-the-box nodes, however, we avoid using them here.

## Example 1. In-Bounds

Because the math node is the cornerstone of so many groups above, it’s helpful to dedicate a function to that. Operations included in the math node can be found here.

Math nodes can be color-coded for further clarity, or parented to a frame. If no name for the node is provide, the function defaults to the operation. With this function in place, we can create the `inbounds`

operation from above.

For input and output sockets, we create a list of dictionaries. Each entry in a dictionary is a key-value pair, where the key is usually a data-type that is easy to sort and check for equivalence. In this case, the key is a string. When creating a new link between nodes, the input socket receiving the noodle is supplied first, then the output socket.

## Example 2. Shaped Scatter Volume

The shaped scatter group depends on `threshold`

and `inbounds`

. To create a group node which depends on others, we need another helper function.

This function checks to see if the prerequisite node group exists, and, as a backup, creates it if not. Since attempting to find a node group by name could raise an error, we enclose this in a `try`

block. Earlier, we used the dictionary `get`

function to supply a default alternative when an item could not be located. In this case, however, the alternative requires a function to create the node group. More on exception handling in Python can be read here.

With that in place, we can create the the shaped absorption volume.

Here and with `threshold`

above, we reference outputs and inputs by number rather than by name. Accessing an element by index can be helpful when two node sockets have the same name; for example, a mix shader labels both inputs as `Shader`

.

# Arranging Nodes

If we look in the node editor at operations generated through Python, we find a clump of nodes on top of each other, making them hard to manually adjust. We can create a script to automatically arrange them.

This depends on functions to calculate a node’s depth or priority and functions to find the width and height of nodes at a given depth. When nodes have been grouped into depths, they are sorted, then eased from the left edge to the right edge. For positioning, positive on the y-axis is up.

There are many ways to write the heuristic we supply as the second parameter to `arrange_nodes`

. Called `calc_priority`

, this will generate the key for each entry in the `depth_nodes`

dictionary. We’ll look at two candidates, one in which we arrange nodes by their type, another in which we look at the nodes input and output sockets.

## Arranging By Type

Checking Strings for equivalence is a crude solution, as typically we have to deal with mismatching cases (`'Wednesday'`

vs. `'WEDNESDAY'`

), partial matches (`'Wed'`

vs. `'Wednesday'`

), trimmable characters (`'VECT_MATH'`

vs. `'VECT MATH'`

) and so on. Since we are using Strings internal to Blender, we keep our checks simple.

The above does not include all possible node types.

## Arranging By Connected Sockets

Alternatively, we could sift through each node’s input and output sockets and assign a score based on whether the socket is connected and, in the case of outputs, how many connections it has.

Not all links are valid, as when the output of a node eventually feeds into one of its inputs, creating an undesirable loop. We may also wish to consider adding to or subtracting from `result`

if a node is unlinked.

An approach not featured here would be to use a function that begins at a pole of the node tree (the material output, for example), and crawls link by link through the nodes, judging priority by distance from that pole.

# Putting It All Together

As an example of how these node groups can be strung together and animated, we can modulate the Minkowski distance of a texture coordinate from the center. The Manhattan distance will generate grid-like patterns; the Euclidean, curvilinear patterns.

That modulated distance is supplied to a color gradient. To convert from a smooth gradient to a distinct pattern, we quantize the value. To add variety, the texture coordinate is rotated on the y-axis and tiled before it is measured.

## Tiled Ring Pattern

We can also lump everything into a monolithic group node. Here we create a circle which can be shown as stroke only, fill only or stroke and fill.

While the volumes of all four are similar, the geometries that intersect that volume are different. A challenge when creating patterns, as this quote from Edwin A. Abbott’s *Flatland* reminds us,

You cannot indeed see more than one of my sections, or Circles, at a time; for you have no power to raise your eye out of the plane of Flatland; but you can at least see that, as I rise in Space, so my sections become smaller. See now, I will rise; and the effect upon your eye will be that my Circle will become smaller and smaller till it dwindles to a point and finally vanishes.

is that we must consider the play of volume against surface.

To see how we can apply this pattern to objects created with Python, we next generate a ring of material samples.

## Generating Swatches

To facilitate animation within our material we create a function that inserts a keyframe for an input socket’s default value.

Because we don’t want a keyframe inserted when the value is constant, we only do so if there is more than one entry in `frame_entries`

.

For now, the base color of the principled shader will be linked to a combine HSV node. The `colorsys`

module and Blender’s Color class in the `mathutils`

module may also assist in the setting of colors. A geometry node provides the tangent and normal to the principled shader.

Before we can append nodes to the material, we have to ensure that it *uses* nodes. We then delete the diffuse shader node. For the sampler itself, we create an empty object that will serve as a parent to the others. We then arrange UV spheres in a ring.

By switching into edit mode upon creating each sphere, we can set UV coordinates according to a spherical projection. We toggle back into object mode and set the sphere’s shading to smooth. Next, we add the pattern.

So as to rotate the pattern based on a swatch’s position in the ring, we add a `rotatey`

group node. The higher a principled shader’s `Transmission`

the closer its appearance will be to that of glass. The `stroke`

of the ring pattern is characterized by a rough metallic look. By supplying this value to the material output’s `displacement`

as well, we also give the stroke a raised edge.

A shaped-absorption and shaped-light node are thrown into the mix. A `map`

node governs the temperature range, which in turn changes the light’s color based on the horizontal position of the input vector.

The absorption volume is shaped by the fill of the ring pattern, while the light is governed by the stroke.

To mix in a little chaos with form, we add a noise and voronoi texture. The noise texture’s factor feeds into the voronoi’s scale, which in turn scales the vector supplied to the `ring`

group.

The full Python script for this sampler can be found here.

# Conclusion

Many possibilities open up from here. We could translate techniques from Vivo and Lowe’s Book of Shaders, draw inspiration from GenerateMe’s Folds, or develop signed distance fields based on Inigo Quilez’s research. Hundreds of procedurally generated textures have been shared on Blender Artists; groups like Blender procedural textures provide forums to ask questions and share experiments. The concept of generating material samplers has recently been combined with artificial intelligence in a recent paper, “Gaussian Material Synthesis” by Zsolnai-Fehér, Wonka and Wimmer.