Node Expressions

by Balding Wizard in Materials, Shaders, Textures

Note : Additional information can now be found on my blog at https://baldingwizard.wixsite.com/blog/node-expressions. The site also includes a number of tutorials which demonstrate the use of this add-on.


The Node Expressions add-on provides a means to enter a mathematical expression which is automatically parsed to generate a node group built up of standard maths nodes (and so is GPU-friendly) to implement that expression. It is available within the Node Editor 'Add' menu as 'Maths Expression'. 

On selecting the menu option you'll be presented with a popup window prompting you to enter your expression.

For example, entering the expression 'a + b' will result in a node with two inputs ('a' and 'b'), outputting a single value that is the mathematical sum of 'a' and 'b'. The expression will be contained within a new node group with the inputs and outputs named appropriately (in this case 'a' and 'b'). 

The name of the output of the group can be indicated by including the name in the expression as in 'SumOfValues = a + b'.

The following standard mathematical operations are implemented :

OperationExampleAdditiona + bSubtractiona - bMultiplicationa * bDivisiona / bPowera ** bSinesin(x)Cosinecos(x)Tangenttan(x)Arcsineasin(x)Arccosineacos(x)Arctangentatan(x)Arctangent(2)atan2(x,y)Absoluteabs(x)Roundround(x)Maximummax(x,y)Minimummin(x,y)Modulomod(x,y)Mathematical Modulommod(x,y)Loglog(x,y)Greater Thanx > yLess Thanx < yGreater or Equalx >= yLess Than or Equalsx <= yEqualsx == yNotnot(a)Andand(a,b)Oror(a,b)Exclusive-Orxor(a,b)Clampclamp(a)Clipclip(a)Fractionfract(a)Floorfloor(a)Ceilingceil(a)


Additionally, you can work with Vectors and Textures using the following functions : Combine components Add Subtract Multiply Divide Dot Product Cross Product Normalize (split vector)

Combine componentscombine(x,y,z)Addvadd(v,w)Subtractvsub(v,w)Multiplyvmult(v,w)Dividevdiv(v,w)Dot Productvdot(v,w)Cross Productvcross(v,w)Normalizevnorm(v)(split vector)v[n]Texturesnoise(vector, scale)
noise(vector, scale, detail)
noise(vector, scale, detail, distortion)
musgrave(vector, scale, ....)
musgrave.fbm(vector, scale, ....)
musgrave.mf(vector, scale, ....)
musgrave.rmf(vector, scale, ....)
musgrave.hmf(vector, scale, ....)
musgrave.ht(vector, scale, ....)
voronoi(vector, scale)
voronoi.cell(vector, scale)


Any of the above functions, operators and comparators can be combined into a single expression and standard operator precedence rules should be followed - ie, power takes precedence over multiplication and division, which take precedence over addition and subtraction. Comparators (ie, >, <, >=, <=, ==) take least precedence. The expression can also include brackets to indicate overriding the operator precedence.

The expression can be edited using the Edit button of the custom node created with the node group. The node group is compatible with GPU rendering (since it only uses 'standard' Maths nodes) and can be exported to use in a Blend file independently of the add-on.

The expression can be arbitrarily complicated and the group inputs and outputs will be automatically updated with the relevant inputs and outputs to the expression.

Edit expression

Note also that multiple expressions can be entered, separated by commas, so as to provide a group with multiple outputs - for example, sum=a+b+c,distance=(a*a+b*b+c*c)**0.5,minimum=min(min(a,b),c),maximum=max(max(a,b),c) to create a group with inputs of a,b,c and outputs of sum,distance,minimum,maximum.

Default Values

Default values can be set for any input variable by adding a suffix within '{}' braces following any one usage of that variable. For example, creating a node as Sum=a{0.25}+b{0.5}+c{0.1} will create a group to add three input values (a, b, c), with default values (used when the input nodes of the group are not connected) of a=0.25, b=0.5, c=0.1.

Vectors

Input Vectors can be split into their component parts by including the suffix - for example, vector[x] wil extract the first element of the vector. Output variables can be defined as vector type by suffixing with [], and individual components can be combined into a vector using the combine(...) function.

'Special' Variables

A number of 'special' variables can be used in expressions to represent common input values. These are as follows :

Texture Coordinates :

  • Generated Input.Generated
  • Normal Input.Normal
  • UV Input.UV
  • Object Input.Object
  • Camera Input.Camera
  • Window Input.Window
  • Reflection Input.Reflection


