Writing a basic OSL color shader

The following is an introduction to basic OSL shader syntax using a simple color blending shader example. a more general explanation of the subject can be read here.

> It’s highly recommended to get acquainted with basic C language syntax, since it’s the basis for common shading languages like OSL, HLSL and GLSL.
> More detailed information about writing OSL shaders can be found in the osl-languagespec PDF document from ImageWorks’s OSL GitHub.

This example shader blends 2 color sources according to the surface viewing angle (aka “facing ratio” or “incident angle” or “Perpendicular-Parallel”). the user can choose a facing (“front”) color or texture, a side color or texture, and the shader’s output bell be a mix of the these 2 inputs that depends on the angle the surface is viewed at.

This is the full code of the shader, you’re also welcome to download the it here.

 #include "stdosl.h"
shader cglColorAngleBlend
[[ string help = "Blend colors by view angle" ]]
	color facing_color = color(0, 0, 0)
		[[ string help = "The color (or texture) that will appear at perpendicular view angle" ]],
	color side_color = color(1, 1, 1)
		[[ string help = "The color (or texture) that will appear at grazing view angle" ]],
	float base_blend = 0.0
		[[ string help = "The percent of side_color that is mixed with facing_color at perpendicular view angle",
		float min = 0.0, float max = 1.0]],
	float curve_exponent = 1.0
		[[ string help = "A power exponent value by which the blend value is raised to control the blend curve",
		float min = 0.001, float max = 10.0]],
	output color color_out = color(1, 1, 1))
	// calculate the linear facing ratio:
	float facing_ratio = acos(abs(dot(-I,N))) / M_PI_2;
	// calculate the curve facing ratio:
	float final_blend_ratio = pow(facing_ratio , curve_exponent);
	// blend the facing color:
	color final_facing_color = (facing_color * (1 - base_blend)) + (side_color * base_blend);
	// blend and output the final color:
	color_out = ((final_facing_color * (1 - final_blend_ratio)) + (side_color * final_blend_ratio));

The first statment:

#include "stdosl.h"

The #include statement is a standard C compiler directive to link the OSL source code library code file stdosl.h to the shader’s code, so that the OSL data types and functions in the code will be recognized.
* Some systems compile the code successfully without this statement. I’m not sure if their compiler links stdosl.h automatically or not.

The double-square bracketed statements provide both help annotations and value range limits for the shader parameters:

[[ string help = "The percent of side_color that is mixed with facing_color at perpendicular view angle", float min = 0.0, float max = 1.0]]

Note that these statements are appended to single parameters in the shader right before the comma character that ends the parameter statement.
* Not all shading systems that supprt OSL also implement the annotations in the shader’s interface generated by the host software (the shader will work, but it’s parameters wont be described and limited to the defined value range).

Removing the #include statement, annotations and comments,
We can see that the OSL shader structure is very similar to a C function:

shader <identifier> (input/output parameters) {code}

shader cglColorAngleBlend
color facing_color = color(0, 0, 0),
color side_color = color(1, 1, 1),
float base_blend = 0.0,
float curve_exponent = 1.0,
output color color_out = color(1, 1, 1)
float facing_ratio = acos(abs(dot(-I,N))) / M_PI_2;
float final_blend_ratio = pow(facing_ratio , curve_exponent);
color final_facing_color = (facing_color * (1 - base_blend)) + (side_color * base_blend);
color_out = ((final_facing_color * (1 - final_blend_ratio)) + (side_color * final_blend_ratio));

First the data type, in this case shader, followed by the shader identifier, in this case “cglColorAngleBlend”:

shader cglColorAngleBlend

After the shader’s type and identifier, a list of parameters is defined within parentheses, separated by comma’s. these parameters define the shader’s input’s, outputs, and default values. Output parameters are preceded by the output modifier.

<parameter type> <parameter identifier> = <parameter default value>,
<parameter type> <parameter identifier> = <parameter default value>,
output <parameter type> <parameter identifier> = <parameter default value>

color facing_color = color(0, 0, 0),
color side_color = color(1, 1, 1),
float base_blend = 0.0,
float curve_exponent = 1.0,
output color color_out = color(1, 1, 1)

