Overview of OSL Shaders in Octane
This guide describes working with OSL shaders in Octane. OSL shaders allow for custom procedural textures, custom texture mappings and custom cameras. The plan is to provide this information in the manual, but that hasn't been finished yet.
An OSL shader can define a number of inputs, and it can contain additional information to format these inputs in user interfaces. This is a simple example of a Mandelbrot shader:
- Code: Select all
shader Mandelbrot(
output color c = 0,
int maxcount = 10
[[int min=1, int max=20000, int sliderexponent=4,
string label = "Max iterations",
string description =
"Maximal amount of iterations (z = z² + c)"]],
int outputScale = 10000
[[int min=1, int max=20000, int sliderexponent=4,
string label = "Iterations scale factor",
string description =
"Scale factor, iteration count mapped to almost white"]],
float gamma = 1.0
[[string label = "Gamma", float min=0.1, float max=10]],
point proj = point(u, v, 0)
[[string label = "Projection"]],
matrix xform = 1
[[string label = "UV transform"]]
)
{
// safety check: max count should not be arbitrarily high:
int maxcount2 = min(maxcount, 20000);
// get a point. This implements the usual UV transform + projection inputs
// of Octane texture nodes
point p = transform(1/xform, proj);
float cx = p[0];
float cy = p[1];
// Iterations
float x = cx;
float y = cy;
int count = 0;
while (x*x + y*y < 4 && count < maxcount2)
{
count += 1;
// z = z² + c
float x2 = x*x - y*y + cx;
y = 2*x*y + cy;
x = x2;
}
// output value: 1.0 if the iteration doesn't diverge, < 1.0 otherwise
if (count < maxcount)
{
float h = (float)count;
c = pow(h / outputScale, gamma);
c = min(c, .99);
}
else
{
c = 1.0;
}
}
After compiling this texture can be rendered like any other Octane texture:
The OSL standard is available from Sony Pictures Imageworks via GitHub. Octane currently is using version 1.8:
https://github.com/imageworks/OpenShadi ... gespec.pdf
WARNING:
When using OSL, you should ensure shaders never lock up or run for exceedingly long times. This may cause the system to freeze, or to reset the display driver.
WARNING:
Some operations, like out-of-bounds array access, may cause kernel crashes.
Supported features of OSL
Octane supports only a subset of the OSL standard, plus a few extensions to use features specific to Octane.
Unsupported features
- Point cloud functions
- Dictionary lookup functions
- Message passing is generally not supported, except for the built-ins listed under "Octane extensions" (see below).
- Derivatives
-
trace()
. For AO like effects, you can add acolor
input and connect the input pin to a dirt node. - Material shaders and
closure
variables -
wavelength color()
. Use_gaussian()
instead. -
struct
variable types - The global variables
Ps
anddPdt
Partially supported features
-
noise()
doesn't support 4D noise, and doesn't support the"simplex"
and"gabor"
noise types - The global variable
time
always has a value between 0 and 1, and represents the time within a subframe. -
getmessage()
andgettextureinfo()
must have string literals as attribute names.
Supported OSL shader types
Octane supports 3 OSL node types. Each has its own requirements regarding the signature of the OSL shader.
Texture
A texture shader must have one output of type color.
- Code: Select all
shader OslTexture(
output color c = 0)
{
c = color(0.7, 0.7, 0.7);
}
Projection
A projection shader must have one output of a point-like type. All global variables have the same meaning as within texture shaders. The output value specifies a texture coordinate.
- Code: Select all
shader OslProjection(
output point uvw = 0)
{
uvw = point(u, v, 0);
}
Camera
The following is a minimal implementation of a perspective camera. It takes into account the aspect ratio of the image
- Code: Select all
shader OslCamera(
output point pos = P,
output vector dir = 0,
output float tMax = 1.0/0.0)
{
float pa;
int res[2];
getattribute("camera:pixelaspect", pa);
getattribute("camera:resolution", res);
float aspect = pa * res[1] / res[0];
vector right = cross(I, N);
dir = I + right * (u - .5) + N * (v - .5) * aspect;
}
Output variables
A camera shader has 3 outputs representing a ray (note that the names are arbitrary):
point pos
= Ray position:This is often set to
P
, but it may be set to other points to implement depth of field, or a near clipping planevector dir
= Ray direction:The render engine will take care of normalizing this vector if needed.
float tMax
= Maximum ray tracing depth:Measured along the direction of
dir
. May be used to implement a far clipping plane.Set to 1.0/0.0 (infinity) to disable far clipping.
If
tMax
is 0, or if dir
has 0 length, the returned ray is considered invalid, and the renderer will not perform any path tracing for this sample.Accessing the camera position
Like other camera types, OSL camera nodes have static input pins which define the position and orientation of the camera. It is not mandatory for your camera shader to use this position, but if it does your camera automatically supports motion blur and stereo rendering.
Within camera shaders, the position and orientation of the camera is available via the standard global variables defined by OSL:
point P
: Camera positionvector I
: Camera direction (sometimes called *forward*)normal N
: vector, perpendicular to I
float u, float v
: Coordinates on the film plane, mapped to the unit square. (0, 0)
is at the bottom-left corner. These coordinates can be fetched via getattribute("hit:uv", uv)
and via the UV projection node.Alternatively, the camera position is also available via the
"camera"
coordinate space. This is usually an orthonormal coordinate space. Without transform the camera is looking along the -Z axis with the +Y axis as up-vector, i.e. the axes are defined as:+X
: Right vector+Y
: Up vector–Z
: Camera directionUsing this the last line of the example above may be written as:
- Code: Select all
dir = vector("camera", 1.0, (v - .5) * aspect, (u - .5));
Supported input types for OSL nodes
Conceptually an OSL shader corresponds to a node in our node system. Input parameters will show up as input pins on the OSL texture/projection/camera node. There is 1 or more output parameters corresponding to the output value of the node (see "Supported OSL Shader Types" below).
The types in OSL code correspond to the following Octane pin types:
color
: Texturepoint
: Projection (UV, spherical, cylindrical…)vector
/ normal
: Float (X, Y, Z)matrix
: Transformfloat
: Float (1D-value)int
: Int (1D-value) or Boolstring
: Filename, String or Enum. See "String handling" below.If an input has a constant value as default value, the corresponding input pin will have that value as default value as well.
Input meta data
OSL supports shader metadata to encode hints about the meaning of input parameters. Octane will use some of this metadata to change the appearance or ranges of input pins.
- Code: Select all
float FocalLength = 1
[[ string label = "Focal length",
float min = 0.1,
float max = 1000,
float sliderexponent = 4]],
Metadata for all inputs
string widget
: Choose which type of widget to use. The possible values depend on the input type (see below).string label
: Override the name shown in the node inspector (normally the variable name is shown)string help
: Provides a tool tip for the pin when you hover your mouse over the pinstring page
: Can be used to group pins into categoriesMetadata for int and float inputs
float/int min, max
: Specify the minimum and maximum values for a float or int input (note that there's no guarantee that the actual value for the variable in the shader is inside these bounds)float/int slidermin, slidermax
: Allows you to specify a more narrow value range for the sliders. Users may still enter values outside this range (but within min
/ max
) using right mouse button drag, and by typing a value.float/int sensitivity
: Allows you to specify the steps for a float/int type variablefloat/int sliderexponent
: sets up the skew factor for the slider. In Octane, only linear (sliderexponent
== 1) and logarithmic (sliderexponent
> 1) are supportedMetadata for int inputs
string widget = "checkBox"
: show a checkbox instead of a slider. The input value will be 0 or 1.string widget = "boolean"
: synonym for checkBoxstring widget = "mapper"
/ string options
: Use a combo box. Options are separated by pipe characters, the keys and value by a colon, e.g.: "option one:1|option two:2|option three:3"
Metadata for matrix inputs
int dim
: set to either 2 or 3, specifies if the matrix should be shown as a 2D or 3D transformMetadata for string inputs
string widget = "popup"
/ string options
: Use a combo box. Options are separated by pipe characters, e.g. "option one|option two|option three"
int editable
: If 1, this allows entering string values in a combo box which are not in the list of optionsINFO:
File name inputs are always displayed as a file input, regardless of any given metadata.
Color handling in OSL shaders
Octane is a spectral renderer, so it handles the
color
variable type in a somewhat non-standard way.Spectrum and RGB
Depending on what returned a given color value, a
color
variable may be represented internally as RGB or as a spectral color. When expressions use both RGB colors and spectral colors, the RGB colors will be converted to spectral colors.- Code: Select all
// a will be an RGB color
color a = 1;
// b will be an RGB color
color b = {1, 0.5, 0};
// c will be a spectral color
color c = _gaussian(1, 0.5, 0.01);
// adding two RGB colors results in another RGB color
color d = a + b
// adding a spectral color to another color always results in
// a spectral color
In practice, most colors will end up being represented as RGB colors. The main exceptions are blackbody and Gaussian spectra.
RGB colors support element access (using
[]
) and casting to point-like types. For spectral colors this can be done only approximately, and the result will have poor color fidelity. The compiler will emit a warning if this happens.String handling in OSL shaders
Supported operations on string values
Octane considers strings to be opaque values, and supports only a limited set of operations:
- assignment (
string b = a;
) - check for equality (
a == b
,a != b
) - using strings as arguments in functions and shaders
None of the standard string functions defined by OSL are supported.
Types of string variables
Octane roughly distinguishes between 3 types of string variables:
- file names: These are strings which are passed into image sampling functions, like
texture()
. - enum values: These are strings which are passed into functions which take a well-defined set of possible values, like
raytype()
ornoise()
. - other strings: Strings which are not used for either of the above, or which are used for multiple types of enums.
The Octane OSL compiler will use static code analysis to determine how each string variable is used. If a variable is used as both an enum value and file name a compiler error is raised.
Using strings as shader inputs
Octane will represent the three types above with different widgets:
- A file name is always shown as a file input, and any metadata is ignored.
- Enum values will by default be represented by an Enum input pin.
- Other string values will by default be represented by an Enum input pin.
String literals in
texture()
callsIf the argument of a texture call is a constant string, the compiler will generate a file name input and use the literal value as the default value. This allows loading OSL code which contains file names, and ensures any referenced files will be packed when a scene is exported to ORBX.
Octane-specific features and extensions
INFO
Some of these functions and string constants may change since 3.08 is not yet finalized.
List of intrinsic functions
Using intrinsics defined by Octane requires including
<octane-oslintrin.h>
._evaluateDelayed()
- Code: Select all
color _evaluateDelayed(
color inputVar,
float texU,
float texV)
Evaluates an input, and make
texU
and texV
available during that evaluation. inputVar
must be a color
input variable for the OSL shader. See "Using delayed input textures" below._gaussian()
- Code: Select all
color _gaussian(
float mean,
float sigma)
Return a Gaussian spectrum, normalized so that the maximal value is 1.0. The useful ranges for the inputs are:
-
mean
: 380nm – 720 nm -
sigma
: 0 – 250 nm
The returned color is represented as a spectrum.
_squareSpectrum()
- Code: Select all
color _squareSpectrum(
float begin,
float end)
Return a spectrum which is 1.0 between
begin
and end
, and 0.0 otherwise._triangularSpectrum()
- Code: Select all
color _triangularSpectrum(
float mean,
float spread)
Return a triangular spectrum which is 1.0 at
mean
, and which reaches 0 at mean
+/- spread
._spectrum()
- Code: Select all
color _spectrum(
float a,
float b,
float c,
float d)
Makes a spectral color. The 4 inputs correspond to the intensities at the wavelengths returned by
getattribute("color:wavelengths", wl)
.wavelength_color()
wavelength_color(float wavelength)
returns a spectrum consisting of a narrow band around wavelength
. Colors returned for wavelengths outside (390, 700) will be close to black.Octane 3.08 converts this call to
_triangularSpectrum(wavelength, 30.0)
.blackbody()
blackbody(kelvins)
has the same meaning as in standard OSL, but returns a spectral color._hueshift()
- Code: Select all
color _hueshift(
color c,
float shift)
Shifts the hue of the given color. This is a circular shift,
shift = 6
represents a full circle. The returned color is represented in the same way as the c
argument.For RGB colors, 1 will shift red to yellow, while 2 will shift red to green.
For spectral colors, colors will be shifted to lower or higher wavelengths. Octane samples only a limited number of wavelengths, so color fidelity will be rather low.
List of string values
Global attributes
Attribute names used by the
getattribute()
function. Must be passed in as strings literals. It must be possible to assign from the correspondingoutput type to the output variable. In this context
float[2]
may be assigned to a point
or other vector-like type.Some of these values are also available via intrinsic functions, these are mentioned in the Meaning column.
-
"camera:resolution"
->int[2]
:
Film resolution -
"camera:pixelaspect"
->float
:
Aspect ratio of a single pixel -
"camera:projection"
->string
:
Camera type -
"camera:fov"
->float
orfloat[2]
:
Horizontal field of view, or (if an array is given) horizontal and vertical field of view -
"camera:clip_near"
->float
:
Near clipping plane -
"camera:clip_far"
->float
:
Far clipping plane -
"camera:clip"
->float[2]
:
Near and far clipping plane -
"camera:distortion"
->float
:
Distortion (thin lens camera) -
"camera:dofrandom"
->float[2]
:
Camera shaders only: random numbers used for DOF sampling -
"color:wavelengths"
->float[4]
:
Wavelengths in nanometres in our current sample. Contains 4 values as of Octane 3.08. -
"hit:obj-seed"
->int
:
Object layer random seed value, provides a random but deterministic value for each object. -
"hit:instance-id"
->int
:
Instance ID provided by the user, eg. theA_INSTANCE_IDS
attribute on the scatter node.<br>Intrinsic function:int _getinstanceid();
-
"hit:uv"
->float[2]
:
UV coordinates. Thearrayindex
input value may be used to select one 1 of 3 UV channels provided by Octane.<br>Intrinsic function:point _getuv(int index);
-
"hit:w"
->float
:
W coordinate, value which increases along hair strands. -
"pixel:pos"
->int[2]
:
Pixel coordinate on the film plane
Texture attributes
Similar to
getattribute()
, but returns properties of specific image textures.-
"exists"
->int
:
0 or 1, indicates if this texture was loaded successfully. -
"resolution"
->int[2]
:
Returns the resolution of this image -
"channels"
->int
:
Image channel type
Noise types
First input argument of the
noise()
function. A pair of string indicate unsigned and signed variants.-
"uperlin"
,"perlin"
: Perlin noise -
"noise"
,"snoise"
: Default noise type (Perlin) -
"cell"
: Cell noise (constant value for each unit cell) -
"circular"
,"scircular"
: Worley noise (F1) -
"chips"
,"schips"
: Worley noise (F2 - F1) -
"voronoi"
,"svoronoi"
: Voronoi noise
Ray types
Input argument of the
raytype()
function.-
"camera"
: Camera ray -
"shadow"
: Shadow ray -
"AO"
: Ambient occlusion -
"diffuse"
: Diffuse -
"glossy"
,"reflection"
: Specular reflection -
"refraction"
: Refraction
Coordinate spaces
Used in functions like
transform()
and matrix()
-
"common"
: Standard coordinate space, (world space) -
"world"
: World space -
"object"
: Object space -
"shader"
: Synonym for object space -
"camera"
: Camera space
-
"rgb"
: sRGB color space -
"hsv"
: HSV color space, based on sRGB -
"hsl"
: HSL color space, based on sRGB -
"XYZ"
: CIE XYZ color space -
"xyY"
: CIE xyY color space
Camera projection types
Returned from
getattribute("camera:projection", type)
-
"spherical"
: Panoramic camera, spherical mode -
"cylindrical"
: Panoramic camera, cylindrical mode -
"cube"
: Panoramic camera, cube mapping mode -
"cube:+x"
: Panoramic camera, cube mapping, +X face -
"cube:-x"
: Panoramic camera, cube mapping, -X face -
"cube:+y"
: Panoramic camera, cube mapping, +Y face -
"cube:-y"
: Panoramic camera, cube mapping, -X face -
"cube:+z"
: Panoramic camera, cube mapping, +Z face -
"cube:-z"
: Panoramic camera, cube mapping, -Z face -
"perspective"
: Thin lens camera -
"orthographic"
: Thin lens camera, orthographic mode -
"baking"
: Baking camera -
"custom"
: OSL camera or Custom camera
Image texture wrapping mode
Used as optional
wrap
argument for texture()
calls.-
"black"
: Render black outside of the standard UV space. -
"white"
: Render white outside of the standard UV space. -
"clamp"
: Clamp the UV coordinates to [0 .. 1]. -
"mirror"
: Mirror image outside of the standard UV space to generate a seamless texture. -
"periodic"
: Repeat the image texture outside of the standard UV space.
Using delayed input texture evaluation
Consider a simple use case: a texture shader which randomly picks one of four input textures for each UV tile.
- Code: Select all
shader Texture(
color col1 = color(1, 0, 0),
color col2 = color(0, 1, 0),
color col3 = color(0, 0, 1),
color col4 = color(1, 1, 1),
output color c = 0)
{
int index = (int) floor(4 * noise("cell", u, v));
// random index
if (index == 0) { c = col1; }
if (index == 1) { c = col2; }
if (index == 2) { c = col3; }
if (index == 3) { c = col4; }
}
Prior to evaluating this texture, the 4 texture inputs
col1
to col4
are evaluated, but only one of them is needed to calculate the output value. This is inefficient if there’s a large amount of different tiles.Delayed evaluation
In Octane you can use the intrinsic function
_evaluateDelayed()
to get around this:- Code: Select all
#include <octane-oslintrin.h>
shader Texture(
color col1 = color(1, 0, 0),
color col2 = color(0, 1, 0),
color col3 = color(0, 0, 1),
color col4 = color(1, 1, 1),
output color c = 0)
{
int index = (int) floor(4 * noise("cell", u, v));
// random index
if (index == 0) { c = _evaluateDelayed(col1, u, v); }
if (index == 1) { c = _evaluateDelayed(col2, u, v); }
if (index == 2) { c = _evaluateDelayed(col3, u, v); }
if (index == 3) { c = _evaluateDelayed(col4, u, v); }
}
Using this intrinsic function will suppress the evaluation of those inputs before the shader runs.
Instead the inputs are evaluated on demand. Now only 1 input is evaluated.
Custom UV mapping
Apart from the input variable, there are two more arguments, which let you control the UV mapping
of the input texture. To use this UV map change the Projection input of any node connected to
this input to an OSL UV projection node.
We can modify our example so the input tiles are rotated randomly at 90° intervals.
- Code: Select all
#include <octane-oslintrin.h>
shader Texture(
color col1 = color(1, 0, 0),
color col2 = color(0, 1, 0),
color col3 = color(0, 0, 1),
color col4 = color(1, 1, 1),
output color c = 0)
{
int rnd = (int) floor(16 * noise("cell", u, v));
// random rotation
float u2 = (rnd & 1) ? u : v;
float v2 = (rnd & 1) ? v : -u;
u2 = (rnd & 2) ? -u2 : u2;
v2 = (rnd & 2) ? -v2 : v2;
// random index
int index = rnd >> 2;
if (index == 0) { c = _evaluateDelayed(col1, u2, v2); }
if (index == 1) { c = _evaluateDelayed(col2, u2, v2); }
if (index == 2) { c = _evaluateDelayed(col3, u2, v2); }
if (index == 3) { c = _evaluateDelayed(col4, u2, v2); }
}
INFO:
To use the custom UV coordinates in another OSL node connected via a delayed input, give it apoint
input and connect a OSL UV Projection node to the corresponding input pin.
Examples
In the archive below you can find 4 small set of OSL texture / camera examples all created by Roeland: