Node Expressions
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 :
Operation | Example |
Addition | a + b |
Subtraction | a - b |
Multiplication | a * b |
Division | a / b |
Power | a ** b |
Sine | sin(x) |
Cosine | cos(x) |
Tangent | tan(x) |
Arcsine | asin(x) |
Arccosine | acos(x) |
Arctangent | atan(x) |
Arctangent(2) | atan2(x,y) |
Absolute | abs(x) |
Round | round(x) |
Maximum | max(x,y) |
Minimum | min(x,y) |
Modulo | mod(x,y) |
Mathematical Modulo | mmod(x,y) |
Log | log(x,y) |
Greater Than | x > y |
Less Than | x < y |
Greater or Equal | x >= y |
Less Than or Equals | x <= y |
Equals | x == y |
Not | not(a) |
And | and(a,b) |
Or | or(a,b) |
Exclusive-Or | xor(a,b) |
Clamp | clamp(a) |
Clip | clip(a) |
Fraction | fract(a) |
Floor | floor(a) |
Ceiling | ceil(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 components | combine(x,y,z) |
Add | vadd(v,w) |
Subtract | vsub(v,w) |
Multiply | vmult(v,w) |
Divide | vdiv(v,w) |
Dot Product | vdot(v,w) |
Cross Product | vcross(v,w) |
Normalize | vnorm(v) |
(split vector) | v[n] |
Textures | noise(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.
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)
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:' (where 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 :
Preset | Description |
Blend | Smoothly blend between two input values |
Fan | Generate a fan, radiating from the centre |
Normal Distribtion | Vary the output based on a 'normal' distribution |
Grid Repeat | Translate input coordinates to generate a repeating grid |
Hexagons | Generate hexagonal/honeycomb patterns |
Mapping | A 'Mapping' node with the transform adjustable via input nodes |
Polar Coordinates | Convert a vector into Angle and Direction |
Scales | Generate overlapping scales |
Spiral 2D | A simple spiral in 2-dimensions |
Spiral 3D | A 3-dimensional spiral (can be used to generate a volumetric galaxy) |
Star | A 2-dirmensional parameterised star/flower |
Wave | An 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.
Discover more products like this
procedural node group noodles hexagonal math Node Editor nodegr nodes NodeTree mathematical hexagon