Blender Tutorial: How to Use Open Shading Language

One of the features of Blender that I think is both really cool and really frustrating is Open Shading Language. In this post, I will provide a quick overview of Open Shading Language (also called OSL) and some examples to get you started.

What is Open Shading Language?

Open Shading Language is a programmable shader language.  Open Shading Language was developed by Sony Imageworks to replace existing C++ plugins. It was released as open source, and, according to the OSL GitHub page, OSL is now the “de facto standard shading language for VFX and animated features”.

Some examples from a 2020 Open Shading Language Reel.

What Makes Open Shading Language so Great?

Open Shading Language allows for much more complicated shaders. An OSL shader calculates the way that light scatters over a surface. This is important particularly for physically based shaders.

For me, I enjoy being able to create shaders through code. In particular, I like using OSL to make procedural textures. You can create any texture that can be described by a math formula, including textures that are either not possible, or at least are very difficult to create using just nodes. In Blender, you can also integrate your OSL shader with other nodes.

Open Shading Language Render

Downside of Open Shading Language

There are a few downsides to Open Shading Language. First, in order to use Open Shading Language in Blender, you must render on CPU.  It doesn’t work on GPU. At first, I thought this was just an area of Blender not implemented yet.  However, I was surprised to learn that many studios use CPUs for rendering. Although GPUs are faster, they come with other challenges such as limited memory and cooling.

Second, Open Shading Language also lacks examples or learning resources. I could find the OSL Language Specification and a few examples using OSL, but otherwise nothing. This lack of resources makes learning OSL difficult (and frustrating).

Our First Shader

Now that we have the background on OSL, let’s write our first OSL shader.  We are going to create a simple diffuse shader.  Yes, I know, this is pointless for a real project, but it is useful for us to show the basic structure.

Here is the code:

surface diffuseShader (color inputColor = color(1), output closure color BSDF = diffuse(N)){       
    BSDF = inputColor * diffuse(N);
}

Compile and Run

To compile and run, first change the render engine to Cycles and enable Open Shading Language. 

Enable Open Shading Language

Next, go to the script and create a new script and label it diffuse.osl. A quick note, Blender has built-in templates for an OSL shader. This is useful so you don’t have to remember how to structure the shader.

Change to the render tab and add a new scripting node. Select “Internal” and in the drop down select diffuse.osl. Click the “Script Node Update” button (looks like circular arrows). Cross your fingers and if everything compiles correctly, you will see a yellow dot on the left of the node with inputColor and a green dot with BSDF on the right.

Compiled Open Shading Language Node

If it did not compile correctly, you may need to check the console to see the error. Most likely mistyped code.

What is happening here?

First, “surface diffuseShader” describes a surface shader function.  OSL includes three types of shaders: “volume”, “displacement”, and “shader” (generic shaders).  

Following the shader name, we add a list of optional parameters.  In this shader, we show both an input parameter and an output parameter. The input parameter allows us to input a color.

color inputColor = color(1)

The parameter is of type color and set to a default of white. We also have an output parameter.

output closure color BSDF = diffuse(N)

This notes that the parameter is an output. The type of output is a closure color, which we will describe below. In Blender shading nodes, an output closure connects to a BSDF node. We give this closure a default value of diffuse(N), which is a built in function for lambertian smooth surface.  N is a built in variable describing the Normal of the area to be shaded.

The default values in the parameters are required. I frequently forget these, leading to a compilation error.

What is a closure?

The concept of a closure is a little complicated. A closure is a function that is needed for a later calculation. In shaders, this later calculation occurs during the rendering process.

Essentially, a closure tells the renderer how to calculate the amount of light that is radiating from a surface and the direction of that light. This allows a cleaner workflow, because when building the shader, we don’t need to worry about the lighting in the scene. The renderer will take care of that.

Open Shading Language Workflow

OSL is designed to be very efficient. Unlike some other shaders, we are not defining exactly how the surface will look. Instead, we are telling the shader how to calculate the surface. OSL then combines our function with all other functions affecting the surface, such as the direct or reflected lights. The images below are taken from an Open Shading presentation. They show how easy it is to change the lighting source (in this case HDRI) with OSL nodes.

HDRI Lighting with Open Shading Language in Smurfs

The Need for Speed

Open Shading Language is designed to be fast. During compilation, OSL includes a step to remove unnecessary code. For example, a variable assignment that is never used would be removed. Similarly, any part of the calculation that does not affect the result will be automatically deleted, leading to the efficient operation. This can make debugging complicated. For example, although OSL includes a print function for debugging to the console, it is sometimes difficult to get the print function to work due to the optimization.

Is that all you’ve got?

You are probably thinking, great, I just did a lot of work to create a diffuse node that Cycles already has. Thankfully, the power of Open Shading Language is not limited to creating a diffuse node. 

For our second script, we are going to mix a diffuse and a refraction (glass) shader.  We are going to use a noise generator function to create a floating point number.  This floating point number determines whether to apply the refraction or diffuse. Cycles nodes could create this same result, but now we are able to combine in a single node. We also have more flexibility for how it operates. For now, we are using existing functions for diffuse and refraction. From here, we can implement our own functions depending on how we want the material to look.

surface test (output closure color BSDF=diffuse(N),float fIOR=1 /*Index of refraction*/) {      
    // Use noise generation function to generate a floating point number    
    float fNoise = (float)noise("cell", P);     

    // If the noise is greater than 0.5, shade as refraction.  If the noise is greater than or equal to 0.5, apply diffuse.    
    if(fNoise > 0.5) {
         // A closure color can be multiplied by a color
         BSDF = refraction(N,fIOR)*color(0,1,0);
    } else {
         BSDF = diffuse(N)*color(1,0,0);    
    }
}

This code generates the following result. Although not visually stunning, it is a small step toward understanding OSL.

Getting started with Open Shading Language in Blender is easy. However, OSL includes incredible power. In future posts, we will provide more examples of OSL, but to get started, check out some sample OSL scripts in Downloads.