NodeGraph Language (NGL)#

NGL is a material description language based on the GLSL shader language. It is used in 3DCoat to create nodes for materials, effects, deformations, and more.

The language includes a preprocessor that handles custom defines and provides additional features needed for node description.

Code Structure#

Unlike standard GLSL, NGL offers flexibility in how you structure your code.

Implicit Main#

You are not required to define a main function. You can write your shader code directly in the global scope, similar to Python scripts.

Example:

in vec3 Color;
out vec3 Result;

// Direct code execution
Result = Color * 2.0;

Explicit Main with Arguments#

If you prefer to define a main function, you can declare your in and out properties directly as arguments to the function. This keeps the global namespace clean and groups property definitions with the main logic.

Example:

void main(
    in vec3 FragCoord(value= 1, knot= ioFragCoord, expression= R=(V*K), min= -10.0, max= 10.0),
    in float Scale,
    out float Closest,
    out float SecondClosest,
    out float CellID
) {
    // Shader logic here
    vec3 p = floor(FragCoord * Scale);
    // ...
}

Properties#

To create incoming or outgoing properties, you can simply prefix the variable with in or out. Properties can be global variables (as seen above) or arguments of the main function.

Syntax:

in type VariableName;
out type VariableName;

Example:

in vec3 Color;
out vec4 Result;

Property Options#

Additional options for each property can be specified in round brackets after the variable declaration.

Syntax:

in type VariableName(option1 = value1, option2 = value2, ...);

Available Options:

  • value: Default value for the property.

  • knot: Specifies a knot type (e.g., ioFragCoord).

  • expression: Defines a custom expression for the property value (e.g., R=V*D(K.x)).

  • min: Minimum allowed value.

  • max: Maximum allowed value.

  • AllowCurve: (Boolean) Allows curve control. Default is false.

  • AllowTexture: (Boolean) Allows texture input. Default is false.

Example:

in vec3 FragCoord(value = 1, knot = ioFragCoord, expression = R=V*D(K.x), min = -10.0, max = 10.0, AllowCurve = false, AllowTexture = false);

Expressions#

Expressions allow you to define custom logic for property values directly in the declaration. They share the same features as NGL but are designed for compactness. You cannot create new in or out properties within an expression.

Shorthand Variables:

  • R: (vec4) Result variable. The output of the expression is written here.

  • V: (vec4) Input value. The value entered by the user in the Node Inspector.

  • K: (vec4) Knot value. The value connected to the property from another node. Defaults to vec4(1,1,1,1) if nothing is connected.

Shorthand Functions:

  • DC(value): Deform Curve. Applies the curve (configurable by the user for this property) to a vec4 or color value.

  • D(value): Deform Curve (Scalar). Applies the curve to a float value or a single scalar.

Accessing Other Properties#

You can access the values of other properties (including custom user-created properties not defined in the code) using dot notation:

  • OtherProp.V: The value of OtherProp defined in the Inspector (User Value).

  • OtherProp.K: The value connected to OtherProp from another node (Knot Value).

Example:

// Result = UserValue * AnotherPropertyValue * AnotherPropertyConnectedValue
in float Scale(expression = R = V * AnotherProp.V * AnotherProp.K);

Example:

// Result = UserValue * DeformCurve(ConnectedValue.x)
in float Scale(expression = R=V*D(K.x));

Special Data Types#

color#

The color type is essentially a vec4, handled by the preprocessor. * In the Editor: It appears as a color picker with a specialized UI, rather than four separate float sliders. * Usage: Use it exactly like a vec4 in your code.

Example:

in color DiffuseColor;

One2DCurve#

The One2DCurve type represents a custom 1D correction curve defined by the user in the UI. * In the Editor: It is accessed via a curve editor. * Usage: In code, it acts as a fixed array of 256 floats (float[256]). You can access values using an index from 0 to 255.

Example:

in One2DCurve MyCurve;

// access value at index 128
float midValue = MyCurve[128];

// map 0.0-1.0 to 0-255 range
float value = MyCurve[int(inputVal * 255.0)];

Property Modifiers and Accessors#

For in properties, you can access additional attributes and modifiers using dot notation.

.INV#