In this case the shader has 4 user input parameters, and 1 output parameter.
2 color type parameters, “facing_color” and “side_color” for the facing and side color that will be blended together, a float* parameter “base_blend” that specifies how much of the side color will be mixed with the facing color regardless of view angle, and a second float parameter “curve_exponent” specifying a power exponent by which the blend value will be raised to create a non-linear blend curve.
The output parameter “color_out” is a color that will calculated by the shader.
* Note that even though the the output parameter will be calculated by the shader, it is required to define a default value for it for the shader to compile.

After the shader parameters, enclosed within curly braces is the actual body of the shader code, containing the instructions, each ending by a semicolon ; character:


float facing_ratio = acos(abs(dot(-I,N))) / M_PI_2;
float final_blend_ratio = pow(facing_ratio , curve_exponent);
color final_facing_color = (facing_color * (1 - base_blend)) + (side_color * base_blend);
color_out = ((final_facing_color * (1 - final_blend_ratio)) + (side_color * final_blend_ratio));

I the case of our shader the first code instruction is to define a new float internal variable named “facing_ratio”, calculate the surface/view angle and assign the resulting value to it:

float facing_ratio = acos(abs(dot(-I,N))) / M_PI_2;

The expression:

acos( abs( dot( -I, N ) ) )  / M_PI_2

calculates incident angle, i.e. angle between 2 vectors, originating at the surface shading point, one pointing towards the origin of the incoming ray, and the other the surface normal, as a factor of 0 to 1 representing 0 – 90 degrees.
These 2 vector are easily obtained through the built in OSL global variables N and I. N is the surface normal at the shading point, and I is the incoming ray vector pointing to the shading point which is inverted in this case to point backwards by  typing a minus before it: -I.
The incident angle is calculated in radians as the arc-cosine of the dot-product of N and -I and then divided by half a π to convert it to a linear factor of 0 to 1 representing 0 to 90 degrees in radians, M_PI_2 being a convenient half π constant.
* M_PI being a full π, M_2PI being 2π representing 180 degrees in radians and 360 degrees respectively (OSL provides there are more constants in this series).

The second instruction raises the facing ratio that was calculated in the previous instruction by a power value provided by the curve_exponent input parameter, to create a non linear angle/color blend in values other than 1.0.
The resulting modified blend value is stored in a new internal variable final_blend_ratio:

float final_blend_ratio = pow(facing_ratio , curve_exponent);

We could avoid setting a new variable by modifying the value of  the facing_ratio variable, and we could also combine the these 2 instruction into 1 bigger expression like this:

pow( acos( abs( dot( -I, N ) ) ) / M_PI_2 , curve_exponent )

But I decided to keep it separated into 2 variable and 2 instructions for clarity.
* try modifying the code as an exercise

The third instruction modifies the input color facing_color by premixing it with the input side_color according to the percent give by the input parameter base_blend and assigns the resulting color to a new internal variable named final_facing_color:

color final_facing_color = (facing_color * (1 - base_blend)) + (side_color * base_blend);

The expression:

( facing_color * ( 1 – base_blend ) ) + ( side_color * base_blend )

Calculates a linear combination** (linear interpolation) between the 2 input colors using the base_blend as a 0 – 1 factor between them.
* Note that OSL allows to define arithmatic operations freely between colors and floats.

The forth and final instruction creates the final mix between the modified facing color stored in final_facing_color variable and the side color given by the input color parameter side_color, by again, calculating a linear combination between the 2 colors, this time using final_blend_ratio variable value we calculated previously as the combination factor, and very importantly, finally, assigning the mixed result to the shader output parameter color_out so it will be the final output of the shader:

color_out = ((final_facing_color * (1 - final_blend_ratio)) + (side_color * final_blend_ratio));

This screen capture shows this shader at work in Blender and Cycles, connected to a Principled BSDF shader as it’s base color source:
Annotation 2020-06-17 235933

Thats it! 🙂
Hope you find this article informative and useful.


* A “float” data type is simply the the computer-science geeky way of saying “accurate non-integer number”. when we have to store numbers that can describe geometry and color, we need a data type that isn’t limited to integers so for that purpose we use float values. there’s actually a lot more to the float formal definition in computer science, but for our purpose here this will suffice.

