5. Data types#
OSL provides several built-in simple data types for performing computations inside your shader:
Type |
Explanation |
---|---|
|
Integer data |
|
Scalar floating-point data (numbers) |
|
Three-dimensional positions, directions, and face orientations |
|
Spectral reflectivities and light energy values |
|
\(4 \times 4\) transformation matrices |
|
Character strings (such as filenames) |
|
Indicates functions that do not return a value |
In addition, you may create arrays and structures (much like C), and OSL has a new type of data structure called a closure.
The remainder of this chapter will describe the simple and aggregate data types available in OSL.
5.1. int
#
The basic type for discrete numeric values is int
. The size of
the int
type is renderer-dependent, but is guaranteed to be at
least 32 bits.
Integer constants are constructed the same way as in C. The following
are examples of int
constants: 1
, -2
, etc. Integer
constants may be specified as hexadecimal, for example: 0x01cf
.
Unlike C, no unsigned, bool, char, short, or long types are supplied. This is to simplify the process of writing shaders (as well as implementing shading systems).
The following operators may be used with int
values (in order of
decreasing precedence, with each box holding operators of the same
precedence):
Operation |
Result |
Explanation |
---|---|---|
int |
|
post-increment by 1 |
int |
|
post-decrement by 1 |
|
|
pre-increment by 1 |
|
|
pre-decrement by 1 |
|
|
unary negation |
|
|
bitwise complement (1 and 0 bits flipped) |
|
|
boolean `not’ (1 if operand is zero, otherwise 0) |
int |
|
multiplication |
int |
|
division |
int |
|
modulus |
int |
|
addition |
int |
|
subtraction |
int |
|
shift left |
int |
|
shift right |
int |
|
1 if the first value is less than the second, else 0 |
int |
|
1 if the first value is less or equal to the second, else 0 |
int |
|
1 if the first value is greater than the second, else 0 |
int |
|
1 if the first value is greater than or equal to the second, else 0 |
int |
|
1 if the two values are equal, else 0 |
int |
|
1 if the two values are different, else 0 |
int |
|
bitwise and |
int |
|
bitwise exclusive or |
int |
|
bitwise or |
int |
|
boolean and (1 if both operands are nonzero, otherwise 0) |
int |
|
boolean or (1 if either operand is nonzero, otherwise 0) |
Note that the not
, and
, and or
keywords are synonyms for !
, &&
, and
||
, respectively.
5.2. float
#
The basic type for scalar floating-point numeric values is float
. The
size of the float
type is renderer-dependent, but is guaranteed to
be at least IEEE 32-bit float (the standard C float
data type).
Individual renderer implementations may choose to implement float
with
even more precision (such as using the C double
as the underlying
representation).
Floating-point constants are constructed the same way as in C. The following
are examples of float
constants: 1.0
, 2.48
, -4.3e2
.
An int
may be used in place of a float
when used with any valid
float
operator. In such cases, the int
will be promoted to a
float
and the resulting expression will be float
. An int
may
also be passed to a function that expects a float
parameters, with the
int
automatically promoted to float
.
The following operators may be used with float
values (in order of
decreasing precedence, with each box holding operators of the same
precedence):
Operation |
Result |
Explanation |
---|---|---|
float |
|
post-increment by 1 |
float |
|
post-decrement by 1 |
|
|
pre-increment by 1 |
|
|
pre-decrement by 1 |
|
|
unary negation |
float |
|
multiplication |
float |
|
division |
float |
|
addition |
float |
|
subtraction |
float |
|
1 if the first value is less than the second, else 0 |
float |
|
1 if the first value is less or equal to the second, else 0 |
float |
|
1 if the first value is greater than the second, else 0 |
float |
|
1 if the first value is greater than or equal to the second, else 0 |
float |
|
1 if the two values are equal, else 0 |
float |
|
1 if the two values are different, else 0 |
5.3. color
#
The color
type is used to represent 3-component (RGB) spectral
reflectivities and light energies. You can assemble a
color out of three floats, either representing an RGB triple or some
other color space known to the renderer, as well as from a single
float (replicated for all three channels). Following are some examples:
color (0, 0, 0) // black
color ("rgb", .75, .5, .5) // pinkish
color ("hsv", .2, .5, .63) // specify in "hsv" space
color (0.5) // same as color (0.5, 0.5, 0.5)
All these expressions above return colors in “rgb” space. Even the third example returns a color in “rgb” space — specifically, the RGB value of the color that is equivalent to hue 0.2, saturation 0.5, and value 0.63. In other words, when assembling a color from components given relative to a specific color space in this manner, there is an implied transformation to “rgb” space. The following table lists the built-in color spaces.
|
The coordinate system that all colors start out in, and in which the renderer expects to find colors that are set by your shader. |
|
hue, saturation, and value. |
|
hue, saturation, and lightness. |
|
the color space used for the NTSC television standard. |
|
CIE XYZ coordinates. |
|
CIE xyY coordinates. |
Colors may be assigned another color or a float
value (which sets
all three components to the value). For example:
color C;
C = color (0, 0.3, 0.3);
C = 0.5; // same as C = color (0.5, 0.5, 0.5)
Colors can have their individual components examined and set using the
[]
array access notation. For example:
color C;
float g = C[1]; // get the green component
C[0] = 0.5; // set the red component
Components 0, 1, and 2 are red, green, and blue, respectively. It is an error to access a color component with an index outside the \([0...2]\) range.
Color variables may also have their components referenced using “named
components” that look like accessing structure fields named r
, g
, and b
,
as synonyms for [0]
, [1]
, and [2]
, respectively:
float green = C.g; // get the green component
C.r = 0.5; // set the red component
The following operators may be used with color
values (in order of
decreasing precedence, with each box holding operators of the same
precedence):
Operation |
Result |
Explanation |
---|---|---|
color |
|
component access |
|
|
unary negation |
color |
|
component-wise multiplication |
color |
|
scaling |
float |
|
scaling |
color |
|
component-wise division |
color |
|
scaling |
float |
|
scaling |
color |
|
component-wise addition |
color |
|
component-wise subtraction |
color |
|
1 if the two values are equal, else 0 |
color |
|
1 if the two values are different, else 0 |
All of the binary operators may combine a scalar value (float
or
int
) with a color
, treating the scalar if it were a color
with
three identical components.
5.4. Point-like types: point
, vector
, normal
#
Points, vectors, and normals are similar data types with identical structures but subtly different semantics. We will frequently refer to them collectively as the “point-like” data types when making statements that apply to all three types.
A point
is a position in 3D space. A vector
has a length and
direction, but does not exist in a particular location. A normal
is a
special type of vector that is perpendicular to a surface, and
thus describes the surface’s orientation. Such a perpendicular vector
uses different transformation rules than ordinary vectors, as we will
describe below.
All of these point-like types are internally represented by three floating-point numbers that uniquely describe a position or direction relative to the three axes of some coordinate system.
All points, vectors, and normals are described relative to some
coordinate system. All data provided to a shader (surface information,
graphics state, parameters, and vertex data) are relative to one
particular coordinate system that we call the "common"
coordinate
system. The "common"
coordinate system is one that is convenient
for the renderer’s shading calculations.
You can “assemble” a point-like type out of three floats using a constructor:
point (0, 2.3, 1)
vector (a, b, c)
normal (0, 0, 1)
These expressions are interpreted as a point, vector, and normal whose three components are the floats given, relative to “common” space .
As with colors, you may also specify the coordinates relative to some other coordinate system:
Q = point ("object", 0, 0, 0);
This example assigns to Q
the point at the origin of “object” space.
However, this statement does not set the components of Q
to (0,0,0)!
Rather, Q
will contain the “common” space coordinates of the point that is
at the same location as the origin of “object” space. In other words, the
point constructor that specifies a space name implicitly specifies a
transformation to “common” space. This type of constructor also can be used
for vectors and normals.
The choice of “common” space is renderer-dependent, though will usually be equivalent to either “camera” space or “world” space.
Some computations may be easier in a coordinate system other than
“common” space. For example, it is much more convenient to apply a
“solid texture” to a moving object in its “object” space than in
“common” space. For these reasons, OSL provides a built-in
transform()
function that
allows you to transform points, vectors, and normals
among different coordinate systems (see Section Geometric functions). Note,
however, that OSL does not keep track of which point variables are
in which coordinate systems. It is the responsibility of the shader
programmer to keep track of this and ensure that, for example, lighting
computations are performed using quantities in “common” space.
Several coordinate systems are predefined by name, listed in the following table. Additionally, a renderer will probably allow for additional coordinate systems to be named in the scene description, and these names may also be referenced inside your shader to designate transformations.
"common"
The coordinate system that all spatial values start out in and the one in which all lighting calculations are carried out. Note that the choice of
"common"
space may be different on each renderer."object"
The local coordinate system of the graphics primitive (sphere, patch, etc.) that we are shading.
"shader"
The local coordinate system active at the time that the shader was instanced.
"world"
The world coordinate system designated in the scene.
"camera"
The coordinate system with its origin at the center of the camera lens, \(x\)-axis pointing right, \(y\)-axis pointing up, and \(z\)-axis pointing into the screen.
"screen"
The coordinate system of the camera’s image plane (after perspective transformation, if any). Coordinate (0,0) of
"screen"
space is looking along the \(z\)-axis of “camera” space."raster"
2D pixel coordinates, with (0,0) as the upper-left corner of the image and (xres, yres) as the lower-right corner.
"NDC"
2D Normalized Device Coordinates — like raster space, but normalized so that \(x\) and \(y\) both run from 0 to 1 across the whole image, with (0,0) being at the upper left of the image, and (1,1) being at the lower right.
Point types can have their individual components examined and set using
the []
array access notation. For example:
point P;
float y = P[1]; // get the y component
P[0] = 0.5; // set the x component
Components 0, 1, and 2 are \(x\), \(y\), and \(z\), respectively. It is an error to access a point component with an index outside the \([0...2]\) range.
Point-like variables may also have their components referenced using “named
components” that look like accessing structure fields named x
, y
, and z
,
as synonyms for [0]
, [1]
, and [2]
, respectively:
float yval = P.y; // get the [1] or y component
P.x = 0.5; // set the [0] or x component
The following operators may be used with point-like values (in order of decreasing precedence, with each box holding operators of the same precedence):
Operation |
Result |
Explanation |
---|---|---|
ptype |
|
component access |
|
|
component-wise unary negation |
ptype |
ptype |
component-wise multiplication |
|
ptype |
scaling of all components |
ptype |
ptype |
scaling of all components |
ptype |
ptype |
component-wise division |
ptype |
ptype |
division of all components |
|
ptype |
division by all components |
ptype |
ptype |
component-wise addition |
ptype |
|
component-wise subtraction |
ptype |
|
1 if the two values are equal, else 0 |
ptype |
|
1 if the two values are different, else 0 |
The generic ptype is listed in places where any of point
, vector
, or
normal
may be used.
All of the binary operators may combine a scalar value (float
or int
) with
a point-like type, treating the scalar if it were point-like with three
identical components.
5.5. matrix
#
OSL has a matrix
type that represents the transformation matrix required to
transform points and vectors between one coordinate system and another.
Matrices are represented internally by 16 floats (a \(4 \times 4\) homogeneous
transformation matrix).
A matrix
can be constructed from a single float or 16 floats. For example:
matrix zero = 0; // makes a matrix with all 0 components
matrix ident = 1; // makes the identity matrix
// Construct a matrix from 16 floats
matrix m = matrix (m00, m01, m02, m03, m10, m11, m12, m13,
m20, m21, m22, m23, m30, m31, m32, m33);
Assigning a single floating-point number \(x\) to a matrix will result in a matrix with diagonal components all being \(x\) and other components being zero (i.e., \(x\) times the identity matrix). Constructing a matrix with 16 floats will create the matrix whose components are those floats, in row-major order.
Similar to point-like types, a matrix
may be constructed in
reference to a named space:
// Construct matrices relative to something other than "common"
matrix q = matrix ("shader", 1);
matrix m = matrix ("world", m00, m01, m02, m03, m10, m11, m12, m13,
m20, m21, m22, m23, m30, m31, m32, m33);
The first form creates the matrix that transforms points from “shader” space
to “common” space. Transforming points by this matrix is identical to calling
transform("shader", "common", ...)
. The second form prepends the
current-to-world transformation matrix onto the \(4 \times 4\) matrix with
components \(m_{0,0} ... m_{3,3}\). Note that although we have used "shader"
and "world"
space in our examples, any named space is acceptable.
A matrix may also be constructed from the names of two coordinate systems, yielding the matrix that transforms coordinates from the first named space to the second named space:
matrix m = matrix ("object", "world");
The example returns the object-to-world transformation matrix.
Matrix variables can be tested for equality and inequality with the ==
and
!=
boolean operators. Also, the *
operator between matrices denotes
matrix multiplication, while m1 / m2
denotes multiplying m1
by the inverse
of matrix m2
. Thus, a matrix can be inverted by writing 1/m
. In
addition, some functions will accept matrix variables as arguments, as
described in Section Standard Library Functions.
Individual components of a matrix variable may be set or accessed using array notation, for example,
matrix M;
float x = M[row][col];
M[row][col] = 1;
Valid component indices are integers on \([0...3]\). It is an error to access a matrix component with either a row or column outside this range.
The following operators may be used with matrices (in order of decreasing precedence, with each box holding operators of the same precedence):
Operation |
Result |
Explanation |
---|---|---|
matrix |
|
component access (row, column) |
|
|
unary negation |
matrix |
|
matrix multiplication |
matrix |
|
component-wise scaling |
float |
|
component-wise scaling |
matrix |
|
multiply the first matrix by the *inverse of the second |
matrix |
|
component-wise division |
float |
|
multiply the float by the *inverse of the matrix |
matrix |
int |
1 if the two values are equal, else 0 |
matrix |
int |
1 if the two values are different, else 0 |
5.6. string
#
The string
type may hold character strings. The main application
of strings is to provide the names of files where textures may be
found. Strings can be compared using ==
and !=
.
String constants are denoted by surrounding the characters with double
quotes, as in "I am a string literal"
. As in C programs, string
literals may contain escape sequences such as \n
(newline),
\r
(carriage return), \t
(tab), \"
(double quote),
\\
(backslash).
Two quote-quoted string literals that are separated only by whitespace (spaces, tabs, or newlines) will be automatically concatenated into a single string literal. In other words,
"foo" "bar"
is exactly equivalent to "foobar"
.
5.7. void
#
The void
type is used to designate a function that does not return a value.
No variable may have type void
.
5.8. Arrays#
Arrays of any of the basic types are supported, provided that they are 1D and statically sized, using the usual syntax for C-like languages:
float d[10]; // Declare an uninitialized array
float c[3] = { 0.1, 0.2, 3.14 }; // Initialize the array
float f = c[1]; // Access one element
The built-in function arraylength()
returns the number of elements in an
array. For example:
float c[3];
int clen = arraylength(c); // should return 3
There are two circumstances when arrays do not need to have a declared length — an array parameter to a function, and a shader parameter that is an array. This is indicated by empty array brackets, as shown in the following example:
float sum (float x[])
{
float s = 0;
for (int i = 0; i < arraylength(x); ++i)
s += x[i];
return s;
}
It is allowed in OSL to copy an entire array at once using the =
operator,
provided that the arrays contain elements of the same type and that the
destination array is at least as long as the source array. For example:
float array[4], anotherarray[4];
...
anotherarray = array;
5.9. Structures#
Structures are used to group several fields of potentially different types
into a single object that can be referred to by name. You may then use the
structure type name to declare structure variables as you would for any of the
built-in types. Structure elements are accessed using the .
(“dot”)
operator. The syntax for declaring and using structures is similar to C or
C++:
struct RGBA { // Define a structure type
color rgb;
float alpha;
};
RGBA col; // Declare a structure
r.rgb = color (1, 0, 0); // Assign to one field
color c = r.rgb; // Read from a structure field
RGBA b = { color(.1,.2,.3), 1 }; // Member-by-member initialization
You can use “constructor expressions” for a your struct types much like
you can construct built-in types like color
or point
:
struct_name
(
first_member_value, …}
For example,
RGBA c = RGBA(col,alpha); // Constructor syntax
RGBA add (RGBA a, RGBA b)
{
return RGBA (a.rgb+b.rgb, a.a+b.a); // return expression
}
// pass constructor expression as a parameter:
RGBA d = add (c, RGBA(color(.3,.4,.5), 0));
You may also use the compound initializer list syntax to construct a type when it can be deduced from context which compound type is required. For example, this is equivalent to the preceding example:
RGBA c = {col,alpha}; // deduce by what is being assigned to
RGBA add (RGBA a, RGBA b)
{
return { a.rgb+b.rgb, a.a+b.a }; // deduce by func return type
}
RGBA d = add (c, {{.3,.4,.5}, 0}); // deduce by expected arg type
It is permitted to have a structure field that is an array, as well as to have an array of structures. But it is not currently permitted to “nest” arrays (that is, to have an array of structs which contain members that are arrays).
struct A {
color a;
float b[4]; // Ok: struct may contain an array
};
RGBA vals[4]; // Ok: Array of structures
vals[0].a = 0;
A d[5]; // NO: Array of structures that contain arrays
5.10. Closures#
A closure
is an expression or function call that will be stored, along
with necessary contextual information, to be evaluated at a later time.
In general, the type “closure
gentype” behaves exactly like a
gentype, except that its numeric values may not be examined or
used for the duration of the shader’s execution. For example, a
color closure
behaves mostly like a color — you can multiply it by a
scalar, assign it to a color closure
variable, etc. — but you may not
assign it to an ordinary color
or examine its individual component’s
numeric values.
It is legal to assign 0
to a closure, which is understood to mean setting it
to a null closure (even though in all other circumstances, assigning a
float
to a closure
would not be allowed).
At present, the only type of closure
supported by OSL is the color closure
,
and the only allowed operations are those that let you form a linear
combination of color closure
’s. Additional closure types and operations are
reserved for future use.
Allowable operations on color closure
include:
Operation |
Result |
Explanation |
---|---|---|
|
|
unary negation |
color |
|
component-wise scaling |
colorclosure |
|
component-wise scaling |
float |
|
scaling |
colorclosure |
|
scaling |
colorclosure |
|
component-wise addition |