Checks if the user has inverted the input property.

  • Usage: PropertyName.INV returns a boolean (or integer 0/1).

  • Context: Use this to manually apply inversion logic if needed, for example, when the input controls a texture lookup rather than a direct value.

.DC()#

Accesses the “Deform Curve” function associated with the property. Users can adjust an input’s curve in the UI. By default, this is applied to the input value. However, if you are using the input to control something else (like a texture coordinate or a fetched texture sample), you can apply the same curve modification to that new value.

  • Syntax: PropertyName.DC(value)

  • Example:

    // Apply the same curve configured for 'Color' input to a texture sample
    vec4 texColor = texture(MyMap, uv);
    vec4 correctedColor = Color.DC(texColor);
    

Material Object#

The Material type is a specialized structure used to represent a full PBR material. Under the hood, it is packed into an array of 7 vectors: vec4[7].

Structure and Packing#

To allow mixing different material types (e.g., Glass vs. Metal) and to optimize storage, multiple properties may share the same scalar value in the underlying vectors. For example, a generic “scalar” might represent Refraction in the 0.0-0.5 range and Metalness in the 0.5-1.0 range.

Accessing Properties#

You can access unpacked properties using the io prefix on the Material object properties.

Syntax: materialInstance.ioPropertyName

Available Properties:

  • AlbedoColor (Color)

  • Emissive (Color)

  • Opacity (float)

  • Iridescence (float)

  • Anisotropy (float)

  • ClearCoat (float)

  • IOR (float)

  • ClearCoatRoughness (float)

  • Volume (float)

  • MicroprotrusionsRoughness (float)

  • ReflectionColor (Color)

  • RefractionBlur (float)

  • SpecularEdgePower (float)

  • EmissiveColor (Color)

  • SubSurfaceColor (Color)

  • SpecularEdgeColor (Color)

  • MicroprotrusionsColor (Color)

  • DiffuseSSS (float)

  • SpecularSSS (float)

  • RefractionChromatic (float)

  • AmbientOcclusion (float)

  • ClearCoatEdgePower (float)

  • Microprotrusions (float)

  • TSNormal (Color) - Tangent Space Normal

  • Metalness (float)

  • Refraction (float)

  • RefractionMetalness (float)

  • Gloss (float)

  • Roughness (float)

  • Velvet (float)

Example:

out Material result;
...
result.ioAlbedoColor = vec4(1.0, 0.0, 0.0, 1.0);
result.ioMetalness = 1.0;
result.ioRoughness = 0.2;

Global Functions#

mixMTL#

A global helper function to correctly mix two materials. It handles the packed structure of the Material type to ensure all properties (even those sharing scalars) are interpolated correctly.

Syntax:

Material mixMTL(Material m1, Material m2, float t);

Parameters:

  • m1: The first material.

  • m2: The second material.

  • t: Interpolation factor (0.0 to 1.0).

Preprocessor Directives#

NGL supports custom preprocessor directives to generate different source code based on settings or to create UI elements.

#enum#

Creates a dropdown menu in the node settings.

Syntax:

#enum DEFINE_NAME OPTION1 OPTION2 OPTION3 ...

In this case, DEFINE_NAME will be replaced by the selected element from the list. This is particularly useful for creating generic types or switching keywords.

Example:

#enum genType float vec2 vec3 vec4

// genType will be replaced by the selected type (e.g. float, vec2, etc.)
in genType Value;

#int#

Allows you to set an integer value via the UI.

Syntax:

#int DEFINE_NAME DEFAULT_VALUE MIN_VALUE MAX_VALUE

Example:

#int ITERATIONS 5 0 10

#bool#

Creates a checkbox in the settings.

Syntax:

#bool DEFINE_NAME

You can check if it is enabled using #ifdef.

Example:

#bool ENABLE_SHADOWS

#ifdef ENABLE_SHADOWS
    // Shadow calculation code
#endif

#sampler#

Provides direct access to a bitmap texture and Sampler2D.

Syntax:

#sampler DEFINE_NAME

This allows users to load textures, and you can use DEFINE_NAME as a Sampler2D in your code.

Example:

#sampler AlbedoMap

void main() {
    vec4 color = texture(AlbedoMap, uv);
}