Geometry :

  • Position Geom.Position
  • Normal Geom.Normal
  • True Normal Geom.TrueNormal
  • Tangent Geom.Tangent
  • Incoming Geom.Incoming
  • Parametric Geom.Parametric
  • Backfacing Geom.Backfacing
  • Pointiness Geom.Pointiness


Object Info :

  • Index Object.Index
  • Material Index Object.Material
  • Random Object.Random
  • Location Object.Location


Particle Info :

  • Index Particle.Index
  • Age Particle.Age
  • Lifetime Particle.Lifetime
  • Location Particle.Location
  • Size Particle.Size
  • Velocity Particle.Velocity
  • Angular Velocity Particle.AngularVelocity
  • Random Particle.Random


Some Examples

Here are some examples to get you started :

Grid : (mod(x,0.1)/0.1>0.1)*(mod(y,0.1)/0.1>0.1)

Grid(with variable Scale and Thickness) :

_scale=1/Scale, mod(x,_scale)/_scale>Thickness)*(mod(y,_scale)/_scale>Thickness)

Wave : abs(x-(sin(y*10)/2.5+0.5))>0.1

Combined waves : abs(x-sin(y*freq1)*amp1-sin(y*freq2)*amp2-sin(y*freq3)*amp3)<0.05

Fan : mod(atan2(x-0.5,y-0.5)+3.141,0.5)/0.5

Spiral : mod((_angle+_distance*Curviness)/4,_invertedScale)/_invertedScale, _angle=(atan2(x,y)+3.141)/3.141/2, _distance=((x)**2+(y)**2)**0.5+z/Scale, _invertedScale=1/Scale

Star : _dist=(x**2+y**2)**0.5, _angle=atan2(y,x), _edge=sin(_angle*Points)/2+0.5, Output=_dist>_edge*(Outer-Inner)+Inner

Fish Scales

A much more complicated example produced by comparing distances to the points in a grid to produce overlapping circles or scales :

The right-most "DynamicMaths..." node converts the output of the Noise (which clusters around 0.5) to cover the full 0.0 to 1.0 range by multiplying by a large value and taking the modulo using the following expression :

Value = max(0,mod(Noise*963.34,1)-Mask)

The other "DynamicMaths..." node creates the scales using the following expression :

CentreX=_x1*_hit1+_x2*_hit2+_x1*_hit3+_x2*_hit4, CentreY=_y1*_hit1+_y1*_hit2+_y2*_hit3+_y2*_hit4, _x1=x-mod(x,Size), _x2=_x1+Size, _y1=y-mod(y,Size), _y2=_y1+Size, _d1 = ((x-_x1)**2+(y-_y1)**2)**0.5, _d2 = ((x-_x2)**2+(y-_y1)**2)**0.5, _d3 = ((x-_x1)**2+(y-_y2)**2)**0.5, _d4 = ((x-_x2)**2+(y-_y2)**2)**0.5, _hit1 = _d1 < Radius, _hit2 = (1-_hit1) * (_d2 < Radius), _hit3 = (1-max(_hit1,_hit2)) * (_d3 < Radius), _hit4 = (1-max(_hit1,max(_hit2,_hit3))) * (_d4 < Radius), _hitNone = (1-max(_hit1,max(_hit2,max(_hit3,_hit4)))), _distToHit = _d1 * _hit1 + _d2 * _hit2 + _d3 *_hit3 + _d4*_hit4 + 99999*_hitNone, Value=abs(_distToHit-Radius)<Thickness