** A Linear Combination, or Linear interpolation (lerp) is one of the most useful numerical operations in 3D geometry and color processing (vector math):
A * ( 1 – t ) +  B * t 
A and B being your source and target locations or colors or any other value you need to interpolate and t being the blending factor from 0 – 1.

Related posts:

  1. What are OSL shaders?
  2. Using OSL Shaders in Maya and Arnold
  3. Using OSL Shaders in Blender and Cycles
  4. Using OSL Shaders in 3ds max and V-Ray

Using Color Lookup Tables (CLUTs)

What is this all about?

Color Lookup Tables – CLUTs (also “Color LUTs“) are a method of storing and reusing complex linear color transformations*.
CLUTs have the advantages of being supported by many video and image processing software packages, and also the ability to be calculated in real-time on the GPU, costing very little computing resources.
* More simple, daily use terms can be: “color styles”, or “color corrections”

CLUTs are used in the movie production industry to perform color conversions of images acquired from different sources for monitoring and editing purposes, and also for testing, applying and sharing different creative color styles across different departments, and stages of the production.
Examples of common CLUT file formats are *.3DL and *.CUBE

Why is this called a “3D” or “Cube” Lookup Table?

The reason CLUTs are referred to as “3D” color lookup tables or “Cube LUT” is that they store the effect of color operations as linear transformations of a 3D cubic space.
To understand this we have to imagine RGB color encoding as a 3D space with the R, G and B values of each color being coordinates in this cubic color space.
This means that the color correction operations we perform to create a color style, like adding contrast, saturation, warming the hues etc. are all defined as a function that for every color coordinate in the RGB color cube space defines the new coordinate where the corrected or stylized color is found.
The term Lookup table means that the new color values don’t have to be calculated every time because they have been pre-calculated and stored in a table of values.
3D CLUTs are often processed and stored as 3D Cubic textures like this example generated with Blackmagic Fusion of a 32 x 32 x 32 value CLUT.
Imagine the little 32 x 32 square patches all stacked one upon the other, that would create a 32 x 32 x 32 RGB color cube, with which color transformations can be stored by simply applying them on this texture:Cube0000

Working with CLUTs:

In this post we’ll go trough the process of creating and using a CLUT in some popular creative software packages.
* Note that there are many other software packages that support creation and usage of CLUTs, the process should be similar.

Steps shown in the following software:
Adobe Photoshop 2020
Adobe After Effects 2020
Adobe Premiere 2020
Blackmagic Design Fusion 9

In this example our source image with which the CLUT will be designed is an interior scene modeled with Blender 2.82 and rendered with Cycles Render Engine with “Filmic” tone-mapping applied, saved as a PNG file.
* I usually save Linear unclamped 32bit float EXR files as the raw output from render engines, because this is the format that provides the most freedom to manipulate and process rendered images and animation, but from my experience CLUTS don’t work well on linear unclamped color, for that reason I usually apply them at a later stage of the image development (usually after applying tone-mapping to the image).
This is why I saved the file directly as a tone-mapped PNG for this example.


Creating a CLUT in Adobe Photoshop:

For this example, a greenish-contrasty-desaturated color style is created in Photoshop by applying color adjustment layers to the image.
In this case Color Balance, Vibrance, and Curves.
* You can use different numbers and combinations of color adjustments


The new Color Style is now exported to CLUT files:


In the Export Color Lookup Tables dialog allows naming the CLUT, adding a description, setting a quality for the color transform it will define, and selecting the wanted CLUT file formats that will be written.
After clicking OK the CLUT files will be saved in a chosen location.C1.Photoshop_Export_CLUTS

Saving the CLUT in the Presets\3DLUTs folder (found in the Adobe Photoshop installation folder) will allow reusing the CLUT as a preset look available by drop-down selection without having to locate the file each time.

Applying a CLUT in Adobe Photoshop:

With the image later selected, add a Color Lookup adjustment layer:


In the Color Lookup adjustment properties, open the 3DLUT File drop-down, choose Load 3D LUT, and locate the CLUT file you saved:E.Photoshop_Lut_Load

The original image now has the same color style we created earlier, but this time it’s applied by only a single Color Lookup adjustment layer:


An example of the same CLUT applied to a different image:


Applying a CLUT in Adobe After Effects:

