Custom Shader API

Introduction

Mari renders a scene in the canvas using its extensible shader system based on a node graph. Shaders and layers in Mari are in the form of nodes internally. Custom shaders and layers can be added to Mari easily. This is done by supplying to Mari XML files defining custom shaders and layers in the form of nodes. Once a node definition XML file is written, the node can be registered to Mari using either Python API or a text file. Mari carries a shader development console to aid the development of custom shaders and layers.

Quick Start

This section explains steps to register a custom adjustment layer that posterizes the result.

  1. Create a file ~/Posterize.xml (On Windows, C:\Users\USERNAME\Posterize.xml) with the following XML pasted in:

    <Node>
        <ID>MY_EXAMPLE_Posterize_Adjustment</ID>
        <Tags>
            <Tag>_adjustment</Tag>
        </Tags>
        <Inputs>
            <Input Name="Input"></Input>
        </Inputs>
        <Attributes>
            <Attribute Name="Colors" Description="The number of colors after posterization" Group="" Type="double" Min="0.0" Max="256" Logarithmic="true">8</Attribute>
        </Attributes>
        <Contexts>
            <Context Type="GLSL">
            <Shader>
                <Inputs>
                <Input Name="Input"><Default>vec4(1,1,1,1)</Default></Input>
                </Inputs>
                <Body>
                <![CDATA[
            Output.rgb = round($Colors * #Input.rgb) / $Colors;
            Output.a = #Input.a;
                ]]>
                </Body>
            </Shader>
            </Context>
        </Contexts>
    </Node>
    
    • Linux: Create a file ~/Mari/Scripts/register_custom_layers.py with the following python code pasted in:

      mari.gl_render.registerCustomAdjustmentLayerFromXMLFile("Posterize","~/Posterize.xml")

      Note

      Mari by default reads any custom .py files from ~/Mari/Scripts directory.

    • Windows: Create a file C:\Users\USERNAME\Documents\Mari\Scripts\register_custom_layers.py with the following python code pasted in:

      mari.gl_render.registerCustomAdjustmentLayerFromXMLFile("Posterize","C:\Users\USERNAME\Posterize.xml")

      Note

      Mari by default reads any custom .py files from C:\Users\USERNAME\Documents\Mari\Scripts on Windows.

  2. Launch Mari

  3. Open a project

  4. In the “Layers” palette, create a new “Posterize” layer.

    _images/create_posterize_layer.png

Node/Layer Examples

Specular Example

This example specular component shader does Phong specular calculation. Save this XML in a file and register it with a python command like: mari.gl_render.registerCustomSpecularShaderFromXMLFile("My Phong","/The/path/to/the/file")

<Node>
    <ID>MY_EXAMPLE_Phong_Specular</ID>
    <Inputs>
        <Input Name="SpecularColor" PrettyName="Specular Color" PreferredInput='1'></Input>
        <Input Name="SpecularShininess" PrettyName="Specular Shininess"></Input>
        <Input Name="Normal"></Input>
    </Inputs>
    <Attributes>
        <Attribute Name="SpecularShininess" PrettyName="Specular Shininess" Group="" Type="double" Min="0" Max="100">10</Attribute>
    </Attributes>
    <Tags>
        <Tag>_notcacheable</Tag>
        <Tag>_specular</Tag>
    </Tags>
    <Contexts>
        <Context Type="GLSL">
        <Shader ShaderType='Fragment'>
            <Inputs>
            <Input Name="SpecularColor"><Default>vec4(1,1,1,1)</Default></Input>
            <Input Name="SpecularShininess"><Default>vec4(1,1,1,1)</Default></Input>
            <Input Name="Normal"><Default>vec4(0,0,1,1)</Default></Input>
            </Inputs>
            <Body>
            <![CDATA[
        vec3 E = normalize(-State.ViewVectorInEyeSpaceFromViewCamera);
        vec3 N = normalize(#Normal.xyz);

        float Shininess = $SpecularShininess*#SpecularShininess.r;

        vec3 Specular = vec3(0.0);

        for(int i = 0; i < 4; i++)
        {
            //Light Direction
            vec3 L = normalize(u_MriLightSources[i].Position.xyz - State.FragmentPositionInEyeSpaceFromViewCamera);
            //Reflection Vector
            vec3 R = 2.0*dot(L,N)*N - L;

            float LightVisibility = mriLightVisibility( i, State.Position);

            float NdotH = clamp( dot(R,E), 0.001, 0.999);

            // Phong specular
            Specular += pow(NdotH,Shininess) * u_MriLightSources[i].SpecularColor.rgb * LightVisibility;
        }

        Output.rgb = Specular * #SpecularColor.rgb;
        Output.a = #SpecularColor.a;
            ]]>
            </Body>
        </Shader>
        </Context>
    </Contexts>
</Node>

Warning

Deprecation: While still supported in legacy scenes, separate Specular and Diffuse shader types are considered deprecated and will be removed in a future version of Mari.

Diffuse Example

This example diffuse component shader does Lambertian diffuse calculation. Save this XML in a file and register it with a python command like : mari.gl_render.registerCustomDiffuseShaderFromXMLFile("My Lambertian","/The/path/to/the/file")

<Node>
    <ID>MY_EXAMPLE_Lambertian_Diffuse</ID>
    <Inputs>
        <Input Name="DiffuseColor" PrettyName="Diffuse Color" PreferredInput='1'></Input>
        <Input Name="Normal"></Input>
    </Inputs>
    <Attributes>
    </Attributes>
    <Tags>
        <Tag>_notcacheable</Tag>
        <Tag>_diffuse</Tag>
    </Tags>
    <Contexts>
        <Context Type="GLSL">
        <Shader ShaderType='Fragment'>
            <Inputs>
            <Input Name="DiffuseColor"><Default>vec4(0,0,0,0)</Default></Input>
            <Input Name="Normal"><Default>vec4(0,0,1,1)</Default></Input>
            </Inputs>
            <Body>
            <![CDATA[
        vec3 N = #Normal.xyz;

        vec3 Diffuse = vec3(0.0);

        for(int i = 0; i < 4; i++)
        {
            //Light Direction
            vec3 L = normalize(u_MriLightSources[i].Position.xyz - State.FragmentPositionInEyeSpaceFromViewCamera);
            float LdotN = max(dot(L,N),0.0);

            float LightVisibility = mriLightVisibility( i, State.Position);

            // Lambertian Diffuse
            Diffuse += LdotN * u_MriLightSources[i].DiffuseColor.rgb * LightVisibility;
        }

        Output.rgb = Diffuse * #DiffuseColor.rgb;
        Output.a = #DiffuseColor.a;
            ]]>
            </Body>
        </Shader>
        </Context>
    </Contexts>
</Node>

Warning

Deprecation: While still supported in legacy scenes, separate Specular and Diffuse shader types are considered deprecated and will be removed in a future version of Mari.

Standalone Shader Example

This example standalone shader does combined Phong specular and Lambertian diffuse calculation. Save this XML in a file and register it with a python command like : mari.gl_render.registerCustomStandaloneShaderFromXMLFile("My Phong and Lambertian","/The/path/to/the/file")

<Node>
    <ID>MY_EXAMPLE_Phong_Specular_And_Lambertian_Diffuse</ID>
    <Inputs>
        <Input Name="DiffuseColor" PrettyName="Diffuse Color"  ShortName="Diff" Scalar="False" PreferredInput='1'></Input>
        <Input Name="SpecularColor" PrettyName="Specular Color" ShortName="Spec" Scalar="False"></Input>
        <Input Name="SpecularShininess" PrettyName="Specular Shininess" ShortName="Shin" Scalar="False" ></Input>
    </Inputs>
    <Attributes>
        <Attribute Name="SpecularShininess" PrettyName="Specular Shininess" Group="" Type="double" Min="0" Max="100">10</Attribute>
    </Attributes>
    <Tags>
        <Tag>_notcacheable</Tag>
        <Tag>_standalone</Tag>
    </Tags>
    <Contexts>
        <Context Type="GLSL">
        <Shader ShaderType='Fragment'>
            <Inputs>
            <Input Name="DiffuseColor"><Default>vec4(0,0,0,0)</Default></Input>
            <Input Name="SpecularColor"><Default>vec4(1,1,1,1)</Default></Input>
            <Input Name="SpecularShininess"><Default>vec4(1,1,1,1)</Default></Input>
            </Inputs>
            <Body>
            <![CDATA[
        vec3 E = normalize(-State.ViewVectorInEyeSpaceFromViewCamera);
        vec3 N = State.NormalInEyeSpaceFromViewCamera;

        float Shininess = $SpecularShininess*#SpecularShininess.r;

        vec3 Specular = vec3(0.0);
        vec3 Diffuse = vec3(0.0);

        for(int i = 0; i < 4; i++)
        {
            //Light Direction
            vec3 L = normalize(u_MriLightSources[i].Position.xyz - State.FragmentPositionInEyeSpaceFromViewCamera);
            //Reflection Vector
            vec3 R = 2.0*dot(L,N)*N - L;

            float LightVisibility = mriLightVisibility( i, State.Position);

            float NdotH = clamp( dot(R,E), 0.001, 0.999);
            float LdotN = max(dot(L,N),0.0);

            // Phong specular
            Specular += pow(NdotH,Shininess) * u_MriLightSources[i].SpecularColor.rgb * LightVisibility;

            // Lambertian Diffuse
            Diffuse += LdotN * u_MriLightSources[i].DiffuseColor.rgb * LightVisibility;
        }

        Output.rgb = Specular * #SpecularColor.rgb + Diffuse * #DiffuseColor.rgb;
        Output.a = #DiffuseColor.a;
            ]]>
            </Body>
        </Shader>
        </Context>
    </Contexts>
</Node>

Procedural Layer Example

This example procedural layer generates a gradient in UV coordinate. Save this XML in a file and register it with a python command like : mari.gl_render.registerCustomProceduralLayerFromXMLFile("My UV Gradient","/The/path/to/the/file")

Adjustment Layer Example

This example adjustment layer adjusts layers below by contrasting them. Save this XML in a file and register it with a python command like : mari.gl_render.registerCustomAdjustmentLayerFromXMLFile("My Contrast","/The/path/to/the/file")

<Node>
    <ID>MY_EXAMPLE_Contrast_Adjustment</ID>
    <Tags>
        <Tag>_adjustment</Tag>
    </Tags>
    <Inputs>
        <Input Name="Input"></Input>
    </Inputs>
    <Attributes>
        <Attribute Name="Contrast" Group="" Type="double" Min="0.0" Max="10.0">1.0</Attribute>
        <Attribute Name="ContrastPivot" PrettyName="Contrast Pivot" Group="" Type="double" Min="0.0" Max="1.0">0.5</Attribute>
    </Attributes>
    <Contexts>
        <Context Type="GLSL">
        <Shader>
        <Inputs>
            <Input Name="Input"><Default>vec4(1,1,1,1)</Default></Input>
        </Inputs>
            <Body>
            <![CDATA[
        Output.rgb = (#Input.rgb-vec3($ContrastPivot))*$Contrast+vec3($ContrastPivot);
        Output.a = #Input.a;
            ]]>
            </Body>
        </Shader>
        </Context>
    </Contexts>
</Node>

Node graph Node Example

This example shows how to register a custom node in the node graph. Save this XML in a file and register it with a python command like : mari.gl_render.registerCustomNodeFromXMLFile("My Color","/The/path/to/the/file")

<Node>
    <ID>MY_EXAMPLE_Color</ID>
    <DefaultName>My Color</DefaultName>
    <Attributes>
        <Attribute Name="Color" Group="" Type="color" >(1.0,1.0,1.0,1)</Attribute>
    </Attributes>
    <Contexts>
        <Context Type="GLSL">
        <Shader>
            <Body>
            <![CDATA[
        Output = $Color;
            ]]>
            </Body>
        </Shader>
        </Context>
    </Contexts>
</Node>

Other Examples

Examples of custom shader and layer XML definition files are supplied in [Mari installation directory]/Media/Examples/Nodes.
Examples of python files to register custom shader and layer registration Python supplied in [Mari installation directory]/Media/Scripts/mari/examples. See custom_shader.py and custom_layer.py.

Custom Shader and Layer Development Workflow

Workflow

Custom layers and shaders are developed in the following workflow. Initially, start creating your own node by the steps below:

  1. Write a node XML file

  2. Register the XML file as a layer or shader in Python API

  3. Instantiate the layer or shader

  4. Check the result

Then, iterate the steps below until the desired result is achieved:

  1. Open the Shader Console from “Tools > Shader Console”

  2. Check if there is any GLSL error

  3. Modify your XML file to fix GLSL or improve the logic

  4. Save the XML file

  5. Hit “All” button in the Shader Console to apply the update to your XML file

  6. Check the result

The “All” button enforces that Mari reloads the XML files, updates all layer and shader instances and recompiles GLSL shaders so that the update to the XML file is reflected in the scene.

Note

In Mari 1.X, shader development used to require restarting Mari the iterative development. In 2.X, shaders and layers can be developed using the Shader Console without restarting Mari.

Write an XML File

This section explains the essence of writing a node XML definition file. Refer to Node XML Reference for the list of elements Mari supports.

Basics

Mari’s layer and shader system is built on top of its node graph based processing engine, so every layer and shader is implemented as nodes. Writing a custom shader or layer is to write a node definition XML file that fulfills particular requirements for those types.

Inputs
Inputs of a node are input points to which preceding nodes supply their outputs.

Attributes
Attributes of a node are parameters of the node that can be adjusted in the GUI.

Tags
Tags of a node are the ways to tag the node so that the application or scripts can treat nodes that have specific tags in a special ways.

Contexts
Contexts of a node are sections to store information of the node specific to a particular context. Currently there is only GLSL context where GLSL code for the node is defined

Note

Mari saves each node information including GLSL code into the node instance so that Mari can replay the scene even if the original custom node XML files are missing. In other words, project archives carry the custom GLSL code and some other node information. If any sensitive code is implemented in the custom node XML files, please reconsider sharing the archive.

Custom Layer and Shader Types

There are 2 types of layers and 3 types shaders that can be registered with Mari’s shader system. Each type has some requirements on tags and the minimum number of inputs to be defined in the XML files.

Type

Compulsory Tags

Inputs

Description

Adjustment Layer

_adjustment

1 or more

An Adjustment Layer adjusts the result of layer stack below the layer and blends the adjusted result with unmodified result through a layer mask.

Procedural Layer

0 or more

A Procedural Layer generates some result procedurally or based on input images and blends the result with the result of layer stack below the layer through a layer mask .

Diffuse Shader

_diffuse
_notcacheable

1 or more

A Diffuse Shader calculates the diffuse component of the lighting. The result is combined with specular component and forms the final lighting result.

Specular Shader

_specular
_notcacheable

0 or more

A Specular Shader calculates the specular component of the specular. The result is combined with diffuse component and forms the final lighting result.

Standalone Shader

_standalone

0 or more

A Standalone Shader does its own calculation to interpret the supplied inputs. For example, it can do its own optimised diffuse, specular and other lighting effect calculation all together and outputs the result. Also, it does not have to do lighting but do some input validation.

Warning

Deprecation: While still supported in legacy scenes, separate Specular and Diffuse shader types are considered deprecated and will be removed in a future version of Mari.

Shader Model Generation

Shader models are an abstraction that represents the inputs associated with a given shader and the properties of those inputs. Shader models are used by Mari when creating multi-channel layers and their associated nodes in order to generate the appropriate input/output streams and drive various user interfaces to match the requested shader. Shader models are generated automatically by Mari when it parses the xml for a shader node during application startup.

Note that shader models will only be generated for standalone lighting nodes, which are indicated by the presence of the “_standalone” tag.

Mari generates the shader model by inspecting the input list at the beginning of the node xml. Mari 4.5 introduces two additional properties for shader inputs. These are “ShortName” and “Scalar”.

Short names are abbreviated versions of the full input names which are used when UI space is limited. e.g. when displaying node input names in the node graph view. A valid shortname should be 4 characters long. If no shortname is provided, one will be automatically generated by Mari. Shortnames are sanitized according to the following rules:

  • If a pretty name is present, that will be used as the basis of the automatically generated shortname. Otherwise, the input name itself will be used as the basis.

  • The starting name will be truncated to four characters.

  • The abbreviated name will be made unique by dropping one or two letters from the end and replacing them with an incremental number from 2-99 as required. Note that there is no 0 or 1 index accounted for. For example, an input named “diffuse”, with a pretty name of “Diffuse” would be abbreviated as follows e.g. Diff, Dif2, Dif3 … Di99.

The scalar property indicates whether an input represents RGB data for calculations (etc specularity or bump)scalar, as opposed to color-managed RGB data. This is not currently used internally, but can be queried via the python API.

The list of registered shader models can be queried via the ShaderModelManager e.g.

...
import mari
shadermodels = mari.shader_models.list()
...

Individual inputs can be queried by name from a ShaderModel object. e.g.

...
firstInputName = myShaderModel.inputNames()[0] # gets the name of the first input in the shader model
firstInput = myShaderModel.input(firstInputName) # gets the first input as a ShaderModelInput object.
...

The properties of individual inputs are accessed via the ShaderModelInput object. e.g.

...
name = myInput.name();
prettyName = myInput.prettyName()
isScalar = myInput.scalar()
shortName = myInput.shortName()
...

Develop GLSL code

This section explains how to develop a GLSL code in a node XML file.

GLSL code for a custom node is to be written into the <Shader>’s <Body> element as text element. It is recommended to use CDATA in <Body> element because CDATA allows GLSL code without escaping special characters for XML. The code will be injected into a larger shader function by Mari, so it should be written as a nested code snippet. No functions should be defined within the snippet. If it is required to have a separate function, refer to Custom GLSL Header and Code.

Mari’s OpenGL support is primarily for OpenGL 3.2 onwards. Therefore, it’s best to write a GLSL code that conforms at least to OpenGL 3.2 and GLSL 1.5 specification. If displacement is needed the code needs to be written for OpenGL/GLSL 4.0+.

Inputs and the predefined “Output” variable are all vec4.

Mari adds special handling of inputs, output and attributes of the Node in extension to the standard GLSL code. Attributes and inputs can be accessed by putting the attribute and input names with prefixes ‘$’ and ‘#’ respectively.

  • Attributes of the node can be accessed within the code by prefixing the name of the attribute with ‘$’. For example $MyAttribute

  • Inputs of the node can be accessed within the code by prefixing the name of the input with ‘#’. For example #MyInput

  • Output of the node is predefined as vec4 Output. The result of the node needs to be written into this.

For example, the code like below will multiply MyInput by MyAttribute and outputs the result as the result of the node.

...
<Body>
<![CDATA[
    Output = vec4($MyAttribute) * #MyInput;
]]>
</Body>
...

Refer to GLSL Reference for the list of globally available functions and uniforms.

Refer to Node Attribute Types for the list of attribute types.

Shader Console

The “Shader Console” is a convenient tool to develop the GLSL code for a custom shader and layer. Go to “Tools > Shader Console” in the main window to open the “Shader Console”.

_images/shader_console.png

When the compilation of GLSL code fails, the error and source code listing views show the error and relevant source code.

Roles of the buttons are described below:

All
“All” button forces Mari to reload the node definition XML files, update the existing shaders and layers and reloads shaders.

Reload XML Files
“Reload XML Files” button forces Mari to reload the node definition XML files. This will not update the existing shader or layers.

Upgrade Existing
“Upgrade Existing” button forces Mari to update existing shaders and layers with up-to-date XML definitions This also forces the shader to reload relevant shaders.

Reload Shader
“Reload Shader” button forces Mari to reload shaders without upgrading existing shaders or layers.

Note

The GLSL code for a custom shader or layer is not compiled or linked until the shader or layer is instantiated and used to render the scene in the canvas. Mari optimises the generated code, so any nodes that are within hidden or cached layers are also not included for shader compilation or linking. In other words, the custom shader or layer needs to be used actively for rendering of the canvas

Note

Some of Mari’s internal shader code is proprietary and encrypted. These encrypted sections of the shader code are displayed as asterisks in the Shader Log and Mari Log.

GLSL Shader Types

OpenGL supports different types of shaders such as fragment shaders, tessellation evaluation shaders etc. Different GLSL code or input default values can be defined for different types of shaders.

Mari’s shader and layer system currently supports two shader types. The layers fed into Displacement input of a shader will be evaluated in the tessellation evaluation shader. The rest are evaluated in the fragment shader.

Write multiple <Shader> elements with ShaderType attribute specifying the shader type. If no ShaderType is supplied, that is used for all shader types that do not have definitions. See the example below.

...
<Context Type="GLSL">
    <Shader>
        <Inputs>
        <Input Name="Input"><Default>vec4(1,1,1,1)</Default></Input>
        </Inputs>
        <Body>
        <![CDATA[
        Output.rgb = #Input.rgb;
        Output.a = #Input.a;
        ]]>
        </Body>
    </Shader>
    <Shader ShaderType="TessEvaluation">
        <Inputs>
        <Input Name="Input"><Default>vec4(0.5,0.5,0.5,1)</Default></Input>
        </Inputs>
        <Body>
        <![CDATA[
        Output.rgb = #Input.rgb * vec3(2.0);
        Output.a = #Input.a;
        ]]>
        </Body>
    </Shader>
</Context>
...

The example code above makes a layer that does scaling by 2.0 for tessellation evaluation shader while it just leaves values for all other shader types. The default input value for Input is 0.5 for tessellation evaluation shaders.

Refer to Node XML Reference for the list of available shader types.

Note

Since the GLSL code for a node can be compiled in different GLSL shader type, source code snippet that compiles for one type may not compile for another. In such case, the GLSL code needs to be split using the mechanism above. One good example is a piece of code that uses gl_FragCoord. It can compile fine for fragment shader stage, but it will cause a compile error as soon as it’s compiled in the tessellation evaluation stage; in other words, when the node is fed into the Displacement input of a shader.

Special Inputs for Shaders

Every shader instance has two inputs added to it.

Displacement
A channel connected to this input will be interpreted as displacement information. This displaces the actual geometry in the canvas.

Vector
A channel connected to this input will be used to render the vectors in the vector inspector tool
Also, there is a special optional input that can be added to a custom shader.

Normal
If an input to a custom shader is named Normal, Mari will auto equip it with Normal input and Bump input. Normal is for normal map input channel while Bump takes in a height map input channel, applies bump mapping internally and passes the resulting normal to the shader code. If they are both connected, the normals are added.

Custom GLSL Header and Code

If multiple nodes’ GLSL codes needs to apply the logic, one common solution is to have a global function shared between callers. It is possible to add declarations and definitions of custom utility functions.

In order to declare and define global functions accessible from various nodes, follow these steps:

  1. Write a function definition into a header file. (In C language, this corresponds to writing .h file)

  2. Write the implementation of the function into a code file. (In C language, this corresponds to writing .c file)

  3. Register them via Python API functions : mari.gl_render.registerCustomHeaderFile(UUID,FilePath) and mari.gl_render.registerCustomCodeFile(UUID,FilePath)

Mari will automatically include those header files into the GLSL shaders that custom nodes can access and include those code files into the linking of the GLSL shader program.

UUID registered together with the file path identifies the file. Mari keeps track of UUID and path pairs to detect missing files. A file registered with the same UUID will overwrite the old one.

Note

Mari stores registered header and code files into a project cache. This is to ensure that a project carries the necessary resources to reproduce the scene. Please bear in mind that the archive will contain the custom header and code files when sending an archive to others. If there is any sensitive code in the custom header or code files, please reconsider sending an archive.

Custom GLSL Header/Code Examples

This example will introduce a function swap_rb that takes in a vec4 and returns a vec4, in which r and b are swapped.

~/Mari/CustomGLSLCode/MyFunc.glslh

vec4 swap_rb(vec4 Input);

~/Mari/CustomGLSLCode/MyFunc.glslc

#version 150

vec4 swap_rb(vec4 Input)
{
    return Input.bgra;
}

~/Mari/Scripts/register_custom_glsl_funcs.py

mari.gl_render.registerCustomHeaderFile("MY_STUDIO_CUSTOM_FUNC_GLSLH","~/Mari/CustomGLSLCode/MyFunc.glslh")
mari.gl_render.registerCustomCodeFile("MY_STUDIO_CUSTOM_FUNC_GLSLC","~/Mari/CustomGLSLCode/MyFunc.glslc")

Shader Macros

Mari will include the registered header and code files into all shader stages. If a function needs to operate differently in different shader stages, or if the function uses a constant or variable specific to a particular stage, Mari’s shader stage defines can be used to conditionally compile a block of code. Refer to Shader Defines for the list of available shader defines.

For example, if a node code needs to call a function that swaps r and b only for a displacement channel that is evaluated in a tessellation evaluation shader stage.

vec4 swap_rb_only_in_displacement(vec4 Input)
{
#if defined MRI_SHADER_TESS_EVALUATION
    return Input.bgra;
#endif
#if defined MRI_SHADER_FRAGMENT
    return Input.rgba;
#endif
}

Access Global Uniforms and Functions

To access global uniforms and functions listed in GLSL Reference, use //! include | GlobalUniforms.glslh and //! include | GlobalFunctions.glslh. For example,

#version 150

//! include | GlobalUniforms.glslh

vec4 object_id_as_color(void)
{
    return vec4(vec3(object_id),1.0);
}

Include a Custom Header File from Another

It is possible to include a custom header file from another custom header file. Use //! include | AnotherHeaderFile.glslh line in your custom header file. The relative paths from the header file is searched for finding the header file to include.

#version 150

//! include | MyVectorFunctions.glslh

vec3 compute_bitangent(vec3 Normal, vec3 Tangent)
{
    //My own cross product function from MyVectorFunctions.glslh
    vec3 Result = my_cross_product(Normal,Tangent);
    return Result;
}

Register an XML File

Once a custom XML definition file is written, it needs registering through Python API so that Mari can recognize it. All the functions are of the form: mari.gl_render.registerCustom*FromXMLFile(Name,FilePath); where Name is the name of the given shader or layer. Forward slash (“/”) in the name can be placed to create nesting for layer menus. E.g. If “My Group/My HSV” is passed to Name argument of registerCustomAdjustmentLayerFromXMLFile(), the given adjustment layer will appear under menu group “My Group” in the adjustment layer creation menu. FilePath is the file path to the XML file.

Python API Functions

Function

Description

mari.gl_render.registerCustomAdjustmentLayerFromXMLFile

Registers the given XML file as a adjustment layer with given sub menu path.

mari.gl_render.registerCustomProceduralLayerFromXMLFile

Registers the given XML file as a procedural layer with given sub menu path.

mari.gl_render.registerCustomDiffuseShaderFromXMLFile

Registers the given XML file as a diffuse shader with given sub menu path.

mari.gl_render.registerCustomSpecularShaderFromXMLFile

Registers the given XML file as a specular shader with given sub menu path.

mari.gl_render.registerCustomStandaloneShaderFromXMLFile

Registers the given XML file as a standalone shader with given sub menu path.

mari.gl_render.registerCustomNodeFromXMLFile

Registers the given XML file as a node graph node with given sub menu path. Accepts an optional Hidden argument (default False) to keep the node out of the Tab Menu and “Add Node” menus while still registering it, and an optional IconPath argument to set the icon shown for the node in the “Add Node” menus.

These python calls can be made directly into “Python Console” on the fly to experimentally register some custom layers and shaders under development.

To register these layers and shaders to be available for a Mari session, these calls need to be made in one of python files executed when Mari is launched. The python files under ~/Mari/Scripts and directories set in the environmental variable MARI_SCRIPT_PATH are loaded by Mari when launched.

Mari currently identifies whether a node is for adjustment layer, procedural layer, diffuse shader, specular shader or standalone shader based on internal menu path. Therefore, those registration functions prepend appropriate menu paths. For example, registerCustomDiffuseShaderFromXMLFile() prepends Lighting/Diffuse before the given Name.

Examples

mari.gl_render.registerCustomAdjustmentLayerFromXMLFile("Cartoon", "~/Mari/CustomAdjustmentLayers/Posterize.xml")
This registers the ~/Mari/CustomAdjustmentLayers/Posterize.xml file as a standalone shader. The registered layer will appear as “Cartoon” in the “Add Shader” menus.

mari.gl_render.registerCustomAdjustmentLayerFromXMLFile("My Custom Adjustment/Posterize", "~/Mari/CustomAdjustmentLayers/Posterize.xml")
This registers the ~/Mari/CustomAdjustmentLayers/Posterize.xml file as an adjustment layer. The registered layer will appear under sub-menu “My Custom Adjustment” in the adjustment layer creation menus.

mari.gl_render.registerCustomNodeFromXMLFile("Procedural/Misc/NoisyOil", "~/Mari/CustomNodes/NoisyOil.xml")
This registers the ~/Mari/Procedural/Misc/NoisyOil.xml file as a node graph item. This node can be accessed in the node graph context menu items in the “Nodes” section under sub-menu “Procedural”->”Misc” menus.

mari.gl_render.registerCustomNodeFromXMLFile("Procedural/Misc/NoisyOil", "~/Mari/CustomNodes/NoisyOil.xml", Hidden=True)
This registers the node so that it remains creatable via the Python API, but it will not appear in the node graph’s Tab Menu (Quick Create) or in the “Add Node” context/main menus. This is useful for nodes that are intended to be created only by script.

mari.gl_render.registerCustomNodeFromXMLFile("Procedural/Misc/NoisyOil", "~/Mari/CustomNodes/NoisyOil.xml", IconPath="~/Mari/CustomNodes/icons/NoisyOil.png")
This registers the node and displays the given icon next to its entry in the “Add Node” context/main menus.

Node XML Reference

Node Elements

<Node>
    <ID>MY_STUDIO_ADJUSTMENT_POSTERIZE</ID>
    <Tags>
        <Tag>_adjustment</Tag>
    </Tags>
    <Inputs>
        <Input Name="Input"></Input>
    </Inputs>
    <Attributes>
        <Attribute Name="Colors" Group="Color Group" Type="double" Min="0.0" Max="256" Logarithmic="true">8</Attribute>
    </Attributes>
    <Groups>
        <Group Name="Color Group" Expanded="false"></Group>
    </Groups>
    <Contexts>
        <Context Type="GLSL">
        <Shader>
            <Inputs>
            <Input Name="Input">
                <Default>vec4(1,1,1,1)</Default>
            </Input>
            </Inputs>
            <Body>
            <![CDATA[
        Output.rgb = round($Colors * #Input.rgb) / $Colors;
        Output.a = #Input.a;
            ]]>
            </Body>
        </Shader>
        </Context>
    </Contexts>
</Node>

<Node>
The root element for defining a node.

<ID>
The unique identifier of the node. Mari uses this to identify the same type of node for node upgrade mechanism. Mari upgrades an existing node with new XML definition provided with the same ID.

<Tags>
The element containing the list of <Tag>.

<Tag>
A tag to attached to a node. Avoid a tag prefixed by underscore(‘_’), which is reserved for special treatment by the application, except for those required for each layer and shader type.

<Inputs>
The element containing the list of <Input>.

<Input>
An input to the node. The Name attribute must be supplied. In the case of a shader type, this becomes a channel input point.

For a standalone shader type, the shader XML file is used to define material shader models, and the following additional attributes are used to define the behaviors of the streams of the material.

  • ShortName is the short name for the input. Short names are used as names of streams in many places for the material system UI and should be unique and four characters or less. If a short name is not specified, one shall be automatically generated for the shader model.

  • Scalar indicates whether the input represents a scalar material stream. The value of this attribute drives many aspects of Mari’s color management system across many UI elements.

  • DefaultColor is the default color of the stream when an empty material is created.

<Attributes>
The element containing the list of <Attribute>.

<Attribute>
A parameter of the layer and shader. It is basically a shader uniform of various types. This will appear as a slider, color chooser etc. The text element of this element will be interpreted as a default value of this attribute.

<Groups>
The element containing the list of <Group>.

<Group>
A group that groups attributes. This element is optional. It allows to configure some aspects of the group. For example, if Expanded=”false” is set on the group, the group is by default closed in the GUI.

<Contexts>
The element containing the list of <Context>.

<Context>
A place to define context-specific information. Currently only GLSL context is supported.

<Shader>
A GLSL context defines a GLSL shader information. If ShaderType attribute is given this shader information is used only for the specified shader type. Available types for ShaderType are

  • Fragment The shader information is used only for fragment shader

  • TessEvaluation The shader information is used only for tessellation evaluation shader

<Inputs>
The element containing the list of <Input>.

<Input>
A GLSL context supplies to each input of the node some information specific to the input. This is currently used to define the default value for the input. An optional attribute of ‘Tags’ can be used to specifiy the ‘_invertable’ flag on an input. This allows the GLSL to toggle the input values from a user button.

<Shader>
    <Inputs>
        <Input Name="Input" Tags="_invertable"><Default>vec4(1,1,1,1)</Default></Input>
    </Inputs>
    <Body>
        <![CDATA[
        Output.rgb = #Input.rgb;
        Output.a = ( bool( #Input_INVERTED) ? (1.0 - #Input.a) : #Input.a);
        ]]>
    </Body>
</Shader>

<Default>
A text element containing GLSL snippet that replaces the corresponding input when no input is plugged in.

<Body>
The text element with GLSL code. The use of CDATA is convenient because many XML specific characters can be used in the code without escaping.

Node Input XML Attributes

Attribute

Example

Description

PreferredInput

PreferredInput=’1’

The index of the input to be used as the default by Mari when making a single node graph connection.

Scalar

Scalar=”False”

Used when generating a shader model to indicate that this input represents scalar data, rather than an RGB color.

ShortName

ShortName=”Diff”

The short name of the input to be displayed where UI space is limited. This should consist of four characters.

Node Attribute XML Attributes

Attribute

Example

Description

Name

Name=”ScaleAmount”

The name of the attribute. This must not contain spaces.

Type

Type=”float”

The type of the attribute. See Node Attribute Types

PrettyName

PrettyName=”Scale Amount”

[Optional] The human readable name of the attribute that appears in the GUI.

Description

Description=”Scales the result by the specified number”

[Optional] The description of the attribute. This will appear as a tooltip in the GUI. “n” behaves as a line break.

Group

Group=”Post Grade”

[Optional] The group this attribute belongs to. The Mari will group the GUI items in collapsible groups.

Min

Min=”(-1.0,-1.0,-1.0,0.0)”

[Optional] Minimum value of the attribute. This defines the minimum value of the slider.

Max

Max=”(10.0,10.0,10.0,1.0)

[Optional] Maximum value of the attribute. This defines the maximum value of the slider.

Logarithmic

Logarithmic=”1”

[Optional] If Logarithmic is set to “1” or “true”, the values in the slider is spaced in logarithmic scale.

Multiline

Multiline=”1”

[Optional] If Multiline is set to “1” or “true”, the text box shown for a “QString” type attribute is shown as a multi-line text box.

Node Attribute Types

Node Attribute Types

Type

GUI Type

Description

GLSL Role

Example Default Value

Default Value Format

float

Slider

Floating point number

float

1.33

Floating point number format

double

Slider

Floating point number

float

2.45

Floating point number format

int

Spinbox or Slider

Integer number

int

1

Integer number format. If Min and Max are specified, a slider is created; Otherwise, a spinbox is created.

bool

Checkbox

Boolean

bool

true

true or false

curve

Curve

Look up curve

1st argument of mriCurveLookup() function

0.0,0.0,0.33,0.33,0.67,0.67,1.0,1.0

4 or more pairs of floating point numbers between 0.0 and 1.0 separated by comma. Each pair is a control point of the curve

color

Color Chooser

Color

vec4

1.0,0.0,0.0,1.0

4 floating point numbers separated by comma in the order of RGBA

stringlist

Combobox

List of strings

int

Item2,Item0,Item1,Item2,Item3

Strings separated by comma. The first value is the default value. The next list is the values displayed in the combobox. The index is passed to GLSL code.

userenum

Combobox

List of strings

String replacement of GLSL code

mult_func2,mult_func0,mult_func1,mult_func2,Multiply By 0,Multiply By 1, Multiply By 2

Strings separated by comma. The first value is the default value. The next list is the list of GLSL replacers, followed by pretty strings displayed in the combobox.
Note: The value of the enums are replaced in the shader code before shader compilation. That means they can be used in preprocessor macros, e.g. for completely excluding particularly expensive code from compilation when not required. However, this means that changes to userenum values requires a full shader recompilation.

image

Image

Image input

1st argument of texture() function

/tmp/default_projection_image.png

Path to the default image file. See Special Node Attributes. To pass this as node Output/Input rather than sampling it locally, see Passing Image Tokens Between Nodes.

imagecube

Image

DDS cubemap image input

1st argument of textureCube() function

/tmp/default_cubemap_image.dds

Path to the default cubemap dds file

locatorlist

Combobox

List of locators, objects, cameras and lights in the scene

mat4 for LocatorType “Matrix”, “Rotation” and “Projector”. vec3 for LocatorType “Scale” and “Translation”

LocatorList is a list of locators, objects, cameras and lights in the scene. The transformation information of the selected locator is passed to GLSL. LocatorType attribute determins what information is passed to GLSL:

  • Matrix : The whole transform matrix is passed to GLSL as mat4

  • TransposeInverseTransposeMatrix : The transpose of inverse of transpose of whole transform matrix is passed to GLSL as mat4

  • Scale : The scale of the locator is passed to GLSL as vec3

  • Rotation : The rotation of the locator is passed to GLSL as mat4

  • Translation : The translation of the locator is passed to GLSL as vec3

  • Projector : The projection matrix of the camera is passed to GLSL as mat4

Vector4f

Grouped Slider

4 component vector

vec4

0.5,1.0,0.0,0.75

4 floating point numbers separated by comma

range

Float Range

4 component vector with x, y, z and w supplying the minimum, low, high and maximum values of the range, respectively.

vec4

0.0,0.25,0.5,1.0

4 floating point numbers separated by comma. They represent the minimum, low, high and maximum values of the range, respectively.

QString

Text Box

Information

None

Informative text

This simply shows a text box in the Node Properties UI with text in it. It is not used by GLSL code.

Special Node Attributes

Image Attributes

Feature

Attribute Type

Attribute Name

Description

Image

image

<Attribute Name>

The image handler.

Image Filtering

userenum

Filter<Attribute Name>

Controls what type of filtering to use on the texture. The values must be from the following but the string part can be anything.
  • 0: Nearest

  • 1: Bilinear

  • 2: Trilinear

  • 3: Anisotropic

Image Filtering Anisotropy

float

Anisotropy<Attribute Name>

Controls the Anisotropy value if the filtering mode is set to 3 (Anisotropic). The value range can be anything but will be clamped between 1 and the MAX value obtained from OpenGL.

Passing Image Tokens Between Nodes

An “image” attribute (see Node Attribute Types) resolves to an image token in GLSL. The mriEncodeImageTokenToVec4 function packs the token into a vec4 so it can be passed through nodes inputs and outputs, but it is not colour or image data itself. Therefore, it must be decoded again with mriDecodeImageTokenFromVec4 before it can be sampled with mriTexture (see Global Functions).

The node below exposes an “image” attribute and outputs it encoded, so it can be connected onward to another node:

<Node>
    <ID>MY_EXAMPLE_Image_Token_Source</ID>
    <DefaultName>My Image Token Source</DefaultName>
    <Attributes>
        <Attribute Name="Image" Type="image"></Attribute>
    </Attributes>
    <Contexts>
        <Context Type="GLSL">
            <Shader>
                <Body>
                <![CDATA[
            Output = mriEncodeImageTokenToVec4($Image);
                ]]>
                </Body>
            </Shader>
        </Context>
    </Contexts>
</Node>

The node below takes that encoded token in as an Input, decodes it, and samples it:

<Node>
    <ID>MY_EXAMPLE_Image_Token_Consumer</ID>
    <DefaultName>My Image Token Consumer</DefaultName>
    <Inputs>
        <Input Name="ImageToken" PrettyName="Image Token"></Input>
    </Inputs>
    <Contexts>
        <Context Type="GLSL">
            <Shader>
                <Inputs>
                <Input Name="ImageToken"><Default>vec4(0,0,0,0)</Default></Input>
                </Inputs>
                <Body>
                <![CDATA[
            vec4 ImageData = mriTexture(mriDecodeImageTokenFromVec4(#ImageToken), State.UV);
            // Perform normal shader operations on Image data
            // ...
                ]]>
                </Body>
            </Shader>
        </Context>
    </Contexts>
</Node>

Warning

An encoded token is only meaningful once decoded and sampled by a receiving node. Viewing it directly, wiring it into a non-token Input, or baking/writing it out produces garbage, since the raw encoded vec4 is not a valid color value.

GLSL Reference

Global Uniforms

Name

Type

Description

Shader Only

u_MriViewType

int

The integer indicating what view type the current rendering is in.
  • 0 : Perspective

  • 1 : Orthographic

  • 2 : UV

Yes

u_MriViewport

vec4

The viewport of current rendering. Left, top, right and bottom are packed as follows:
  • u_MriViewport[0] or u_MriViewport.x : Left

  • u_MriViewport[1] or u_MriViewport.y : Top

  • u_MriViewport[2] or u_MriViewport.z : Right

  • u_MriViewport[3] or u_MriViewport.w : Bottom

Yes

u_MriViewMatrix

mat4

The view matrix

Yes

u_MriModelViewMatrix

mat4

The model-view matrix

Yes

u_MriProjectionMatrix

mat4

The projection matrix

Yes

u_MriNormalMatrix

mat4

The normal matrix

Yes

u_MriModelMatrix

mat4

The model matrix. This transforms 3D coordinates from a local model space to the world space no matter which view is currently rendered.

u_MriObjectId

int

The object id

u_MriObjectCenter

vec3

The center of the bounding sphere of the object in the world coordinate

u_MriObjectRadius

float

The radius of the bounding sphere of the object

u_MriObjectLowerBound

vec3

The lower bound of the object

u_MriObjectUpperBound

vec3

The upper bound of the object

u_MriLightingMode

int

The integer indicating what lighting mode the current rendering is in.
  • 0 : Flat

  • 1 : Basic (Diffuse only)

  • 2 : Full

u_MriLightSources

MriLightSource[]

The array of light source structs. There are MRI_LIGHT_ARRAY_SIZE number of light sources in the scene. This is currently fixed to 4.

u_MriCurrentFrame

int

The current animation frame

u_MriSystemTime

int

The system clock in milliseconds. This wraps in about every 70 minutes to ensure the accuracy

u_MriMaxSupportedTextureSize

vec2

The maximum supported texture size. It is currently 32768 x 32768.

Note

Shader only uniforms are only to be used in a shader because they are view-dependent. If it is used for a procedural or adjustment layer, the layer cannot be cached, merged, flattened or converted to a paintable.

Global Functions

Function

Description

float mriCurveLookup( int Line, float InValue);

1D curve look up (remap) function to use for attributes of type curve.

Line: The curve ID passed from curve attribute.
InValue: The input value to be remapped.

mriPerlinNoise(vec2 P);

Perlin noise generation function based on 2D input.

P: Input to the noise.

float mriPerlinNoise(vec3 P);

Perlin noise generation function based on 3D input.

P: Input to the noise.

float mriPerlinNoise(vec4 P);

Perlin noise generation function based on 4D input.

P: Input to the noise.

float mriSimplexNoise(vec2 P);

Simplex noise generation function based on 2D input.

P: Input to the noise.

float mriSimplexNoise(vec3 P);

Simplex noise generation function based on 3D input.

P: Input to the noise.

float mriSimplexNoise(vec4 P);

Simplex noise generation function based on 4D input.

P: Input to the noise.

float mriCellular(vec3 P,int norm);

Cellular (Worley) noise generation function based on 3D input and different types of norm(distance) calculation. This returns vec3 in which F1, F2 and F1-F2 are returned in r, g and b of the resulting vec3 respectively. Cellular noise is a noise obtained by calculating distance to randomly generated points. The definition of the distance is the norm. F1 is the distance to the closest point while F2 is the distance to the 2nd closest point.

P: Input to the noise.
norm: The norm(distance calculation) definition to use.

  • 0 : Manhattan norm (||v||1)

  • 1 : Euclidian norm (||v||2)

  • 2 : Chebyshev(Infinity) norm (||v||∞)

  • 3 : P-norm where P=0.5 (||v||0.5)

vec4 mriConvertLinearToLinear(vec4 color);

Color space conversion function from Linear color space to Linear color space. In other words, no conversion is applied.

color: Input color to convert.

vec4 mriConvertsRGBToLinear(vec4 color);

Color space conversion function from sRGB color space to Linear color space.

color: Input color to convert.

vec4 mriConvertLinearTosRGB(vec4 color);

Color space conversion function from Linear color space to sRGB color space.

color: Input color to convert.

vec4 mriConvertGamma22ToLinear(vec4 color);

Color space conversion function that applies inverse gamma correction 2.2.

color: Input color to convert.

vec4 mriConvertLinearToGamma22(vec4 color);

Color space conversion function that applies gamma correction 2.2.

color: Input color to convert.

genType mriBlendToDefault(genType default, vec4 input);

Generally used in nodes to blend a value from an input towards a default value from an attribute based on the input’s alpha value.
e.g. float roughness = blend_to_default($RoughnessAttr, #RoughnessInput);
May be float, vec2, vec3 or vec4.
The default value should match the return type.
In cases where the return type is of lower dimension than the input value, the first components of the input value will be used (i.e. red channel for float, rg channels for vec2, rgb channels for vec3).

default: The type of default is also the return type.
input: The input value.

genType mriSaturate(genType value);

Utility function that returns value clamped between 0 and 1. Functionally the same as clamp(value, 0.0, 1.0).
May be float, vec2, vec3 or vec4.

value: The value to clamp.

genType mriInverseLerp(genType x, genType y, genType a);

Performs an inverse linear interpolation. Effectively, return b such that a = mix(x, y, b)
May be float, vec2, vec3 or vec4.

x: The minimum value of the range.
y: The maximum value of the range.
a: The value to return the interpolation factor of.

vec3 mriVectorBias(vec3 vector);

Pack a vector from the range [-1,1] to a colour in the range [0,1]. Equivalent of vec3 biased = vector * 0.5 + 0.5;.

vector: The vector to bias.

vec3 mriVectorUnbias(vec3 colour);

Unpack a colour from the range [0,1] to a vector in the range [-1,1]. Equivalent of vec3 biased = vector * 2.0 - 1.0;.

colour: The vector to bias.

vec3 mriVectorPaintToTangent2D(vec3 screen);

Transforms a vector from screen space to tangent space. Disregards the blue channel (Z component) and normalizes the resulting vector.

screen: The vector to transform.

vec3 mriVectorPaintToTangent3D(vec3 screen);

Transforms a vector from screen space to tangent space.

screen: The vector to transform.

vec3 mriVectorWorldToTangent(vec3 tangent);

Transforms a vector from world space to tangent space.

tangent: The vector to transform.

vec3 mriVectorTangentToWorld(vec3 tangent);

Transforms a vector from tangent space to world space.

tangent: The vector to transform.

vec3 mriVectorTangentToScreen(vec3 tangent);

Transforms a vector from tangent space to screen space.

tangent: The vector to transform.

vec3 mriVectorTangentToEye(vec3 tangent);

Transforms a vector from tangent space to eye/camera space.

tangent: The vector to transform.

genType mriVectorNormalize(genType vector);

Returns the normalized vector. In cases where vector is zero length, a zero length vector is returned.
Effectively a safety precaution against undefined behaviour when dividing by zero using the intrinsic normalize() function.
May be vec2 or vec3.

tangent: The vector to transform.

genType mriDistToLine(genType point, genType lineStart, genType lineEnd);

Returns the distance from point to the nearest position on the line defined by start and end positions.
May be vec2 or vec3.

point: The point to get the distance to.
lineStart: The point that defines the start of the line.
lineEnd: The point that defines the end of the line.

vec4 mriTexture(token, genType coords);

Returns the given image sampled at the given coordinates. Similar to the OpenGL function texture.

token: The image to sample from. Generally the attribute from an “image” type attribute on a node, but can be a sampler2D or samplerCube.
coords: The texture coordinates to use. vec2 for 2D images and vec3 for cubemaps.

vec4 mriTextureLod(token, genType coords, float lod);

Returns the given image sampled at the given mip level. Similar to the OpenGL function textureLod.

token: The image to sample from. Generally the attribute from an “image” type attribute on a node, but can be a sampler2D or samplerCube.
coords: The texture coordinates to use. vec2 for 2D images and vec3 for cubemaps.
lod: The mipmap level to sample from.

void mriTextureGatherAll(token, genType coords, out vec4 red, out vec4 green, out vec4 blue, out vec4 alpha);

Outputs the neighboring texels of the given image to the given coordinates, for each component. Similar to running the OpenGL function textureGather for all 4 components.

token: The image to sample from. Generally the attribute from an “image” type attribute on a node (uvec2 for textures or uvec4 for color managed textures), but can be a sampler2D or samplerCube.
coords: The texture coordinates to use. vec2 for 2D images and vec3 for cubemaps.

Note that if the GL function textureGather is unsupported on the current platform, a manual 4-tap value for 2D textures will be returned and for cubemaps a single value will be returned in each component of the return vector.

vec4 mriTextureGather(token, genType coords, int component);

Returns the neighboring texels of the given image to the given coordinates, for the given component. Similar to the OpenGL function textureGather.

token: The image to sample from. Generally the attribute from an “image” type attribute on a node (uvec2 for textures or uvec4 for color managed textures), but can be a sampler2D or samplerCube.
coords: The texture coordinates to use. vec2 for 2D images and vec3 for cubemaps.
component: The component of the texture to sample, must be an integer from 0-3 representing R,G,B or A component index.

Note it is more efficient to use mriTextureGatherAll rather than multiple calls of mriTextureGather at the same coordinate for different components.
Note that if the GL function textureGather is unsupported on the current platform, a manual 4-tap value for 2D textures will be returned and for cubemaps a single value will be returned in each component of the return vector.

vec4 mriEncodeImageTokenToVec4(uvec4 ImageToken);

Encodes an image token into a vec4 so it can be carried through a regular node Input/Output connection, which are always vec4.

ImageToken: The image token to encode. Generally the attribute from an “image” type attribute on a node.

uvec4 mriDecodeImageTokenFromVec4(vec4 EncodedImageToken);

Decodes a vec4 previously produced by mriEncodeImageTokenToVec4 back into an image token that can be passed to mriTexture and related sampling functions.

EncodedImageToken: The encoded token, as produced by mriEncodeImageTokenToVec4.

Note

The decoded value is only meaningful if it was produced by mriEncodeImageTokenToVec4. Feeding an arbitrary/unconnected vec4 into mriDecodeImageTokenFromVec4 (or viewing the encoded vec4 directly without decoding it) gives undefined, garbage results.

Global Compositing Functions

Function

Description

vec4 Comp_Add(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Add mode

vec4 Comp_BurnHighlights(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Burn Highlights mode

vec4 Comp_BurnMidrange(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Burn Midrange mode

vec4 Comp_BurnShadows(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Burn Shadows mode

vec4 Comp_Clear(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Clear mode, which reduces the alpha of Base color

vec4 Comp_Color(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Color mode

vec4 Comp_ColorBurn(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Color Burn mode

vec4 Comp_ColorDodge(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Color Dodge mode

vec4 Comp_Darken(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Darken mode

vec4 Comp_Default(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Default (aka Normal or Over) blending mode

vec4 Comp_Difference(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Difference mode

vec4 Comp_DodgeHighlights(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Dodge Highlights mode

vec4 Comp_DodgeMidrange(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Dodge Midrange mode

vec4 Comp_DodgeShadows(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Dodge Shadows mode

vec4 Comp_Exclusion(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Exclusion mode

vec4 Comp_HardLight(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Hard Light mode

vec4 Comp_HardMix(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Hard Mix mode

vec4 Comp_Hue(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Hue mode

vec4 Comp_InverseDifference(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Inverse Difference mode

vec4 Comp_Invert(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Invert mode

vec4 Comp_Lighten(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Lighten mode

vec4 Comp_Luminance(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Luminance mode

vec4 Comp_Multiply(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Multiply mode

vec4 Comp_Overlay(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Overlay mode

vec4 Comp_PinLight(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Pin Light mode

vec4 Comp_Saturation(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Saturation mode

vec4 Comp_Screen(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Screen mode

vec4 Comp_SoftLight(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Soft Light mode

vec4 Comp_SpongeDesaturate(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Sponge Desaturate mode

vec4 Comp_VividLight(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Vivid Light mode

vec4 Comp_Contrast(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Contrast mode

vec4 Comp_Decontrast(vec4 Base,vec4 Blend,float Opacity)

Blends the Blend color over Base with the Opacity using Decontrast mode

Fragment Shader Stage Functions

Function

Description

float mriLightVisibility(int LightIndex,vec3 FragmentPosition)

Returns the visibility of light source from the given fragment position in object coordinate. Multiplying the lighting result by this number gives the shadow effect.

LightIndex: The index of light source to test the visibility against.
FragmentPosition: The position of the fragment to test the light source visibility from. The position must be in object coordinate

vec3 mriSampleEnvironment(Name, vec3 dir, float level)

Returns the lighting value, from an external environment map with a given name, at the given level in the given direction.

Name: The name of the environment map to sample. This may be a custom registered environment map or one of Mari’s own maps (detailed below).
dir: The direction to sample in. Generally a surface normal or reflection vector.
level: Generally this input determines the surface roughness to sample for, with values towards 1.0 giving blurrier values.

Mari comes with a few commonly used environment maps which can be sampled using these names:

  • Environment
    The current environment map, unaltered.
    Note that level has no effect on this environment map.

  • Lambert
    Standard Lambertian lighting from the environment. Commonly used for diffuse environment lighting.
    Note that level has no effect on this environment map.

  • GGX
    Environment map convolved with GGX BRDF. Commonly used for environment reflections in shaders which use the GGX BRDF.
    level corresponds to the surface roughness.

  • NdotL
    Environment map convolved with Mari’s BRDF. Used by Mari’s own BRDF shader.
    level corresponds to the inverse of the surface glossiness (lower values are more glossy, higher values are less glossy).

vec2 mriBRDF_EnvBRDF_GGX_Lookup(float Roughness, float NdotV)

Returns modifiers for the GGX environment light to approximately account for Fresnel at different roughnesses.
Performs a texture-based lookup to provide more accurate values.
Commonly used as:

vec2 GGXLookup = mriBRDF_EnvBRDF_GGX_Lookup(roughness, NdotV);
vec3 GGXEnvironmentReflection = mriSampleEnvironment(GGX, direction, roughness) * (SpecularColor * GGXLookup.xxx + GGXLookup.yyy);

Roughness: The roughness of the surface.
NdotV: The dot product of the surface normal with the view direction.

vec2 mriBRDF_EnvBRDF_GGX_Approx(float Roughness, float NdotV)

Returns modifiers for the GGX environment light to approximately account for Fresnel at different roughnesses.
Performs a coarse approximation without a texture lookup, potentially more lightweight but less accurate results.
Commonly used as:

vec2 GGXLookup = mriBRDF_EnvBRDF_GGX_Approx(roughness, NdotV);
vec3 GGXEnvironmentReflection = mriSampleEnvironment(GGX, direction, roughness) * (SpecularColor * GGXLookup.xxx + GGXLookup.yyy);

Roughness The roughness of the surface.
NdotV: The dot product of the surface normal with the view direction.

bool mriEnvironmentLightOn()

Returns whether the environment light is active. Can be useful for skipping environment lighting calculations when they would do nothing.

MriLightSource Struct Members

Name

Type

Description

Position

vec4

The position of the light source

AmbientColor

vec4

The ambient color of the light source

DiffuseColor

vec4

The diffuse color of the light source

SpecularColor

vec4

The specular color of the light source

SpotCutoff

float

The spot light cutoff factor of the light source. Mari’s standard lighting shaders do not use this. This is passed from the python API.

SpotExponent

float

The spot light exponent factor of the light source. Mari’s standard lighting shaders do not use this. This is passed from the python API.

ConstantAttenuation

float

The constant attenuation factor of the light source. Mari’s standard lighting shaders do not use this. This is passed from the python API.

LinearAttenuation

float

The linear attenuation factor of the light source. Mari’s standard lighting shaders do not use this. This is passed from the python API.

QuadraticAttenuation

float

The quadratic attenuation factor of the light source. Mari’s standard lighting shaders do not use this. This is passed from the python API.

MriFragmentState Struct Members

Name

Type

Description

Shader only

Position

vec3

The position of the fragment.

UV

vec2

The UV coordinate of the fragment.

Normal

vec3

The surface normal of the fragment.

Tangent

vec3

The tangent vector of the fragment.

Bitangent

vec3

The bitangent vector of the fragment.

AmbientOcclusion

float

The ambient occlusion factor of the fragment. 1 means it is completely occluded while 0 means it is not occluded at all

PatchId

float

The patch ID of the fragment.

FaceId

float

The Face ID of the fragment.

NormalInEyeSpaceFromViewCamera

vec3

The surface normal in eye coordinate from the current view

Yes

TangentInEyeSpaceFromViewCamera

vec3

The tangent in eye coordinate from the current view

Yes

BitangentInEyeSpaceFromViewCamera

vec3

The bitangent in eye coordinate from the current view

Yes

ViewVectorInEyeSpaceFromViewCamera

vec3

The view vector in eye coordinate from the current view

Yes

FragmentPositionInEyeSpaceFromViewCamera

vec3

The fragment position in eye coordinate from the current view

Yes

NormalInEyeSpaceFromProjectionCamera

vec3

The surface normal in eye coordinate from the current camera from which paint buffer is projected

Yes

TangentInEyeSpaceFromProjectionCamera

vec3

The tangent in eye coordinate from the current camera from which paint buffer is projected

Yes

BitangentInEyeSpaceFromProjectionCamera

vec3

The bitangent in eye coordinate from the current camera from which paint buffer is projected

Yes

ViewVectorInEyeSpaceFromProjectionCamera

vec3

The view vector in eye coordinate from the current camera from which paint buffer is projected

Yes

FragmentPositionInEyeSpaceFromProjectionCamera

vec3

The fragment position in eye coordinate from the current camera from which paint buffer is projected

Yes

FragmentPositionInNdcSpaceFromProjectionCamera

vec3

The fragment position in normalized device coordinate from the current camera from which paint buffer is projected

Yes

Note

Shader only struct members are only to be used in a shader because they are view-dependent. If it is used for a procedural or adjustment layer, the layer cannot be cached, merged, flattened or converted to a paintable.

MriTessellationEvaluationState Struct Members

Name

Type

Description

Position

vec3

The position of the evaluation point.

UV

vec2

The UV coordinate of the evaluation point.

Normal

vec3

The surface normal of the evaluation point.

Tangent

vec3

The tangent vector of the evaluation point.

Bitangent

vec3

The bitangent vector of the evaluation point.

AmbientOcclusion

float

The ambient occlusion factor of the evaluation point. 1 means it is completely occluded while 0 means it is not occluded at all

PatchId

float

The patch ID of the evaluation point.

Shader Defines

Define

Value

Description

MRI_SHADER_VERTEX

1

This is defined in the compilation of vertex shader.

MRI_SHADER_GEOMETRY

1

This is defined in the compilation of geometry shader.

MRI_SHADER_TESS_CONTROL

1

This is defined in the compilation of tessellation control shader.

MRI_SHADER_TESS_EVALUATION

1

This is defined in the compilation of tessellation evaluation shader.

MRI_SHADER_FRAGMENT

1

This is defined in the compilation of fragment shader.

MRI_LIGHT_ARRAY_SIZE

4

This indicates how many light sources exist in the u_MriLightSources struct array.

MARI_VERSION

eg. 30401 for Mari 3.4v1

This indicates the current Mari version in the following form: MmmRR, where M is the major version number, mm is the minor verion number filled with 0 and RR is the release number filled with 0.

Deprecated Functions

Warning

These functions will be removed in a future version of Mari, please alter your code to use the replacements to avoid shader errors.

Function

Introduced

Deprecated

Removed

Replacement

There are currently no deprecated functions awaiting removal.

Custom Environment Cubemaps

As of Mari 4.6v2, it is possible to provide Mari with a shader with which to generate an environment cubemap to sample from nodes.

This may be particularly helpful in cases where you desire an environment map that is convolved with a BRDF that Mari does not natively support.

Cubemap Overview

Similarly to registering custom GLSL header/body files, you register a shader file with Mari which it will then use to generate a cubemap
mari.gl_render.registerCustomCubemapFile(UUID,FilePath,MinMipSize)

Similar to the other registration functions, it takes a unique identifier and a file path

  • UUID is the unique identifier for this cubemap and is the name by which it will be sampled in shader code.
    It must contain only letters, numbers and underscores.

  • Filepath is the path to the shader file to use.

  • MinMipSize is the smallest dimension to generate mip maps for. This defaults to 16 if not supplied.
    It must be a power of two.

The argument MinMipSize controls mip map generation. This behaves slightly differently depending on whether cubemap arrays are enabled in Mari’s preferences and if they are supported by the hardware running Mari

  • If cubemap arrays are enabled and supported then 8 layers of cubemap will be generated at full resolution (the resolution specified by the environment light’s settings).

  • If cubemap arrays are disabled or unsupported then each mip level of the cubemap will be generated up to the size specified by the MinMipSize.
    e.g. If the environment light is set to 128 resolution and the MinMipSize is set to 16, then 4 mip levels will be generated (128, 64, 32 and 16).

For this reason, it is good practice to use the smaller mip maps to store blurrier environment maps in, which need less detail to appear more or less correct. While this will have no effect for cubemap arrays (each entry is at the full resolution) it will help in cases where cubemap arrays are not available.

Note

In both cases, specifying a MinMipSize of 0 will only generate a single image at full resolution. A single cubemap in the case of cubemap arrays and a single mipmap in the case where cubemap arrays are unavailable.

Cubemap Generation Shader Defines

The cubemaps are generated in batches where possible, with an array of up to 8 output levels generated at once for a given sample direction.
Each entry of the output is placed into a layer of the resulting cubemap array (or mipmap, in the case where cubemap arrays are unavailable).
As such, each entry in the output array receives a corresponding float value between 0.0 and 1.0 (see the uniforms listed below), which would commonly be used to do something like determine the roughness value to use for that entry in the output array - with a fully smooth entry having the value 0.0 and a fully rough entry having the value 1.0.

The predefined token MRI_CUBERENDER_BATCH_SIZE specifies how many layers (or mips) are being generated in this batch. In most cases this will either be 8 or 1.

The other predefined token is MRI_CUBERENDER_NUM_SAMPLES
It specifies the number of samples to take of the source cubemap.
This is set as whichever is the smaller of the square of the resolution of the cubemap being generated or an upper limit of 4096. Note that on OSX 10.11 or earlier, the upper limit is set to 1024 as higher values can cause instability.

Note

This sample count can be safely ignored if you’d prefer to do your sampling some other way, but it appears to strike a good balance between quality and speed.

There are various numeric predefines, too, which are common in environment sampling.

  • PI The famous constant.

  • TWO_PI PI * 2.0

  • INV_PI 1.0 / PI

  • HALF_PI PI / 2.0

  • INV_TWO_PI 1.0 / (PI * 2.0)

Cubemap Generation Shader Uniforms

There are various uniform variables which can be helpful.

  • samplerCube s_InputTexture
    This is the source/input cube map. It is generated from the given environment image.

  • int s_InputCubeSize
    The size of the source/input cube map to read from.

  • int s_OutputCubeSize
    The size of the output cube map being generated by this shader.

  • float s_SolidAngleTexel
    Solid angle of a texel of the input cube map. This is useful for sampling lower mips of the input cubemap at angles that face away from the surface normal.

  • float s_Values[MRI_CUBERENDER_BATCH_SIZE]
    Values that vary from 0.0 - 1.0 based on the batch index. These are useful to use as, e.g., a roughness value to figure out how wide the sample spread should be.
    Level 0 will always have 0.0 and the last level will always have 1.0 (or mip maps rather than levels, in the case where cubemap arrays are unsupported). With levels in between having a linear blend.

  • float MRI_CUBERENDER_INV_NUM_SAMPLES
    This is simply 1.0 / MRI_CUBERENDER_NUM_SAMPLES

Cubemap Generation Shader Functions

There are various functions defined, too.

  • void GenerateEnvMap (in vec3 normal, inout vec3 outputs[MRI_CUBERENDER_BATCH_SIZE])
    This is the main generation function and your shader code must define it.
    It takes in a world normal and expects the outputs to be filled with the final sampled values for storing in the cubemap. Each entry in the output array has been initialised to vec3(0.0).

  • mat3 GetTangentBasis(vec3 normal)
    This calculates a rotation matrix to use for converting tangent space vectors into world space vectors relative to the given normal.

  • vec3 UniformSampleHemisphere(vec2 U) and vec3 UniformSampleHemisphere(vec2 U, out float PDF).
    Given a disc coordinate U (with U.x being distance from the disc centre and U.y being rotation around the disc) return a sample vector that is evenly distributed across the hemisphere.
    If the secondary version is used, PDF will contain the PDF for the returned vector.

  • vec3 CosineSampleHemisphere(vec2 U) and vec3 CosineSampleHemisphere(vec2 U, out float PDF).
    Given a disc coordinate U (with U.x being distance from the disc centre and U.y being rotation around the disc) return a sample vector that is evenly cosine weighted across the hemisphere.
    If the secondary version is used, PDF will contain the PDF for the returned vector.

  • float RadicalInverse(uint bits)
    Calculate the radical inverse of the given unsigned integer.

  • vec2 Hammersley(int Sample, float NumSamplesReciprocal)
    Return a disc coordinate generated using Hammersley distribution. This disc coordinate is suitable for passing into the SampleHemisphere functions.
    It takes the current sample index, Sample and the reciprocal of the total sample count, NumSamplesReciprocal.

  • float SolidAngleMipLevel(int NumSamples, float PDF)
    Return a the mip level to sample the source cubemap at for the given PDF.
    It takes the total sample count, NumSamples and the PDF for the given sample vector, PDF.

Cubemap Example Shader Code

A very simplistic example of a shader file might look like this. This simply averages the environment map across the hemisphere and blends it out to pure red depending on the value.

The shader code must contain at least the version identifier, include CubeRender_Main.glslh and CubeRender_Utils.glslh, and define a function called GenerateEnvMap which matches the signature shown below.

#version 150

//! include | CubeRender_Main.glslh
//! include | CubeRender_Utils.glslh

void GenerateEnvMap (in vec3 normal, inout vec3 outputs[MRI_CUBERENDER_BATCH_SIZE])
{
    mat3 TangentToWorld = GetTangentBasis(normal);

    for( int i = 0; i < MRI_CUBERENDER_NUM_SAMPLES; i++ )
    {
        vec2 U = Hammersley(i, MRI_CUBERENDER_INV_NUM_SAMPLES);

        float PDF;
        vec3 L = UniformSampleHemisphere(U, PDF);

        float Mip = SolidAngleMipLevel(MRI_CUBERENDER_NUM_SAMPLES, PDF);
        vec3 color = textureLod(s_InputTexture, TangentToWorld * L, Mip).rgb * PDF;

        for (int indexBatch = 0; indexBatch < MRI_CUBERENDER_BATCH_SIZE; indexBatch++)
        {
            outputs[indexBatch] += mix(color, vec3(1.0, 0.0, 0.0), s_Values[indexBatch]) * MRI_CUBERENDER_INV_NUM_SAMPLES;
        }
    }
}

Cubemap Example Sampling Code

Once registered, a custom cubemap may be sampled from node shader code using the function mriSampleEnvironment which is defined as:
vec3 mriSampleEnvironment(UUID, vec3 direction, float level);

For example, to sample the GGX cubemap that Mari ships with, you might sample it like this. .. code-block:

vec3 SampleDirection;
float Roughness;
...
vec3 GGXSpecular = mriSampleEnvironment(GGX, SampleDirection, Roughness);

Alternatively, if the example code above was registered using mari.gl_render.registerCustomCubemapFile("CustomEnv", "/path/to/shader.frag", 16), it might be sampled like this. .. code-block:

vec3 SampleDirection;
float Redness;
...
vec3 CustomEnv = mriSampleEnvironment(CustomEnv, SampleDirection, Redness);

Built in Cubemaps

Mari comes with some build in UUIDs for cubemaps which can be used in shaders.

  • Environment
    Simply the current environment. This has no mip levels.

  • Lambert
    Simple lambertian lighting. This has no mip levels.

  • GGX
    GGX convolved cubemap. This takes in Roughness as it’s level, with 0.0 being perfectly smooth and 1.0 being fully rough.

  • NdotL
    Mari’s BRDF convolved cubemap. This takes in 1.0-Glossiness as it’s level, with 0.0 being fully glossy and 1.0 being fully rough.

Custom Image Maps

As of Mari 4.6v2, it is possible to provide Mari with a shader with which to generate an image map to sample from nodes.
This may be particularly helpful in cases where you desire a precomputed lookup texture or similar.
All images are generated as floating point RGBA at 512x512 resolution and no mipmaps are generated.

Imagemap Overview

Similarly to registering custom GLSL header/body files, you register a shader file with Mari which it will then use to generate an imagemap;
mari.gl_render.registerCustomImagemapFile(UUID,FilePath)

Similar to the other registration functions, it takes a unique identifier and a file path;

  • UUID is the unique identifier for this imagemap and is the name by which it will be sampled in shader code.
    It must contain only letters, numbers and underscores.

  • Filepath is the path to the shader file to use.

Imagemap Generation Shader Functions

There is only one function defined.

  • vec4 GenerateImageMap(vec2 uv)
    This is the main generation function and your shader code must define it.
    It takes in a UV coordinate normal and expects the function to return the value to be stored at that UV coordinate.

Imagemap Example Shader Code

A very simplistic example of a shader file might look like this. This simply generates a 10x10 white/black checkerboard.
The shader code must contain at least the version identifier, include ImageRender.frag and define a function called GenerateImageMap which matches the signature shown below.

#version 150

//! include | ImageRender.frag


vec4 GenerateImageMap(in vec2 uv)
{
    vec2 uvscaled = floor(uv*10.0);
    float checker = mod(uvscaled.x + uvscaled.y,2.0);
    return vec4(vec3(checker), 1.0);
}

Imagemap Example Sampling Code

Once registered, a custom imagemap may be sampled from node shader code using the function mriCustomImage which is defined as:
vec4 mriCustomImage(UUID, vec2 uv);
If the example code above was registered using mari.gl_render.registerCustomCubemapFile("Checkerboard", "/path/to/shader.frag"), it might be sampled like this:
vec4 checker = mriCustomImage(Checkerboard, State.UV);

Built in Imagemaps

Mari only ships with one custom imagemap.

  • GGXLookup
    This is a companion lookup texture for the GGX cubemap.
    See the function mriBRDF_EnvBRDF_GGX_Lookup documented above.