In this tutorial I’ll discuss how to write a colour attenuation light filter for the Arnold Renderer. The default light filters allow for control over the intensity of the light, but not the colour. So.. Let’s write our own! This is meant as an introductory shader writing tutorial for Arnold. ~09/16




Sections covered:

– Logic

– Arnold shader structure

– MTD file

– Interface file

– Compiling

– Installing


What you’ll need:

– The Arnold SDK []

– A C++ compiler. I like G++ on osx/linux and MingW on Windows (which is a port of G++). But really – any compiler will do the job.

– The Arnold API docs []



– Windows / Arnold

– OSX / Arnold Check back soon!




Before we start coding, let’s figure out the logic. The light filter will control the light’s colour, depending on a distance from the light source. Since we want a colour gradient, we need two colours – “Color Start” and “Color End”. To control the colour gradient, we will have to define two distances from the light. We’ll call the first one “Attenuation Start” and the second one “Attenuation End”. We’ll name the distance from the shading point (at which the shader is evaluated) to the light source “Light Distance”.


This means, in pseudocode:



The only thing we still need to figure out is what colour we should pick when the light distance is in between the two attenuation points. This is easy – we will need to use some kind of linear colour interpolation and supply some percentage. We can calculate this percentage (between 0 and 1):


\(percentage =\frac{Light Distance – Attenuation Start}{Attenuation End – Attenuation Start}\)



That’s all the logic there is to it – really rather simple. Now, let’s start writing code.



Arnold shader structure


The basic Arnold shader structure (or template) looks like this. I’ve commented in parts to explain the sections.


In our case, we’re interested in calculations done at every shading point. For now, we’ll write some code and we’ll worry about the UI integration later.


So, what values do we need from Arnold? We need to know both the distance from the shading point to the light and the light intensity at that shading point. Looking through the Arnold API, we can see that the distance from the shading point to the light can be queried and written. The distance value is stored in sg->Ldist  and the light intensity in sg->Liu . And since colour interpolation is a task regularly performed in a raytracer, it already provides a linear colour interpolation function. This can be called with AiColorLerp() .


Writing the logic down then, leaves us with:


The logic is working, but now we should make our variables work with the whole Arnold system. Like this, we would have no way to change the variables through the Maya UI.


There’s 3 key parts to this. The variables need to be defined in an enumeration type, they need to be defined in node_parameters{} , and we need to get them inside of shader_evaluate{} .






This leaves us with the finished .cpp file! It is worth noting that throughout the process of writing this shader, I continually compile and run it, so Arnold gives me feedback about the variable values and I know whether I’m on the right track. The easiest way to return values with these multithreaded processes is by using  AiMsgInfo("SOME_TEXT %f", some_float_variable);




Compiling the shader into a shared library


For this tutorial, I’ll be compiling through the command line with G++.  If you prefer doing this through an IDE, there’s a tutorial for that on the Arnold support site. What’s important here is that we link the Arnold libraries and set the include path properly, so the compiler can find  #include <ai.h>. Don’t forget to change the pseudocode like $PATH_TO or Arnold-X.X.X.X.



g++ -std=c++11 -O3 -I"$PATH_TO/Arnold-X.X.X.X-darwin/include" -L"$PATH_TO/Arnold-X.X.X.X-darwin/bin" -lai -dynamiclib $PATH_TO/lightcolorattenuation.cpp -o $PATH_TO/lightColorAttenuation.dylib


If you’re on Linux, I’m sure you can already do this part (lol).

g++ -std=c++11 -03 -o $ZENOCAMERA/src/zoic.os -c -fPIC -D_LINUX -I$PATH_TO/Arnold-X.X.X.X-linux/include $PATH_TO/zoic.cpp; g++ -o $PATH_TO/ -shared $PATH_TO/zoic.os -L$PATH_TO/Arnold-X.X.X.X-linux/bin -lai


On Windows (using MingW (64 bit version – important!), not the usual cmd prompt):

g++ -std=c++11 -O3 -I"$PATH_TO/Arnold-X.X.X.X-windows/include" -L"$PATH_TO/Arnold-X.X.X.X-windows/bin" -lai --shared $PATH_TO/lightColorAttenuation.cpp -o $PATH_TO/lightColorAttenuation.dll



Metadata file


Now we need to write the .MTD metadata file. These metadata files will be automatically loaded when the library is first loaded.

Extra information on metadata files can be found here: []


Writing this file is rather straightforward apart from a few quirks. We need to supply a HEX number since Maya needs a unique ID assigned to nodes to keep track of custom nodes. You can take whichever number.




Maya interface file


All that’s left to do now is write the Maya interface file. This is a small python file. I won’t go into this as it is pretty self-explanatory.


More information on these interface files can be found here: []




Installing the shader


Usually Arnold shaders get organised into two folders, bin and ae. Inside the bin folder go both the compiled shader (.dll, .dylib, .so) and inside the ae folder goes the python maya interface file. It is important that the metadata file is in the same folder as the compiled shader.


Set the following environment variables, replacing "$PATH_TO_SHADER" with the actual path on your machine.










It’s also possible to copy the files into your MtoA install, but I personally prefer the first option. Just copy the files like this:


Files in /bin  go to [$MTOA_LOCATION]/shaders

Files in /ae  go to [$MTOA_LOCATION]/scripts/mtoa/ui/ae



That’s it guys! Hope it was clear enough. If you’ve got any questions, post it in the comments or send me an email – I’ll make sure to respond.



  • Pal Mezei

    Hey. A couple of errors with the tutorial.

    – You are missing the -O3 (or the optimizations flag of your choice) from the compilation flags. It doesn’t matter at this scale, but it does with larger shaders.
    – You are not correctly querying the parameters. The AiNodeGetParams or the direct access via the macros are not designed to be used during shader evaluate. You need to use the AiShaderEvalParamXXX functions, or else the parameter mappings (i.e., connections to your shader) won’t be picked up.
    – There are (rare) cases when this code generates divides by zero.

    October 2, 2016 at 1:39 pm
  • Brian

    How is enum lightColorAttenuateParams{} associated with node_parameters{} and shader_evaluateP{}. what is the difference between “p_colorStart” and “colorStart”?

    October 2, 2016 at 5:05 pm
  • David Jensen


    maybe release the compile code as a shader for download too ?

    this is very interesting,

    thanks for posting

    October 2, 2016 at 8:42 pm
  • David Jensen
    October 9, 2016 at 10:53 am
    • David Jensen

      Thanks Zeno. I was pretty close to compiling this using the recipe you set out.

      I’m really interested in mixing subtle tones of light in a scene

      Using gamut masking and in camera light mixing could create some tonally sophisticated results ?

      I’m going to check out Zoic too.

      There are not many light shaders for Arnold online.

      Using ramp as colour, volume and attenuation is something I was trying too.

      A 3D ramp over the light frustrum could be interesting too.

      October 12, 2016 at 1:26 pm
  • Sven

    Great Zeno thanks, so little info around about that but it’s so powerful.

    December 17, 2016 at 12:07 pm
  • Thank you for taking the time out to write these tutorials. They’re quite helpful.

    June 23, 2017 at 8:27 pm

Post a Comment

Powered by