Add a Util > Apply Color LUT effect to a layer,
In the Effect Controls window, click Choose LUT and locate the wanted CLUT file:


Applying a CLUT in Adobe Premiere:

  1. Select the image/video clip in the timeline.
  2. Switch to the Color UI tab to get access to the Lumetri Color controls on the right.
  3. In the Creative section of the Lumetri Color controls, open the Look dropdown, choose Browse and locate the wanted CLUT file.



Applying a CLUT in Blackmagic Design Fusion:

Add a LUT >File LUT node to the image source.
In the File LUT properties, click the browse button and locate the wanted CLUT file:


Creating a CLUT in Blackmagic Design Fusion:

* See the numbered nodes in the flow graph below

  1. Source image/video on which the CLUT is designed.
  2. A LUT Cube Creator node, generating default neutral 3D CLUT data in the form of a Color Cube map.
  3. The nodes creating the actual color style (in this case a Color Corrector and Color Curve nodes) are operating on the LUT Cube Creator node’s output.
  4. A LUT Cube Apply node is applying the stylized CLUT data to the image/video for previewing purpose (displayed on the right viewer)
  5. A LUT Cube Analyzer node generates CLUT data from the styled LUT Cube Creator data, and allows saving it to disk as a CLUT file.
    * Choose a location and click Write File to save the CLUT file.


3ds max – Using a GradientRamp procedural texture in Mapped mode

3ds max 2020

Using the GradientRamp procedural texture map in Mapped mode can very useful for creating procedural material effects.
The Idea is that the lightness value from a different map will determine what part of the GradientRamp is sampled.

In this example the GradientRamp uses values produced by a procedural Falloff map set to Perpendicular-Parallel mode, as its coordinates source, to create richly colored metal that changes its Hue depending on View/Incident angle:

Annotation 2020-05-02 184426

In this example the GradientRamp uses values produced by a procedural Noise map as its coordinates source to create an irregular color effect:

Annotation 2020-05-02 184849

The examples here were rendered using V-Ray Next for 3ds max, but this technique could also be used with other rendering engines.



  1. 3ds max Island/seashore tip

UE4 – RemapValueRange Node

Unreal Engine 4.24

Having to remap a value range is very common in designing shaders, whether it’s to perform a traditional “levels” operation on a texture, or use just a specific range of values in a float input.
The RemapValueRange node let’s us do just that. this node has 5 inputs:

  1. Input: The input value
  2. Input Low: Input mapping range minimum
  3. Input High: Input mapping range maximum
  4. Target Low: Output range minimum
  5. Target Height: Output range maximum

Mono Noise examples:

  1. The original noise pattern:0
  2. Mapping input 0 -> 1 to output 0 -> 1 obviously has no effect:1
  3. Mapping input 0 -> 1 to output 0.3 -> 0.7 reduces the pattern contrast:2
  4. Mapping input 0.3 -> 0.7 to output 0 -> 1 increases the pattern contrast:3
  5. Mapping input 0 -> 1 to output 1 -> 0 inverts the pattern:4

In this example RemapValueRange nodes are applied to a texture’s individual color channels to increase contrast (Levels):
> Note that this operation can be performed using a simpler graph by using float3d adding and multiplication operations on the texture color (this will be a subject for a different post)
> Note that a different remapping operation can be performed on each color channel of a texture to adjust its color balance.

Before the value remapping:5

After the value remapping tha value in each channel 0.1 -> 0.9 to 0.0 to 1.0:6

The same operation performed by multiplication by 1.2 and subtracting 0.1:7.jpg

Fusion – Create a Solid color or Gradient element

Fusion 9

When you need a solid color source or background for your composite,
The Background node is just that,
It provides either a flat color plate or 4 types of gradients (color ramps).
Set the plate fill type in the Color tab of the node’s settings, and the format (size) in the Image tab.

Add Tool > Creator > Background




Clamp Colors in Fusion

Fusion 9

To Clamp (limit / clip) the color values in a Fusion flow:

  1. Add a Color > Brightness / Contrast node to the composition and connect it to the desired output.
  2. and activate the Clip Black and Clip White options at the bottom of the Brightness / Contrast node parameters.
  3. Set the Low and High parameters to set the wanted color limits.]


Clamp colors in Photoshop 32 bit float color mode