The '_' before a variable indicates it's a hidden variable (so it's not present on the output). The expression calculates the location of the 4 closest points ([x1,y1], [x1,y2], [x2,y1], [x2,y2]) and calculates the distances to each (dist1, dist2, dist3, dist4), determines which one of these is uppermost ('hit' - hit1, hit2, hit3, hit4), and finally determines if the 'hit' was close to the edge of that 'scale'. This generates the following nodes within the node group (which I certainly wouldn't want to have put together manually) :

Text Blocks

By setting the expression to 'TEXT:<textblock>' (where <textblock> is the name of an existing text block in the Text Editor) the expression can be sourced from the Text Editor. This allows for much easier editing of complicated expressions, split over multiple lines, and can include comments and additional line spacing. For example, the following text can be used to generate a basic Hexagon Shader :

# Hexagons

#Set X and Y scaling based on Aspect ratio (0.866 for undistorted hexagons (`sin(60 degrees)`)
_xpitch=Aspect/Scale
_ypitch=0.866/Scale

#Logic to control where we need to place the reference points
_firsthalf=mod(x,_xpitch)>_xpitch/2
_tophalf=mod(y,_ypitch)>_ypitch/2
_oddline=mod(y,_ypitch*2)>_ypitch

#Define the reference points - x1 is "main" reference, others positioned in relation to that
_x1=x-mod(x,_xpitch)+_oddline*((0-_xpitch/2*0)),  _y1=y-mod(y,_ypitch)+_oddline*_ypitch
_x2=_x1+_xpitch,                                                        _y2=_y1
_x3=(_x1+_x2)/2,                                                        _y3=_y1-_ypitch
_x4=_x3,                                                                      _y4=_y1+_ypitch
_x5=_x3-_xpitch+_xpitch*2*_firsthalf,                     _y5=_y3*not(_tophalf)+_y4*_tophalf

#Distance to each reference point
_d1 = ((x-_x1)**2+(y-_y1)**2)**0.5
_d2 = ((x-_x2)**2+(y-_y2)**2)**0.5
_d3 = ((x-_x3)**2+(y-_y3)**2)**0.5
_d4 = ((x-_x4)**2+(y-_y4)**2)**0.5
_d5 = ((x-_x5)**2+(y-_y5)**2)**0.5

#Distance to the closest point
_dist = min(_d1,_d2,_d3,_d4,_d5)

#Determine which of the reference point is the closest
_closest1 = (_dist == _d1)
_closest2 = not(_closest1)*(_dist == _d2)
_closest3 = not(or(_closest1,_closest2))*(_dist == _d3)
_closest4 = not(or(_closest1,_closest2,_closest3))*(_dist == _d4)
_closest5 = not(or(_closest1,_closest2,_closest3,_closest4))

#Determine the distance to the *next* closest
_nextdist = max(_closest1*min(_d2, _d3, _d4, _d5)
              _closest2*min(_d1, _d3, _d4, _d5)
              _closest3*min(_d1, _d2, _d4, _d5)
              _closest4*min(_d1, _d2, _d3, _d5)
              _closest5*min(_d1, _d2, _d3, _d4, _d5))

#Determine which of the points is the next closest
_nextclosest1 = (_d1 == _nextdist)*not(_closest1)
_nextclosest2 = (_d2 == _nextdist)*not(_closest2)*not(_nextclosest1)
_nextclosest3 = (_d3 == _nextdist)*not(_closest3)*not(or(_nextclosest1,_nextclosest2))
_nextclosest4 = (_d4 == _nextdist)*not(_closest4)*not(or(_nextclosest1,_nextclosest2,_nextclosest3))
_nextclosest5 = not(_closest5)*not(or(_nextclosest1,_nextclosest2,_nextclosest3,_nextclosest4))

#Calculate how close we are to the edge (the edge is halfway between the two distances)
Hexagon = 1-(_dist /((_nextdist + _dist)/2))
#DistanceFromCentre = _dist
#DistanceFromNextCentre = _nextdist

Presets

When creating new expresions the add-on provides a number of 'Preset' expressions that can be selected from a list. The presets can be used as they are or can be used as the start point for your own expressions. Some presets include optional modes or extra functionality which can be adjusted before the preset is applied. In addition, presets can be used to automatically generate a text block so as to allow them to be easily viewed and edited as required.

The following presets are currently available :

PresetDescriptionBlendSmoothly blend between two input valuesFanGenerate a fan, radiating from the centreNormal DistribtionVary the output based on a 'normal' distributionGrid RepeatTranslate input coordinates to generate a repeating gridHexagonsGenerate hexagonal/honeycomb patternsMappingA 'Mapping' node with the transform adjustable via input nodesPolar CoordinatesConvert a vector into Angle and DirectionScalesGenerate overlapping scalesSpiral 2DA simple spiral in 2-dimensionsSpiral 3DA 3-dimensional spiral (can be used to generate a volumetric galaxy)StarA 2-dirmensional parameterised star/flowerWaveAn adjustable 1-dimensional sine wave with additional harmonics

New presets are generally added at each new version of the add-on - so this list is expected to grow over time. The presets are intended to provide examples of what is possible with the add-on as well as providing useful node groups that can be immediately included in your own projects.