OSL shaders: part 3

Converting a Renderman shader for use in Redshift

This is a kind of experimental approach to getting a shader working in Redshift. This shader has a long, long history. It originally appeared in a book* (see reference at foot of this page) and was written for use in Renderman. Then I think it must have been converted to OSL for use in Blender Cycles, before it was converted to Cycles 4D and now - if possible - to Redshift in C4D.

The shader is called 'FakeReflect' and that reflects (sorry) its function: it produces a surface showing a fake metallic reflection of ground and sky. It does this by selecting from an array of colours, brown for the ground and light blue deepening to black for the sky. I chose it for this page because:

  • we know what it is supposed to do
  • it shows some of the problems encountered when converting a shader between render engines
  • and it also shows how the original shader can be enhanced by adding features

For reference, here is what it does in Cycles 4D:

FakeReflect Cycles 4D

And here is a link to the .osl file which produces the above render in Cycles 4D (please remember that copyright remains with the original authors*).

Initial use in Redshift

Loading the unedited Cycles 4D script into a Redshift OSL node, it will compile but with a warning, which looks like this:

D:\\Documents\\C4D Plugins\\OSL_files\\RS_Working_OSL\\FakeReflect\\FakeReflect.osl:38: warning: Ambiguous call to 'normalize (point)'
Chosen function is:
C:\\ProgramData\\Redshift\\osl\\include\\stdosl.h:203 vector normalize (vector)
Other candidates are:
C:\\ProgramData\\Redshift\\osl\\include\\stdosl.h:202 normal normalize (normal)
Compiled successfully

Before we can fix the warning, this message gives us another slight problem because the actual error occurs on line 43 in the text editor, not line 38. I can't figure out how line 38 is arrived at, so sometimes it takes a bit of thinking to resolve this. In this file there are two calls to the 'normalize()' function, and the error is this one:

Rworld = normalize(transform(sspace, R));

This happens because 'Rworld' and 'R' are declared as point variables, but OSL only has two versions of normalize() - one which takes a normal and the other takes a vector. The shader still works because point, vector, normal and color are all variables with three components, but if we want to lose the warning, all we have to do is declare Rworld and R as vectors, and then the warning goes away. In any case it makes sense to do this because R and Rworld are in fact vectors, not points.

Redundant code

If you look at the .osl file, you can see that there is a block of commented-out code starting on line 44. This means that in this version, the input 'Axis' parameter has no effect: it always uses the Y axis to calculate the 'altitude' variable, as you can see from line 57:

altitude = 0.5 * Rworld[1] + 0.5;

If you remove line 57 and uncomment the commented block, you will get different results depending on which component of Rworld is used to calculate the 'altitude' variable. In fact, the original Renderman code only used 'Rworld[1]', but we can uncomment the code and make the 'Axis' variable have an effect.

There are three more commented-out lines from 61-63 in the original file, but these can't be uncommented. These are closures and although they are available in OSL, if you try to use them either the code won't compile or the Redshift core will crash. So they could just be deleted.

Now we can see what effect the .osl file has in Redshift, having corrected the warning and changed/removed the commented sections. If we connect the 'Color' port to the 'Base Color' port of a standard material node, we see this:

FakeReflect Redshift

This doesn't look anything like the Cycles version, but we'll come back to it shortly.

'Altitude' output parameter

This is a value between zero and one. We can use it, for example, as a feed into a Ramp node. Using a simple red to blue gradient, to give a node tree like this:

We get a render like this, using the Y axis in the 'Axis' parameter and world space:

That's quite nice, but we still aren't getting the intended result in the 'Color' output. Why not?

'Color' output parameter

To answer that, we need to look at how this shader actually works. What it does is first calculate the reflection direction of the point being shaded depending on the vector from the camera to that point. That is the 'R' variable, which is transformed into either world or object space then normalized. Each axis component can range from -1 to 1, so this is converted into the range zero to 1 - giving the variable named 'altitude'.

Having done that, the spline() function takes an array of color values (the 'yarr' array) and interpolates between them using the 'altitude' variable. The result is a colour, which depends on the type of spline used - which in this case is 'catmull-rom' - and the value of 'altitude'. What we should see therefore is a colour which is somewhere in the range of colours specified in the 'yarr' array, and in Cycles 4D, that is exactly what we get. But in Redshift we don't. It's always some shade of red.

Looking at the 'Altitude' parameter when connected to the Ramp node, it's clear that this value is being calculated correctly - we get a range of colour from red to blue, as expected. After trying many different ways to get this to work, and also in a completely different shader from another source which gave the same result, I can only conclude that the spline() function does not work as it should in Redshift's OSL implementation, which is disappointing. Fortunately the 'Altitude' output is fine and works pretty well with a Ramp node, with the advantage that we can use whatever colours we like, not just the brown/blue/black colours in the array. That means that we could remove the 'Color' output since it doesn't work correctly, but we can leave it in anyway.

Code improvement

The code contains this line:

vector Nf = (dot(1, Ng) > 0) ? -N : N;

What this does is return a vector which is either the surface normal or its inverse, depending on whether the true surface normal (the 'Ng' inbuilt variable) points in the same general direction as the incident vector or not.

But the original Renderman code had this instead:

point Nf = normalize(faceforward(N, I));

I'm not sure why this was changed, since OSL also has a faceforward() function. Possibly this wasn't implemented for Cycles. In any event, we can change it back to the original code, which does the same thing, and make the code look neater and more readable.

Improving the interface

The 'Axis' and 'Space' inputs are numeric values, but 'Axis' can only be one of three values and 'Space' one of just two. It would be much easier for the user, and would look nicer, if these could be drop-down menus rather than numbers. OSL has a 'widget' metadata to do this and its 'mapper' type is an enumerated list, which is ideal here. So instead of this:

int Axis = 1,

We could use this instead:

int Axis = 1 [[ string widget = "mapper", string options = "X:0" "|Y:1" "|Z:2"]],

This gives the user a menu to select from, which avoids confusion and makes the shader easier to use. The actual value returned from the selected option is the number following the colon in each case - that is, 0, 1, or 2. The same thing could be done for the 'Space' setting.

Add altitude scale input

There is one final thing we could do. The 'altitude' variable parameter is calculated from the 'Rworld' vector, but we could tweak the value of 'altitude' after it is calculated. To do that we could add an input variable and multiply the calculated 'altitude' with that. The code would look like this:

float AltScale = 1.0 [[ string label = "Altitude Scale", float min = 0, float max = 10 ]],

The final parameter interface now looks like this:

And that's about it. Here are some examples of what it does, using the Altitude output as an input to a Ramp node with different gradients. The gradients are all from the presets supplied with Cinema 4D. Click the thumbnail images to see a larger version:

  • Black-Violet-Orange
  • Flame 3
  • Flame 6
  • Heat 1
  • Pattern 6
  • Red Fever
  • Percules
  • Sky 1
  • Violet-Aqua-Yellow

And here is a link to the converted shader. Feel free to modify it as desired, and if you find a solution to why the 'Color' output isn't as expected, please let me know!

Reference

Ebert DS, Musgrave FK, Peachey D, Perlin K, Worley S. Texturing and Modeling: A Procedural Approach. 3rd edition, Morgan Kaufmann, San Francisco 2003.

<-- Part 2: Errors, problems, snags and other issues

Part 4: Light-emitting shaders-->

Page last updated July 17th 2025