OSL shaders: part 1
Writing a shader
1. The start
The code in an OSL shader starts with a keyword indicating the shader type. The OSL specification lists five shader types:
- surface
- light
- displacement
- volume
- shader
Which one to use? Well, the OSL spec then goes on to say that there's no need for a separate 'light' shader, as the 'surface' shader can code for light-emitting surfaces. So we can forget that one. I've never come across a 'displacement' shader, and in any case Redshift doesn't need it - it has the Displacement node, and the output of an OSL node can be piped into that. Interestingly, the Displacement node output should be connected to the 'Displacement' input of an Output node, which implies that it outputs a closure (see below), not a value you can read or manipulate further.
Nor have I ever found a 'volume' shader, and the Maxon help files state "OSL does not currently work with volume shading". This means that OSL volume shaders are not usable in Redshift, and a standard OSL shader cannot be used at all in rendering a volume, even as a simple colour input to a port in a Volume shader.
That leaves 'surface' and 'shader'. If you look at shaders other people have written, you may see 'shader' and 'surface' used interchangeably. They are in fact interchangeable...almost.
'Surface' is intended to indicate that the shader describes an object's surface and how it interacts with light. In theory therefore it should output a closure (again, more on that later), but it doesn't have to and you can find many ordinary pattern shaders of type 'surface' which just output a colour or a float value. The term 'shader' on the other hand should indicate something used for generic code whose output is used by another node further down the line - but some of type 'shader' output a closure, whose output cannot be used by any other nodes.
The vast majority, if not all, OSL shaders you can find will be generating surface patterns of one kind or another. Therefore, I'd suggest using 'shader' as the shader type unless you discover some feature that requires the shader to be the 'surface' type.
2. When a shader is not just a shader...
Cinema 4D users will probably think that a 'shader' is something found in a material which changes the surface of an object - colour, bump, displacement, alpha, whatever. But that's true only in this particular context. More generally, a shader is just a bit of code which does something; its output can be used in any way that makes some kind of sense, and that doesn't necessarily mean altering a surface characteristic.
For example, consider this:
shader sqrt_shader(
float inValue = 2.0,
output float Fac = 1.0
)
{
float res;
res = sqrt(inValue);
Fac = res - floor(res);
}
All the code does is take the input number, finds its square root, and outputs the fractional part of the result. That isn't useful in changing a surface directly, but officially it's still a shader. Now, Redshift is smart enough that if you connect the 'Fac' output of this node to the 'Surface' port of an Output node, it will use that value to create a colour, which in this case will always be a greyscale value. This render shows what you might get if you did that:
You note there's no shading on the object being rendered, it's just a plain colour. To get the sort of shading we would normally expect, it would be necessary to send the OSL output to some other node, such as a Standard Material node. Then the same OSL code will result in this:
More usefully, the output could be used to select a colour from a Ramp node, for example. But the standard material node is still doing most of the work. The shader is just a black box which takes a value of some kind and outputs another value.
3. ...is when it's a closure
An ordinary shader of the type discussed above just outputs a value with no shading, and that value can be read, changed and used to do something else. But OSL can also output a fully shaded result, which is called a closure. The advantage there is that you can implement your own shading model, but the downside is that you can't use its output as a value to be read or changed in some other node. All that a shader needs to be a closure is to define its output as a closure. Take a look at this code:
shader closure_test
(
color inColor = color(1, 0.5, 0),
output closure color outColor = diffuse(N)
)
{
outColor = inColor * diffuse(N);
}
This is the simplest possible closure: it just outputs a colour but as a closure, not a standard shader. The rendered result is this:
This didn't require any other node, simply the OSL node and the Output node, but it's still a shaded result. In fact, it's a surface which is shaded by a Lambertian reflectance model. However, if you were to connect the output of this OSL node to the 'Base Color' input of a Standard Material node, nothing would happen; the input would be completely ignored by the material node, because the OSL output is a closure and can't be read or changed.
I initially found the difference between an ordinary shader and a closure to be very confusing. OSL has a number of different closures but many render engines (Redshift included) only implement a small subset. Sadly, the only ones I could find which work are the deprecated surface closures diffuse(), oren_nayar(), and microfacet().
Most of the time you will be writing standard shaders, not closures. But be aware that sometimes your OSL code may be outputting a closure, not a standard shader, even if you didn't tell it to. A typical example would be light-emitting surfaces, which I'll cover in another part.
4. The end
Code execution starts in the shader() function after the parameter declarations. It completes when it reaches the end of the function, by which time all your output parameters should have been assigned values. If they haven't, then they will have the default values given to them in the shader's parameter list. Note that there is no 'return' statement required to complete this function. However, you can use 'return' to exit prematurely. Look at this code:
shader OSLShader(
color inColor=color(0, 0.5, 1), // light blue
output color outColor=0
)
{
outColor = inColor;
// return;
outColor = color(1, 0.5, 0); // orange
}
The 'return' statement is commented out, so the output colour is first assigned the input colour (blue by default), then orange. The rendered result is orange. What if the 'return' statement is uncommented? The function exits before the output colour is assigned orange, so the rendered result is the default blue.
Hopefully this has made clearer the distinction between shader types and closures, and has shown how the code is executed. In part 2, we'll look at some of the issues and difficulties faced when writing OSL shaders, some of which are specific to Redshift.
Part 2: Errors, problems, snags and other issues -->
Page last updated July 11th 2025