A rainbow OSL shader for Blender Cycles

Here I present an OSL shader to render a simple, single rainbow in a scene.

The example image was generated using one of Bob Groothuis excellent HDRI maps from his Dutch Skies collection.
The aim here is to produce a believable but not necessarily completely realistic rendition of a rainbow. The theory behind rainbows is quite clear but we don't want to go as far as approximating Mie theory as this makes for very complex shaders indeed.
So what do we consider believable? The color progression and the angle of the arc should be correct of course but also we would like to be able to influence the intensity. Rainbows are most often seen against the backdrop of rain showers and the density of the distribution of the raindrops is not uniform and this has an effect on the visibility of the rainbow.

OSL limitations

In real life rainbows are seen when there is a very bright light behind you, most often the sun. The center of the rainbow arc is positioned in the direction of your shadow. Now we want to project the rainbow on a plane we can use a trick to find out the camera vector (by transforming the location of the object from world to camera space, see line 16 below) but there is currently no way to find out the location of objects other than the one being shaded. That means that to create a believable scene we must take care ourselves to position the plane with the rainbow opposite the sun, relative to the camera!
Another thing I found that although OSL has a wavelenght_oolor() function it was very difficult to create the washed out colors that we associate with rainbows. So instead of trying to mix colors and account for exact dispersion and stuff like the size of the disk of the sun, I opted for just calculating the angle an plugging the result into a color ramp.

#define INNER 0.766   // cos(40)
#define OUTER 0.743   // cos(42)
#define SPREAD (INNER - OUTER)

shader rainbow(
 output float T = 0 
){
    if( raytype("camera") ){
        point Pos = P;
     // vector from point being shaded to camera
        point cam = normalize(transform("common","camera",Pos));

        // vector from object center to camera
        point obj;
        getattribute("object:location",obj);
        obj=transform("world","camera",obj);
    
        obj=normalize(obj);
        
        float theta = dot(obj,cam);
     if (theta > OUTER && theta < INNER){
            theta = (theta-OUTER)/SPREAD ;
            T = theta;
    }
    }
}
So basically all that this shader does, is calculating the cosine of the angle between the vector pointing from the camera to the point being shaded and the vector pointing from the camera to the center of the object being shaded (line 20).

Example node setup


The trick used to create the example image is to multiply a suitable density (here a simple vertical gradient mapped to an appropriate position, shown on the left) to the the output color that we extract from a color ramp and input that into a diffuse shader that is added (not mixed) to a completely transparent shader. Note that the color ramp must start with an all black node (because T is zero outside the rainbow). The plane to which this material is added may be positioned at any distance from the camera so it is possible for example to position buildings that partially obscure the rainbow.

2 comments:

  1. yes, I would also need the position of another object for a script I'm implementing. That would be nice to have.

    ReplyDelete
  2. Rain dispersed (water drops) causes rainbow.

    Water at ~15ºC - ior 1.333 and abbe number 56

    In the real life, any specular (= transparent) material must have dispersion. ;)

    ReplyDelete