Version 5 Real-Time Shading Language Description
------------------------------------------------
Kekoa Proudfoot
October 26, 2000
Language version history
------------------------
The version 1 language had lisp-like parenthetical constructs and shaders
expressions of fixed colors, textures, and lit materials. The only data
type was a [0,1] clamped color, and the allowed operators were add,
multiply, and blend (over).
The version 2 language replaced the lisp-like constructs of the version 1
language with ones more like C. The underlying expressions, operators, and
data types did not change.
The version 3 language was discussed but never implemented. The intent was
to extend the version 2 language to remove the restriction that colors,
textures, and lit materials be fixed by making these data types
configurable through parameters to shaders. This language version was also
to introduce a separation between light shaders and surface shaders.
The version 4 language allowed shaders to be configured using shader
parameters and provided a light/surface shader abstraction. It also
introduced the concept of multiple computation frequnecies, making use of
types to manage when and how computations are performed. New vertex and
primitive-group processing capabilities were exposed to complement a set of
fragment processing capabilities similar to those available in previous
language versions.
The version 5 language is described in this document. It is an extension
of the version 4 language intended to allow us to explore compilation to
advanced fragment processing pipelines. The new features include
three-component vectors, three-by-three matrices, three-vector operations,
more fragment operations, operators to assist with compiling to fragment
pipelines, and conditional compilation.
Basics
------
The general format of our language, as well as our language's declaration
and expression syntax, is similar to C. Our language does, however, have a
number of notable differences. These include a different set of data
types, a number of specialized type modifiers, a slightly different set of
operators, and different semantics with regards to function calls and
global variables. These differences will become clearer as you proceed
through this document.
As with C, our language relies on white space and indenting only to the
extent that they separate tokens in the language. White space and
indenting are otherwise ignored.
Comments are allowed in our language. These may be denoted using either
the C /* */ syntax or the C++ // comment syntax. Identifiers, integers,
and floats are all specified as they are in C. Identifiers are
case-sensitive.
Base data types
---------------
We begin the discussion of our language with a description of its data
types.
In our language, data types are composed of a base data type preceeded by
an optional list of type modifiers. In this section, we describe the base
data types. We leave the discussion of type modifiers for later sections.
Our language supports ten base data types. They are:
bool boolean value
clampf1 scalar [0,1]-clamped floating-point value
clampf3 3-component [0,1]-clamped floating-point vector
clampf4 4-component [0,1]-clamped floating-point vector
float1 scalar unclamped floating-point value
float3 3-component unclamped floating-point vector
float4 4-component unclamped floating-point vector
matrix3 3x3 floating point matrix
matrix4 4x4 floating point matrix
texref texture reference
Two of these types need further explanation.
The bool type is either true or false. It has no numerical value.
The texref type stores a reference to a texture. Its value corresponds to
an OpenGL texture name as specified to glBindTexture.
Additionally, note that although the clamped float types are described as
floating point, because their ranges are limited to [0,1], they may be
implemented using either fixed- or floating-point.
In addition to the ten base types, we support some additional type names
for compatibility with the previous version of the language:
clampf same as clampf1
clampfv same as clampf4
float same as float1
floatv same as float4
matrix same as matrix4
Expressions, operators, and builtin functions
---------------------------------------------
The expression syntax of our language is much like that of C, except that
we provide a different set of operators and also a core set of builtin
functions. In this section, we introduce and describe these operators and
functions.
Most operators that we provide have both float and clampf versions, where
the clampf versions are defined to clamp their results (but not their
intermediate values) to [0,1]. We make special note of operators which
either do not have clampf versions or do not operate on float or clampf
values at all.
We begin with operators for manipulating scalars and vectors.
The join operator {} assembles scalars into vectors. It comes in three
versions:
{ x, y, z } // make a 3-vector from scalars x, y, and z
{ x, y, z, w } // make a 4-vector from scalars x, y, z, and w
{ xyz, w } // make a 4-vector from 3-vector xyz and scalar w
The index operator [] extracts a scalar from a 3- or 4-component vector.
Indexing is zero-based:
{ x, y, z }[0] // extract x
{ x, y, z }[2] // extract z
{ x, y, z, w }[3] // extract w
The rgb(), alpha(), and blue() operators help make compilation to fragment
pipelines efficient. Their various forms are shown here:
rgb({ r, g, b, a }) // extract 3-vector { r, g, b } from 4-vector
alpha({ r, g, b, a }) // extract scalar a from 3-vector
blue({ r, g, b, a }) // extract scalar b from 3-vector
blue({ r, g, b }) // extract scalar b from 3-vector
rgb(c) // construct 3-vector { c, c, c } from scalar c
We provide scalar and vector versions of add, multiply, subtract, and
divide. For multiply and divide, we also provide versions that operate on
one scalar and one vector in either order. Some examples:
a + b
a - b
a * b
a / b
{ ax, ay, az } + { bx, by, bz }
{ ax, ay, az } - { bx, by, bz }
{ ax, ay, az } * { bx, by, bz }
{ ax, ay, az } / { bx, by, bz }
a * { bx, by, bz }
a / { bx, by, bz }
{ ax, ay, az } * b
{ ax, ay, az } / b
etc.
Multiplication of two matrices and multiplication of one matrix (on the
left) and one vector (on the right) are also supported. Since we do not
support clamped matrices, there are no clampf matrix-matrix or
matrix-vector multiply operations.
We provide an unclamped floating-point negate operator:
- a
We do not provide a clampf version of the negate operator, since its result
would always be zero.
We provide a generic blend operator that operates on clamped and unclamped
4-vectors only. The blend operator is based on the OpenGL blend function
and takes the following form:
blend ( src_factor, dst_factor )
Note this the blend operator is a binary infix operator. The value to the
left of the blend is called the source (src) and the value to the right of
the blend is called the destination (dst):
src blend(src_factor,dst_factor) dst
Such an expression computes:
src_factor * src + dst_factor * dst
Both src_factor and dst_factor are placeholders for names chosen from the
following list. Each has the value indicated:
Factor Name Factor Value
ZERO { 0, 0, 0, 0 }
ONE { 1, 1, 1, 1 }
SRC_COLOR src
SRC_ALPHA { src[3], src[3], src[3], src[3] }
DST_COLOR dst
DST_ALPHA { dst[3], dst[3], dst[3], dst[3] }
ONE_MINUS_SRC_COLOR { 1, 1, 1, 1 } - src
ONE_MINUS_SRC_ALPHA { 1, 1, 1, 1 } - { src[3], src[3], src[3], src[3] }
ONE_MINUS_DST_COLOR { 1, 1, 1, 1 } - dst
ONE_MINUS_DST_ALPHA { 1, 1, 1, 1 } - { dst[3], dst[3], dst[3], dst[3] }
We provide two additional blend operators to simplify the specification of
common blend operations. The `over' operator composites two values with
premultiplied alpha, and is equivalent to blend(ONE,ONE_MINUS_SRC_ALPHA).
The `blend_over' operator composites two values where only second value has
premultiplied alpha. The first value has non-premultiplied alpha. It is
equivalent to blend(SRC_ALPHA,ONE_MINUS_SRC_ALPHA).
We provide a standard set of comparison operators (==, !=, >, <, >=, <=)
for computing boolean values. We also provide a lthalf() operator to
assist with fragment compilation. The lthalf() operator returns true if
its operand is less than one half.
Boolean expressions are used with the conditional select operator. The
select operator takes three parameters: a boolean, a value to return if the
boolean is true, and a value to return if the boolean is false. Some
examples:
select(0 == 0, t, f) // value is t
select(0 > 1, t, f) // value is f
select(lthalf(0), t, f) // value is t
select(lthalf(0.5), t, f) // value if f
We provide a number of additional operations, including: scalar and vector
clamp, min, and max operations; vector dot, length, and normalize
operations; a 3-vector reflect and cross operations; sin, cos, pow, and
sqrt. Some examples:
clamp(0.5, 0, 1) // value is 0.5
clamp({ -1, 0, 1, 2 }, 0, 1) // value is { 0, 0, 1, 1 }
clamp({ -1, 1, 3 }, { 0, 0, 1 }, { 1, 2, 2}) // value is { 0, 1, 2 }
min({ -1, 1, 2, 3 }, { 1, 0, 1, 4 }) // value is { -1, 0, 1, 4 }
dot({ 0, 1, 2, 3 }, { 4, 5, 6, 7 }) // value is 38
length({ 3, 4, 0 }) // value is 5
length({ 1, 1, 1 }) // value is 1.7320...
length({ 1, 1, 1, 1 }) // value is 2
normalize({ 0, 0, 2 }) // value is { 0, 0, 1 }
reflect({ 1, 1, 1 }, { 0, 0, 1 }) // value is { -1, -1, 1 }
reflect({ 1, 0, 0 }, { 0, 1, 0 }) // value is { 0, 0, 1 }
sin(3.14159) // value is 0
cos(3.14159) // value is -1
pow(10,2) // value is 100
sqrt(2) // value is 1.4142...
We also provide a number of matrix operations:
affine extracts the upper-left 3x3 matrix from a 4x4 matrix
frustum generates a 4x4 frustum projection matrix
identity generates a 4x4 identity matrix
invert inverts a 3x3 or a 4x4 matrix
lookat generates a 4x4 lookat matrix
ortho generates a 4x4 orthographic projection matrix
rotate generates a 4x4 rotation matrix of an angle about an axis
scale generates a 4x4 scale matrix
translate generates a 4x4 translation matrix
transpose transposes a 3x3 or 4x4 matrix
identity3 generates a 3x3 identity matrix
rotate3 generates a 3x3 rotation matrix
scale3 generates a 3x3 scale matrix
The exact parameters needed for each matrix operation are discussed in the
operator appendix.
A number of texturing and lookup operations are also available:
cubemap perform a cubemap lookup given a texref and a 3-vector
cubenorm perform a 3-vector normalization given a 3-vector
lut perform a component-wise fragment clamp4 table lookup
texture perform a 2d texture lookup given a texref and a 3- or 4-vector
texture3d perform a 3d texture lookup given a texref and a 3- or 4-vector
bumpdiff perform a diffuse bumpmap operation
bumpspec perform a specular bumpmap operation (requires bumpdiff)
The exact parameters needed for each texture/lookup operation are discussed
in the operator appendix.
The lut operator performs a component-wise table lookup of fragment value.
It uses the OpenGL color lookup table defined using glPixelMap. Our intent
is to eventually abstract lookup table specification to allow multiple
lookup tables, but currently we only support one color lookup table at a
time.
The bumpdiff and bumpspec operators implement bumpmapping as described for
NVIDIA hardware by Mark Kilgard. The bumpdiff operator computes the
diffuse reflection coefficient given a tangent-space normal map, texture
coordinates, and a tangent-space light vector. The bumpspec operator
computes the specular reflection coefficient given the same normal map and
texture coordinates plus the tangent-space half-angle vector. The bumpdiff
operator leaves a self-shadowing term in alpha which must be used to
modulate the bumpspec result. The blend operator, configured as
blend(ONE,SRC_ALPHA), is used to accomplish this.
As with C, we support parentheses () for grouping expressions to override
the default operator precedences.
Two special operators are the assignment and cast operators. Both are used
as they typically are in C. Assignment implies a cast to the type of the
value being set. Type conversion is discussed in greater detail in a later
section describing type conversion.
An important note about the assignment operator. We currently do not
support assignment to an indexed vector element:
v[3] = 0; // forbidden
Use something like this instead:
v = { v[0], v[1], v[2], 0 }; // use something like this instead
Finally, we mention the integrate() operator, which we discuss in more
detail in a later section on surface and light shaders.
Operator precedence
-------------------
We define the following binary operator precedences, by group from lowest
precedence to highest precedence:
=
== !=
> < >= <=
+ -
blend over blend_over
* /
All of the binary operators are left associative, except for =, which is
right associative.
Statements
----------
Our language supports three kinds of statements: variable declarations,
expression statements, return statements. Empty statements are permitted;
these are ignored.
A variable declaration is similar to C, and consists of a type followed by
an identifier followed by an optional initializer followed by a semicolon.
float1 f1; // declare f1
float1 f2 = 1; // declare and initialize f2
float4 v1 = { 1, 2, 3, 4 }; // declare and initialize v1
float4 v2 = f1 * v1; // declare and initialize v2
As with C++, variables may be declared anywhere in a basic block.
Expression statements are simply an expression followed by a semicolon:
1; // valid but useless, eventually optimized away
N = normalize(N); // normalize N
NdotL = dot(N,L); // compute dot product of N and L
A return statement is used to indicate the final value of a shader or
function:
return color;
Functions
---------
Our language allows functions to be defined and called mostly like they are
in C, with a few exceptions. First, there is no such thing as a `void'
function, and therefore all functions must return a value. Second, there
is (currently) no such thing as a function declaration for user-defined
functions. All user-defined functions must be defined before they may be
used. Finally, recursion is forbidden.
All of these differences are due to the way function calls are
implemented. All function calls are inlined.
Here are some examples:
float4 lerp (float4 a, float4 b, float afrac)
{
return afrac * a + (1 - afrac) * b
}
float4 bilerp (float4 v00, float4 v01, float4 v10 float4 v11,
float frac0, float frac1)
{
float v0 = lerp(v00, v01, frac0);
float v1 = lerp(v10, v11, frac0);
return lerp(v0, v1, frac1);
}
Surface shaders, light shaders, and the integrate() operator
------------------------------------------------------------
Our language borrows the RenderMan concept of separate surface and light
shaders to provide orthogonality between these shading operations. Light
shaders compute how much light is incident on a surface, while surface
shaders compute the amount of light reflected toward the viewer, possibly
querying lights to determing and account for the amount of light arriving
from each light source.
Surface and light shaders are written as functions are, except that their
return types are preceeded by the `shader' modifier plus also either the
`surface' or the `light' modifier. In addition, shaders must return a
float4 or a clamp4 type:
float func () { return ...; } // an ordinary function
surface shader float4 surf () { return ...; } // a surface shader
light shader float4 light () { return ...; } // a light shader
The surface and light modifiers may also be applied to functions. When
this is done, such a function may access special features (variables and
such) available only to surface and light shaders. In addition, the
function becomes accessible only to other surface or light functions and
shaders, as appropriate. More examples:
surface float surffunc () { return ...; } // a surface function
surface float lightfunc () { return ...; } // a light function
To query light sources, surface shaders (and functions) use the integrate()
operator. This operator takes an expression and loops over all active
light sources, evaluating the expression once per light source. The
operator returns the sum of the expression evaluations.
The integrate() operator evaluates special `per-light' expressions, which
are expressions that depend directly on special built-in per-light values
(in particular the light vector, the half-angle vector, and the light
intensity) and/or other per-light expressions. In evaluating a per-light
expression once per light, the integrate() operator removes the per-light
attribute of the integrated expression.
We use a type modifier scheme to track per-light expressions. Just as
every value in our system has a type, every value also has a type modifier
that specifies whether or not the value changes with every light. In our
system, the keyword `perlight' is used to indicate such a value. We
require all variables and return values that hold per-light values to be
declared with the perlight modifier. We impose this requirement to make
user code more readable. Our compiler separately infers which values are
perlight, and it uses this information to report an error when a perlight
value is stored to a non-perlight variable.
Here are some examples of perlight values and the integrate() operator.
Assume L, H, and Cl are per-light values:
float4 Kd = ...; // compute diffuse surface color
perlight float NdotL = max(dot(N,L),0); // max(dot(N,L),0) is perlight
perlight float intensity = Cl * NdotL; // Cl * NdotL is perlight
float color = Kd * integrate(intensity); // integrate light and modulate
perlight float NdotH = dot(N,H); // dot(N,H) is perlight
float NdotH = dot(N,H); // error: missing perlight modifier
As we will see in a later section on builtin global values, Cl in
particular references the amount of light incident on the surface from each
light. By referencing Cl, surface shaders indirectly reference the active
light shaders.
Values that have been integrated once cannot be integrated again. This is
something of an artificial restriction that was imposed because it really
doesn't make a lot of sense to integrate a value that has already been
integrated.
Computation frequencies and computation frequency type modifiers
----------------------------------------------------------------
A key aspect of our system is its support for computations at a variety of
different rates, or computation frequencies. We support four different
computation frequencies: once at compile time, once per group of
primitives, once per vertex, and once per fragment. In our system every
shading computation occurs at one of the rates.
Note that we do not provide a frequency that corresponds to once per
primitive. Ideally we would support such a frequency, in particular for
flat shading, but do not because OpenGL only provides limited support for
that computation frequency. Specifically, OpenGL does not provide support
for per-primitive texture coordinates.
As with our treatment of per-light expressions, we use a type modifier
system to control the frequencies at which computations occur. This
modifier specifies how often that value is computed (or specified, if the
value is a parameter).
There is one type modifier for each computation frequency. The modifiers
are: `constant', `vertex', `primitive group', and `fragment'. We provide
an additional modifier, `perbegin', for compatibility with the previous
language version. This additional modifier is equivalent to the primitive
group modifier.
Three base types, namely the two matrix types and the texref type, have a
maximum computation frequency of primitive group. This restriction
effectively limits how often matrices and texrefs may be computed or
specified. This is somewhat of an arbitrary restriction for the matrix
types, since there is no reason matrices cannot be computed per-vertex or
per-fragment; however, we impose this restriction to simplify our compiler
somewhat. The restriction on texrefs reflects the fact that in OpenGL,
textures are specified for entire primitive groups and never more often
(such as per-vertex).
Our language defines a set of rules to allow compilers to infer how often a
particular value is computed. Such a set of rules is important both
because it removes the need for the user to explicitly manage computation
frequencies and because it allows for efficient generation of code when the
user does not know the computation frequencies of certain values, in
particular the intensity of light arriving at a surface, which can
reasonably have any computation frequency. In the latter case, a compiler
that can infer computation frequencies can properly choose, for example,
vertex operations or fragment operations to integrate vertex and fragment
lights, respectively.
Two rules are used to infer computation frequencies. The first deals with
the default computation frequencies of shader parameters, while the second
deals with the propagation of computation frequencies across operators. By
applying these rules, a compiler can always infer the computation
frequency of a given operation.
All shader parameters have a well-defined default computation frequency
that indicates how often the parameter may be specified. This frequency
depends on the parameter's base type and the corresponding shader's type
(surface or light):
Type Default for surfaces Default for lights
bool vertex primitive group
clampf1 vertex primitive group
clampf3 vertex primitive group
clampf4 vertex primitive group
float1 vertex primitive group
float3 vertex primitive group
float4 vertex primitive group
matrix3 primitive group primitive group
matrix4 primitive group primitive group
texref primitive group primitive group
Note that the defaults are different for surfaces and lights. This
reflects the fact that typically light properties do not change more often
than per-primitive-group.
The default shader parameter computation frequencies take effect when no
computation frequency is specified with the parameter. An
explicitly-specified computation frequency overrides the default.
Some examples:
surface shader float4 surf1 (float1 f) { ... } // f is vertex
surface shader float4 surf2 (matrix3 m) { ... } // m is primitive group
light shader float light1 (float1 f) { ... } // f is primitive group
light shader float light2 (vertex float1 f) { ... } // f is vertex
light shader float light3 (matrix3 m) { ... } // m is primitive group
Note that the rules for default computation frequencies do not apply to
functions. They only apply to shaders:
surface surffunc1 (float1 f) { ... } // no default computation frequency
In this case, the computation frequency of f is determined by the value
passed to f when surffunc1 is called.
The computation frequencies of computed values are determined by applying a
second rule that propagates computation frequencies across operators. For
the most part, we try to compute things as infrequently as possible.
Specifically, the computation frequency of a computed value is the least
frequent computation frequency possible given the constraint that a value
must be computed at least as often as the most frequent value it depends
on. For example, the result of adding a vertex value to another vertex
value is a vertex value, but adding a vertex value to a fragment value
results in a fragment value, both because of the rule previously mentioned
and because really it doesn't make any sense to try to obtain vertex values
from fragment ones.
A number of operations can only be evaluated at certain computation
frequencies. For example, texturing can only be computed per-fragment,
while matrix-matrix multiplication can be computed at most
per-primitive-group. We place additional constraints on computation
frequencies to satisfy the limitations of each operation. We describe the
details of these per-operator constraints in the operator appendix.
While the computation frequencies of computed values are inferred using the
rules just described, they may be controlled by explicitly specifying
computation frequencies. For example, if two vertex values N and L are to
be used to compute dot(N,L), the result of the dot product will normally be
per-vertex. However, a per-fragment dot product can be achieved by first
casting N or L (or both) to a fragment value:
float3 Nf = (fragment float3) N; // cast N, fragment Nf inferred
float3 Lf = (fragment float3) L; // cast L, fragment Lf inferred
// compute and use dot(Nf,Lf)...
fragment float3 Nf = N; // use implicit cast from assign
fragment float3 Lf = L; // use implicit cast from assign
// compute and use dot(Nf,Lf)...
dot(N, (fragment float3)L)... // cast L only
In all three cases, once a fragment version of N or L is computed, the
resulting dot product is inferred to be evaluated per-fragment.
Type conversion
---------------
A number of type conversions are permitted, including conversion of clamped
values to float values, conversion of float values to clamped values,
conversion from one computation frequency to a more-frequency computation
frequency, and conversion of non-per-light values to per-light values.
Converting clamped values to float values has no effect except perhaps one
of number representation (specifically, floating point or fixed point).
Also, since floating-point values are more general than clamped
floating-point values, this conversion is considered a promotion. Before
performing an operation that involves both clamped and unclamped values,
clamped values are automatically promoted to unclamped values.
Converting a float value to a clamped value clamps the float value to
[0,1]. The number representation possibly changes also. This conversion
may be performed explicitly using a type cast, or implicitly when assigning
a float value to a clampf variable.
Conversion from one computation frequency to another is only possible if
the new computation frequency is more frequent than the old one. In most
cases, such a conversion simply replicates the old value at the new
computation frequency; however, the conversion from vertex to fragment is
special. In this case, vertex values are interpolated between vertices to
obtain a fragment value. The exact nature of the interpolation is
currently being left unspecified. Our compiler follows what OpenGL
specifies, i.e. texture coordinates are perspective-correct while color
values are not necessarily that way.
The conversion of the computation frequencies of operands to an operator is
performed automatically as necessary for each operator. This process
follows the rules for operator overloading and the function prototypes for
operators discussed in later sections.
A non-per-light value may be converted into a per-light value. Performing
this conversion has the effect of replicating the non-per-light value for
every light.
Unlike in C, there is no way to interpret the value of a comparison
numerically.
Global variables
----------------
Our system supports user-defined global variables as long as they are
constant and their values are specified. Globals must be explicitly
declared as constant:
constant float4 Red = { 1, 0, 0, 1 }; // valid
constant float4 Red; // error: missing definition
float4 Red = { 1, 0, 0, 1 }; // error: missing constant keyword
constant float4 DarkRed = 0.5 * Red; // functions of constants are valid
Predefined globals
------------------
A number of global values are predefined and initialized on demand before a
shader executes, or, in the case of predefined perlight globals, before
each evaluation of the expression integrated by the corresponding
integrate() operator. The predefined light shader global variables are:
vertex float3 S; // light-space surface vector, normalized
vertex float Sdist; // distance to surface point
The predefined surface shader globals are:
vertex float3 N; // eye-space normal vector, normalized
vertex float3 T; // eye-space tangent vector, normalized
vertex float3 B; // eye-space binormal vector, normalized
vertex float3 E; // eye-space eye vector, normalized
vertex float4 P; // eye-space surface position, w=1
vertex float4 Pobj; // object-space surface position, w=1
vertex float4 Ca; // color of global ambient light
vertex float4 Cprev; // previous framebuffer color
vertex perlight float3 L; // eye-space light vector, normalized
vertex perlight float3 H; // eye-space halfangle vector, normalized
vertex perlight float4 Cl; // color of light (from a light shader)
Note that the definitions of the various globals currently cause light
shaders to be evaluated in light space and surface shaders to be evaluated
in eye space. Light space is defined by the light's position and
orientation, while eye space is defined by the viewer's position and
orientation.
The use of builtin parameters implicitly makes a shader dependent on one or
more implicit shader parameters which are used to evaluate the builtin
parameters. It is important to recognize these implicit shader parameters
even though they are not a formal part of the language, since ultimately
the user must set these parameters in addition to all those explicitly
required by the active surface and light shaders. The implicit parameters
are:
perbegin float4 __ambient; // color of global ambient light
perbegin matrix4 __modelview; // modelview matrix
perbegin matrix4 __projection; // projection matrix
vertex float3 __normal; // object-space normal vector
vertex float3 __tangent; // object-space tangent vector
vertex float3 __binormal; // object-space binormal vector
vertex float4 __position; // object-space surface position
perbegin perlight float4 __lightpos; // homogeneous position of light
perbegin perlight float3 __lightdir; // unnormalized eye-space light direction
perbegin perlight float3 __lightup; // unnormalized eye-space light up vector
Perlight builtin parameters must be specified once per active light shader.
Note that all shaders depend on __modelview, __projection, and __position.
Function overloading
--------------------
Our language allows functions to be overloaded in a manner similar to C++.
Overloading allows for many functions to be available when a function is
called. Availability is defines as a function with the same name and
number of parameters. We define a set of rules to select which function to
select when more than one choice is available. The rules examine the base
types of the parameters used in the call to form groups of matching
functions.
The first group consists of functions whose parameter base types match the
base types of the parameters in the call exactly.
The second group consists of functions whose parameter base types match the
base types of the parameters in the call through the possible use of
promotion. In particular, we consider the promotion of clamped floats to
floats to form matches.
The third group consists of functions whose parameter base types match the
base types of the parameters in the call through the use of both promotion
and demotion.
The first group is checked first. If empty, the second group is checked,
and likewise for the third group. If all three groups are empty, there is
no match, and an error is generated. If any group being checked has more
than one choice available, the call is ambiguous, and an error is
generated. A match is found only if exactly one match is available in the
first non-empty group.
This overloading mechanism is used for user-defined functions as well as
builtin functions and builtin operators. Builtin functions and operators
are defined using function prototypes in the operator appendix, below.
[Note: The above description is how things are supposed to work.
Currently, function calls to builtin and user-defined functions are matched
using the rules from the previous language version.]
Conditional compilation
-----------------------
Today's hardware platforms offer differing sets of functionality. Some
operators are not available on all hardware. To solve this problem, our
language supports conditional compilation using a very-limited subset of
C preprocessor directives. We support:
#if
#ifdef
#ifndef
#else
#endif
Our compiler defines a number of identifiers based on whether or not
certain hardware features are available. These identifiers are:
HAVE_FRAGMENT_SUBTRACT
HAVE_TEXTURE_3D
HAVE_CUBEMAP
HAVE_BUMPOPS
HAVE_REGISTER_COMBINERS
HAVE_FRAGMENT_INDEX
HAVE_FRAGMENT_COMPARES
The HAVE_FRAGMENT_SUBTRACT, HAVE_TEXTURE_3D, and HAVE_CUBEMAP identifiers
indicate whether or not the subtract operator is available per-fragment,
whether or not the texture3d operator is available, and whether or not the
cubemap operator is available, respectively. The HAVE_BUMPOPS identifier
indicates whether or not the bumpdiff and bumpspec operators are available.
The HAVE_REGISTER_COMBINERS identifier covers the availability of the
following operators per-fragment: dot, select, rgb, blue, alpha, lthalf,
cubenorm. The HAVE_FRAGMENT_INDEX identifier indicates whether or not the
[] operator is available per-fragment. The HAVE_FRAGMENT_COMPARES
identifier indicates whether or not the ==, !=, >, <, >=, and <= operators
are available per-fragment.
Appendices
----------
Builtin operators and functions
-------------------------------
In this appendix, we describe the enumerate the builtin operators and
functions made available by our language. Except for the syntax by which
they are referred to, builtin operators and functions behave identically.
Every builtin operator and function has a range of computation frequencies
at which it may be evaluated; the range specifies both a minimum and a
maximum frequency.
As described earlier, values are evaluated as infrequently as possible. We
define this computation frequency precisely as the maximum frequency among
all of an operator's operands and the operator's miminum computation
frequency.
Minimum and maximum computation frequencies limit the kinds of operations
available at each computation frequency. For example, they restrict many
matrix manipulation operations to a maximum computation frequency of
per-primitive-group, and they force texture mapping to be per-fragment.
An error is generated if an operator's evaluation computation frequency
exceeds the operator's maximum computation frequency.
In addition to each operator having a range of computation frequencies,
every operand of every operator also has an associated range of computation
frequencies. In most cases, this range has a minimum frequency of constant
and a maximum frequency equal to the maximum frequency of the operator
itself, but in a few cases, the range is more restrictive. For example,
current hardware does not support the use of per-fragment texture
coordinates. We therefore limit the maximum computation frequency of
texture coordinates to vertex values.
In cases where the minimum frequency of an operand is not met, the value
passed to the operand is automatically cast to an appropriate computation
frequency. In cases where the maximum frequency of an operand is exceeded,
an error is generated.
We now list all of the available operators. In the listings below, ranges
are specified using a [min:]max syntax. For operators, if the min is
unspecified, it defaults to constant. For operands, if the min and max are
unspecified, the range defaults to the range of the corresponding operator,
otherwise if only the min is unspecified, the min defaults to the max.
fragment float1 operator+ (float1, float1)
fragment float3 operator+ (float3, float3)
fragment float4 operator+ (float4, float4)
fragment clampf1 operator+ (clampf1, clampf1)
fragment clampf3 operator+ (clampf3, clampf3)
fragment clampf4 operator+ (clampf4, clampf4)
fragment float1 operator- (float1, float1)
fragment float3 operator- (float3, float3)
fragment float4 operator- (float4, float4)
fragment clampf1 operator- (clampf1, clampf1)
fragment clampf3 operator- (clampf3, clampf3)
fragment clampf4 operator- (clampf4, clampf4)
fragment float1 operator* (float1, float1)
fragment float3 operator* (float3, float3)
fragment float3 operator* (float1, float3)
fragment float3 operator* (float3, float1)
fragment float4 operator* (float4, float4)
fragment float4 operator* (float1, float4)
fragment float4 operator* (float4, float1)
fragment clampf1 operator* (clampf1, clampf1)
fragment clampf3 operator* (clampf3, clampf3)
fragment clampf3 operator* (clampf1, clampf3)
fragment clampf3 operator* (clampf3, clampf1)
fragment clampf4 operator* (clampf4, clampf4)
fragment clampf4 operator* (clampf1, clampf4)
fragment clampf4 operator* (clampf4, clampf1)
perbegin matrix3 operator* (matrix3, matrix3)
perbegin matrix4 operator* (matrix4, matrix4)
vertex float3 operator* (matrix3, float3)
vertex float4 operator* (matrix4, float4)
vertex float1 operator/ (float1, float1)
vertex float3 operator/ (float3, float3)
vertex float3 operator/ (float1, float3)
vertex float3 operator/ (float3, float1)
vertex float4 operator/ (float4, float4)
vertex float4 operator/ (float1, float4)
vertex float4 operator/ (float4, float1)
vertex clampf1 operator/ (clampf1, clampf1)
vertex clampf3 operator/ (clampf3, clampf3)
vertex clampf3 operator/ (clampf1, clampf3)
vertex clampf3 operator/ (clampf3, clampf1)
vertex clampf4 operator/ (clampf4, clampf4)
vertex clampf4 operator/ (clampf1, clampf4)
vertex clampf4 operator/ (clampf4, clampf1)
fragment float1 operator- (float1)
fragment float3 operator- (float3)
fragment float4 operator- (float4)
fragment float1 operator[] (float3)
fragment float1 operator[] (float4)
fragment clampf1 operator[] (clampf3)
fragment clampf1 operator[] (clampf4)
vertex float3 operator{} (float, float, float)
vertex float4 operator{} (float, float, float, float)
vertex clampf3 operator{} (clampf, clampf, clampf)
vertex clampf4 operator{} (clampf, clampf, clampf, clampf)
fragment float4 operator{} (float3 rgb, float1 alpha)
fragment clampf4 operator{} (clampf3 rgb, clampf1 alpha)
fragment bool operator== (float, float)
fragment bool operator!= (float, float)
fragment bool operator> (float, float)
fragment bool operator< (float, float)
fragment bool operator>= (float, float)
fragment bool operator<= (float, float)
fragment bool operator== (clampf, clampf)
fragment bool operator!= (clampf, clampf)
fragment bool operator> (clampf, clampf)
fragment bool operator< (clampf, clampf)
fragment bool operator>= (clampf, clampf)
fragment bool operator<= (clampf, clampf)
fragment float4 operator blend (float4, float4)
fragment clampf4 operator blend (clampf4, clampf4)
fragment float4 operator over (float4, float4)
fragment clampf4 operator over (clampf4, clampf4)
fragment float4 operator blend_over (float4, float4)
fragment clampf4 operator blend_over (clampf4, clampf4)
surface fragment float1 operator integrate (float1)
surface fragment float3 operator integrate (float3)
surface fragment float4 operator integrate (float4)
surface fragment clampf1 operator integrate (clampf1)
surface fragment clampf3 operator integrate (clampf3)
surface fragment clampf4 operator integrate (clampf4)
vertex bool operator () (bool)
fragment float operator () (float)
fragment float3 operator () (float3)
fragment float4 operator () (float4)
fragment clampf operator () (clampf)
fragment clampf3 operator () (clampf3)
fragment clampf4 operator () (clampf4)
perbegin matrix3 operator () (matrix4)
perbegin matrix4 operator () (matrix4)
perbegin texref operator () (texref)
constant matrix3 identity3 ()
constant matrix4 identity ()
perbegin float cos (float)
perbegin float sin (float)
perbegin float3 cross (float3, float3)
perbegin matrix3 affine (matrix4)
perbegin matrix3 invert (matrix3)
perbegin matrix3 rotate3 (float angle, float x, float y, float z)
perbegin matrix3 scale3 (float x, float y, float z)
perbegin matrix3 transpose (matrix3)
perbegin matrix4 frustum (float l, float r, float b, float t, float n, float f)
perbegin matrix4 invert (matrix4)
perbegin matrix4 lookat (float ex, float ey, float ez, float cx, float cy,
float cz, float ux, float uy, float uz)
perbegin matrix4 ortho (float l, float r, float b, float t, float n, float f)
perbegin matrix4 rotate (float angle, float x, float y, float z)
perbegin matrix4 scale (float x, float y, float z)
perbegin matrix4 translate (float x, float y, float z)
perbegin matrix4 transpose (matrix4)
vertex float clamp (float val, float lo, float hi)
vertex float3 clamp (float3 val, float lo, float hi)
vertex float3 clamp (float3 val, float3 lo, float3 hi)
vertex float4 clamp (float4 val, float lo, float hi)
vertex float4 clamp (float4 val, float4 lo, float4 hi)
vertex float dot (float4, float4)
vertex float length (float3)
vertex float length (float4)
vertex float max (float, float)
vertex float3 max (float3, float3)
vertex float4 max (float4, float4)
vertex float min (float, float)
vertex float3 min (float3, float3)
vertex float4 min (float4, float4)
vertex float3 normalize (float3)
vertex float4 normalize (float4)
vertex float pow (float val, float exp)
vertex float3 reflect (float3 vec, float3 norm)
vertex float sqrt (float)
vertex float ceil (float)
vertex float floor (float)
vertex float mod (float, float)
vertex float trunc (float)
fragment float dot (float3, float3)
fragment float1 select (bool, float1, float1)
fragment float3 select (bool, float3, float3)
fragment float4 select (bool, float4, float4)
fragment clampf1 select (bool, clampf1, clampf1)
fragment clampf3 select (bool, clampf3, clampf3)
fragment clampf4 select (bool, clampf4, clampf4)
fragment float3 rgb (float1)
fragment float3 rgb (float4)
fragment clampf3 rgb (clampf1)
fragment clampf3 rgb (clampf4)
fragment float1 blue (float3)
fragment float1 blue (float4)
fragment clampf1 blue (clampf3)
fragment clampf1 blue (clampf4)
fragment float1 alpha (float4)
fragment clampf1 alpha (clampf4)
fragment bool lthalf (float1)
fragment bool lthalf (clampf1)
fragment:fragment clampf4 lut (fragment clampf4)
fragment:fragment clampf4 texture (texref tex, constant:vertex float3 coord)
fragment:fragment clampf4 texture (texref tex, constant:vertex float4 coord)
fragment:fragment clampf4 texture3d (texref tex, constant:vertex float3 coord)
fragment:fragment clampf4 texture3d (texref tex, constant:vertex float4 coord)
fragment:fragment clampf4 cubemap (texref ref, constant:vertex float3 coord)
fragment:fragment clampf4 cubemap (texref ref, constant:vertex float4 coord)
fragment:fragment clampf3 cubenorm (constant:vertex float3 vec)
fragment:fragment clampf4 bumpdiff (texref ref, constant:vertex float4 coord,
constant:vertex float3 Ltan)
fragment:fragment clampf4 bumpspec (texref ref, constant:vertex float4 coord,
constant:vertex float3 Htan)
Not all operations are supported by all hardware at all computation
frequencies. The compiler is allowed to generate an error when an
unsupported operation is used. The section regarding conditional
compilation enumerates the most important sets of operators that fall into
this category.
Grammar
-------
The following grammar describes the overall organization of the language.
PROGRAM : DECL_LISTopt
DECL_LIST : DECL_LISTopt DECL
DECL : TYPE IDENT ;
| TYPE IDENT = EXPR ;
| TYPE IDENT ( PARAM_LISTopt ) { STMT_LIST }
TYPE : MOD_LISTopt BASE_TYPE
MOD_LIST : MOD_LISTopt MOD
MOD : constant | primitive group | vertex | fragment | light | surface |
shader | perlight | perbegin
BASE_TYPE : bool | clampf | clampf1 | clampf3 | clampf4 | clampfv |
float | float1 | float3 | float4 | floatv | matrix3 | matrix4 |
matrix | texref
PARAM_LIST : PARAM
| PARAM_LIST ',' PARAM
PARAM : TYPE IDENT
STMT_LIST : STMT_LISTopt STMT
STMT : TYPE IDENT ;
| TYPE IDENT = EXPR ;
| EXPR ;
| return EXPR ;
| ;
EXPR : UNARY = EXPR
| EXPR BINOP EXPR
| UNARY
BINOP : == | != | > | < | >= | <= | + | - | blend | over | blend_over | * | /
UNARY : - UNARY
| ( TYPE ) UNARY
| PRIMARY
PRIMARY : ( EXPR )
| { EXPR_LIST }
| IDENT
| PRIMARY [ INTEGER ]
| integrate ( EXPR )
| IDENT ( EXPR_LISTopt )
| INTEGER
| FLOAT
EXPR_LIST : EXPR
| EXPR_LIST , EXPR
The following non-terminals are described by regular expressions:
IDENT : [_a-zA-Z][_a-zA-Z0-9]*
INTEGER : [0-9]+
FLOAT : (([0-9]+(\.[0-9]*)?)|(\.[0-9]+))([eE][-+]?[0-9]+)?f?
Sample shaders
--------------
The following example shaders serve to illustrate how the shading language
might be used to implement a number of interesting shading effects.
// Useful constants
constant float4 Zero = { 0, 0, 0, 0 };
constant float4 Black = { 0, 0, 0, 1 };
constant float4 White = { 1, 1, 1, 1 };
constant float pi = 3.14159;
// Light shaders
light float
atten (float ac, float al, float aq)
{
return 1.0 / ((aq * Sdist + al) * Sdist + ac);
}
light shader float4
simple_light (float4 color, float ac, float al, float aq)
{
return color * atten(ac, al, aq);
}
float
smoothstep (float value, float min, float max)
{
float t = clamp((value - min) / (max - min), 0, 1);
return t * t * (3 - 2 * t);
}
float
smoothspot (float spot_cos, float inner_edge_angle, float outer_edge_angle)
{
float inner_cos = cos(inner_edge_angle * pi / 180);
float outer_cos = cos(outer_edge_angle * pi / 180);
return smoothstep(spot_cos, outer_cos, inner_cos);
}
light shader float4
spotlight (float4 color, float ac, float al, float aq)
{
float4 Cl = smoothspot(-S[2], 15, 30) * color * atten(ac, al, aq);
return Cl;
}
light float4
star_projector_f (float4 color, float ac, float al, float aq, texref stars,
float time)
{
float4 Cl = smoothspot(-S[2], 15, 30) * color * atten(ac, al, aq);
float4 uv = { S[0], S[1], 0, -S[2] }; // project
matrix4 t_rot = rotate(time * 15, 0, 0, 1);
return Cl * texture(stars, t_rot * scale(1.5, 1.5, 1) * uv);
}
light shader float4
star_projector (float4 color, float ac, float al, float aq, texref stars)
{
return star_projector_f(color, ac, al, aq, stars, 0);
}
light shader float4
star_projector_anim (float4 color, float ac, float al, float aq, texref stars,
float time)
{
return star_projector_f(color, ac, al, aq, stars, time);
}
// Reflection models
surface float4
lightmodel (float4 a, float4 d, float4 s, float4 e, float sh)
{
perlight float diffuse = dot(N,L);
perlight float specular = pow(max(dot(N,H),0),sh);
perlight float4 fr = select(diffuse > 0, d * diffuse + s * specular, Zero);
return a * Ca + integrate(fr * Cl) + e;
}
surface float4
lightmodel_diffuse (float4 a, float4 d)
{
perlight float diffuse = dot(N,L);
perlight float4 fr = select(diffuse > 0, d * diffuse, Zero);
return a * Ca + integrate(fr * Cl);
}
surface float4
lightmodel_specular (float4 s, float4 e, float sh)
{
perlight float diffuse = dot(N,L);
perlight float specular = pow(max(dot(N,H),0),sh);
perlight float4 fr = select(diffuse > 0, s * specular, Zero);
return integrate(fr * Cl) + e;
}
surface float4
lightmodel_anisotropic_u (float4 a, float4 d, float4 s, float4 e, float sh)
{
float EdotT = dot(E,T);
perlight float LdotT = dot(L,T);
perlight float diff = sqrt(1 - LdotT * LdotT);
perlight float spec = max(diff * sqrt(1 - EdotT*EdotT) - LdotT*EdotT, 0);
perlight float4 fr = max(dot(N,L),0) * (d * diff + s * pow(spec,sh));
return a * Ca + integrate(fr * Cl) + e;
}
surface float4
lightmodel_anisotropic_v (float4 a, float4 d, float4 s, float4 e, float sh)
{
float EdotB = dot(E,B);
perlight float LdotB = dot(L,B);
perlight float diff = sqrt(1 - LdotB*LdotB);
perlight float spec = max(diff * sqrt(1 - EdotB*EdotB) - LdotB*EdotB, 0);
perlight float4 fr = max(dot(N,L),0) * (d * diff + s * pow(spec,sh));
return a * Ca + integrate(fr * Cl) + e;
}
float center (float value) { return 0.5 * value + 0.5; }
surface float4
lightmodel_textured_anisotropic_u (texref anisotex, float4 a, float4 e)
{
perlight float4 uv = { center(dot(T,E)), center(dot(T,L)), 0, 1 };
// moving Cl helps group vertex/fragment computations
//perlight float4 fr = max(dot(N,L),0) * texture(anisotex, uv);
//return a * Ca + integrate(Cl * fr) + e;
perlight float4 clfr = Cl * max(dot(N,L),0) * texture(anisotex, uv);
return a * Ca + integrate(clfr) + e;
}
surface float4
lightmodel_textured_anisotropic_v (texref anisotex, float4 a, float4 e)
{
perlight float4 uv = { center(dot(B,E)), center(dot(B,L)), 0, 1 };
// moving Cl helps group vertex/fragment computations
//perlight float4 fr = max(dot(N,L),0) * texture(anisotex, uv);
//return a * Ca + integrate(Cl * fr) + e;
perlight float4 clfr = Cl * max(dot(N,L),0) * texture(anisotex, uv);
return a * Ca + integrate(clfr) + e;
}
surface float4
lightmodel_cartoon (texref cartoon, float4 a, float4 d)
{
perlight float fr = max(dot(N,L),0);
// clamp upper end to avoid texture border color
float4 uv = { min(integrate(fr) + 0.2, 0.75), 0, 0, 1 };
return a * Ca + d * texture(cartoon, uv);
}
// Standard material properties
constant float4 Ma = { 0.35, 0.35, 0.35, 1.00 };
constant float4 Md = { 0.50, 0.50, 0.50, 1.00 };
constant float4 Ms = { 1.00, 1.00, 1.00, 1.00 };
constant float4 Me = { 0.00, 0.00, 0.00, 0.00 };
constant float Msh = 300;
surface shader float4
default ()
{
return lightmodel(Ma, Md, Ms, Me, Msh);
}
surface shader float4
cartoontest (texref cartoon)
{
return lightmodel_cartoon(cartoon, {.4, .4, .8, 1}, {.4, .4, .8, 1});
}
surface shader float4
bowling_pin (texref pinbase, texref bruns, texref circle, texref coated,
texref marks, float4 uv)
{
float4 uv_wrap = { uv[0], 10 * Pobj[1], 0, 1 };
float4 uv_label = { 10 * Pobj[0], 10 * Pobj[1], 0, 1 };
matrix4 t_base = invert(translate(0, -7.5, 0) * scale(0.667, 15, 1));
matrix4 t_bruns = invert(translate(-2.6, -2.8, 0) * scale(5.2, 5.2, 1));
matrix4 t_circle = invert(translate(-0.8, -1.15, 0) * scale(1.4, 1.4, 1));
matrix4 t_coated = invert(translate(2.6, -2.8, 0) * scale(-5.2, 5.2, 1));
matrix4 t_marks = invert(translate(2.0, 7.5, 0) * scale (4, -15, 1));
float front = select(Pobj[2] >= 0, 1, 0);
float back = select(Pobj[2] <= 0, 1, 0);
float4 Base = texture(pinbase, t_base * uv_wrap);
float4 Bruns = front * texture(bruns, t_bruns * uv_label);
float4 Circle = front * texture(circle, t_circle * uv_label);
float4 Coated = back * texture(coated, t_coated * uv_label);
float4 Marks = texture(marks, t_marks * uv_wrap);
float4 Cd = lightmodel_diffuse({ 0.4, 0.4, 0.4, 1 }, { 0.5, 0.5, 0.5, 1 });
float4 Cs = lightmodel_specular({ 0.35, 0.35, 0.35, 1 }, Zero, 20);
return (Circle over (Bruns over (Coated over Base))) * (Marks * Cd) + Cs;
}
surface shader float4
glossy_moons (texref gloss, float4 uv)
{
float4 base_a = { 0.1, 0.1, 0.1, 1.00 };
float4 base_d = { 0.70, 0.40, 0.10, 1.00 };
float4 base_s = { 0.07, 0.04, 0.01, 1.00 };
float4 base_e = { 0.00, 0.00, 0.00, 1.00 };
float base_sh = 15;
float4 gloss_a = { 0.07, 0.04, 0.01, 1.00 };
float4 gloss_d = { 0.07, 0.04, 0.01, 1.00 };
float4 gloss_s = { 1.00, 0.90, 0.60, 1.00 };
float4 gloss_e = { 0.00, 0.00, 0.00, 1.00 };
float gloss_sh = 25;
float4 Cbase = lightmodel(base_a, base_d, base_s, base_e, base_sh);
float4 Cgloss = lightmodel(gloss_a, gloss_d, gloss_s, gloss_e, gloss_sh);
float4 uv_gloss = invert(scale(.335,.335,1)) * uv;
return Cbase + Cgloss * texture(gloss, uv_gloss);
}
surface shader float4
anisotropic_ball_vertex (texref star)
{
float4 Ma = { 0.1, 0.1, 0.1, 1.0 };
float4 Md = { 0.3, 0.3, 0.3, 1.0 };
float4 Ms = { 0.7, 0.7, 0.7, 1.0 };
float4 Me = { 0.0, 0.0, 0.0, 0.0 };
float Msh = 15;
float4 base = texture(star, { center(Pobj[2]), center(Pobj[0]), 0, 1 });
return base * lightmodel_anisotropic_v(Ma, Md, Ms, Me, Msh);
}
surface shader float4
anisotropic_ball_texture (texref star, texref anisotex)
{
float4 Ma = { 0.1, 0.1, 0.1, 1.0 };
float4 Me = { 0.0, 0.0, 0.0, 0.0 };
float4 base = texture(star, { center(Pobj[2]), center(Pobj[0]), 0, 1 });
return base * lightmodel_textured_anisotropic_v(anisotex, Ma, Me);
}
surface float4
spheremap (texref env)
{
float3 R = normalize(reflect(E,N) + { 0, 0, 1 });
float4 uv = { center(R[0]), center(R[1]), 0, 1 };
return texture(env, uv);
}
surface shader float4
sphere_map_env (texref env)
{
return spheremap(env);
}
surface shader float4
poolball (texref one, float4 uv)
{
float4 Ma = { 0.35, 0.35, 0.35, 1.00 };
float4 Md = { 0.50, 0.50, 0.50, 1.00 };
float4 Ms = { 1.00, 1.00, 1.00, 1.00 };
float4 Me = { 0.00, 0.00, 0.00, 1.00 };
float Msh = 127;
float4 Cd = lightmodel_diffuse(Ma, Md);
float4 Cs = lightmodel_specular(Ms, Me, Msh);
matrix4 tm = invert(translate(0.35, 0.2, 0.0) * scale(0.3, 0.6, 1.0));
return Cd * texture(one, tm * uv) + Cs;
}
surface shader float4
poolball_with_env (texref one, texref env, float4 uv)
{
float4 Ma = { 0.35, 0.35, 0.35, 1.00 };
float4 Md = { 0.50, 0.50, 0.50, 1.00 };
float4 Ms = { 1.00, 1.00, 1.00, 1.00 };
float4 Me = { 0.00, 0.00, 0.00, 1.00 };
float Msh = 127;
float4 Cd = lightmodel_diffuse(Ma, Md);
float4 Cs = lightmodel_specular(Ms, Me, Msh);
matrix4 tm = invert(translate(0.35, 0.2, 0.0) * scale(0.3, 0.6, 1.0));
return Cd * texture(one, tm * uv) + (Cs + spheremap(env));
}
float4
turb (texref noise, float4 uv)
{
float4 uv_0 = invert(rotate(30.2, 0, 0, 1) * scale(4, 4, 1)) * uv;
float4 uv_1 = invert(rotate(-35.5, 0, 0, 1) * scale(2, 2, 1)) * uv;
float4 uv_2 = invert(rotate(274.1, 0, 0, 1) * scale(1, 1, 1)) * uv;
float4 N_0 = 0.57 * texture(noise, uv_0);
float4 N_1 = 0.29 * texture(noise, uv_1);
float4 N_2 = 0.14 * texture(noise, uv_2);
return N_0 + N_1 + N_2;
}
surface shader float4
noise_2d_multipass (texref noise, float4 uv)
{
return turb(noise, uv);
}
surface shader float4
noise_2d_multipass_specular_modulate (texref noise, float4 uv)
{
float4 Cl = lightmodel(Ma, Md, Ms, Me, Msh);
return Cl * turb(noise, uv);
}
surface shader float4
noise_2d_multipass_specular_separate (texref noise, float4 uv)
{
float4 Cd = lightmodel_diffuse(Ma, Md);
float4 Cs = lightmodel_specular(Ms, Me, Msh);
return Cd * turb(noise, uv) + Cs;
}
float4
skymap (texref clouds, float4 dir, float time)
{
dir = normalize(dir);
dir = { dir[0], dir[1], 4 * (dir[2] + 0.707), 0 };
dir = normalize(dir);
float4 uv_lo = dir * { 2, 2, 0, 0 } + { time / 15 , time / 15, 0, 1 };
float4 uv_hi = dir * { 3, 3, 0, 0 } + { time / 15 , time / 15, 0, 1 };
float4 Lo = texture(clouds, uv_lo);
float4 Hi = texture(clouds, rotate(125, 0, 0, 1) * uv_hi);
// for now, do not use Lo over (Hi over { 0.6, 0.5, 1.0, 1.0 })
// texture_env_combine does not do over correctly
return Lo over Hi over { 0.6, 0.5, 1.0, 1.0 };
}
surface shader float4
quake_sky (texref clouds, float time)
{
return skymap(clouds, { Pobj[0], -Pobj[2], Pobj[1], 0 }, time);
}
surface shader float4
bowling_pin_with_sky (texref pinbase, texref bruns, texref circle,
texref coated, texref marks, float4 uv,
texref clouds, float time)
{
float4 uv_wrap = { uv[0], 10 * Pobj[1], 0, 1 };
float4 uv_label = { 10 * Pobj[0], 10 * Pobj[1], 0, 1 };
matrix4 t_base = invert(translate(0, -7.5, 0) * scale(0.667, 15, 1));
matrix4 t_bruns = invert(translate(-2.6, -2.8, 0) * scale(5.2, 5.2, 1));
matrix4 t_circle = invert(translate(-0.8, -1.15, 0) * scale(1.4, 1.4, 1));
matrix4 t_coated = invert(translate(2.6, -2.8, 0) * scale(-5.2, 5.2, 1));
matrix4 t_marks = invert(translate(2.0, 7.5, 0) * scale (4, -15, 1));
float front = select(Pobj[2] >= 0, 1, 0);
float back = select(Pobj[2] <= 0, 1, 0);
float4 Base = texture(pinbase, t_base * uv_wrap);
float4 Bruns = front * texture(bruns, t_bruns * uv_label);
float4 Circle = front * texture(circle, t_circle * uv_label);
float4 Coated = back * texture(coated, t_coated * uv_label);
float4 Marks = texture(marks, t_marks * uv_wrap);
float Lscale = 0.5;
float4 Cd = lightmodel_diffuse({ 0.4, 0.4, 0.4, 1 }, { 0.5, 0.5, 0.5, 1 });
Cd = Cd * Lscale;
float4 Cs = lightmodel_specular({ 0.35, 0.35, 0.35, 1 }, Zero, 20);
Cs = Cs * Lscale;
float3 R = reflect(E,N);
return (Circle over (Bruns over (Coated over Base))) * (Marks * Cd) + Cs +
0.5 * skymap(clouds, { R[0], -R[2], R[1], 0 }, time);
}
#ifdef HAVE_BUMPOPS
surface shader float4
bowling_pin_bump (texref pinbase, texref bruns, texref circle, texref coated,
texref marks, texref marksbump, float4 uv)
{
float4 uv_wrap = { uv[0], 10 * Pobj[1], 0, 1 };
float4 uv_label = { 10 * Pobj[0], 10 * Pobj[1], 0, 1 };
matrix4 t_base = invert(translate(0, -7.5, 0) * scale(0.667, 15, 1));
matrix4 t_bruns = invert(translate(-2.6, -2.8, 0) * scale(5.2, 5.2, 1));
matrix4 t_circle = invert(translate(-0.8, -1.15, 0) * scale(1.4, 1.4, 1));
matrix4 t_coated = invert(translate(2.6, -2.8, 0) * scale(-5.2, 5.2, 1));
matrix4 t_marks = invert(translate(2.0, 7.5, 0) * scale (4, -15, 1));
float front = select(Pobj[2] >= 0, 1, 0);
float back = select(Pobj[2] <= 0, 1, 0);
float4 Base = texture(pinbase, t_base * uv_wrap);
float4 Bruns = front * texture(bruns, t_bruns * uv_label);
float4 Circle = front * texture(circle, t_circle * uv_label);
float4 Coated = back * texture(coated, t_coated * uv_label);
float4 uv_marks = t_marks * uv_wrap;
float4 Marks = texture(marks, uv_marks);
perlight float3 Lt = { dot(T,L), dot(B,L), dot(N,L) };
perlight float3 Ht = { dot(T,H), dot(B,H), dot(N,H) };
float4 Ma = {.4,.4,.4,1};
float4 Md = {.5,.5,.5,1};
float4 Ms = {.3,.3,.3,1};
float4 Kd = (Circle over (Bruns over (Coated over Base))) * Marks;
return Kd * Ma +
integrate(Cl * (Kd * Md * bumpdiff(marksbump, uv_marks, Lt)
blend(ONE,SRC_ALPHA)
Ms * bumpspec(marksbump, uv_marks, Ht)));
}
#endif /* HAVE_BUMPOPS */
#ifdef HAVE_CUBEMAP
surface shader float4
cube_from_obj_normal (texref cube) {
return cubemap(cube, {-1,-1,1}*__normal);
}
surface shader float4
poolball_with_cube (texref one, float4 uv, texref cube)
{
float4 Ma = .5 * { 0.35, 0.35, 0.35, 1.00 };
float4 Md = .5 * { 0.50, 0.50, 0.50, 1.00 };
float4 Ms = .5 * { 1.00, 1.00, 1.00, 1.00 };
float4 Me = .5 * { 0.00, 0.00, 0.00, 1.00 };
float Msh = 127;
float4 Cd = lightmodel_diffuse(Ma, Md);
float4 Cs = lightmodel_specular(Ms, Me, Msh);
matrix4 tm = invert(translate(0.35, 0.2, 0.0) * scale(0.3, 0.6, 1.0));
float3 R = reflect(E,N);
return Cd * texture(one, tm * uv) + Cs + 0.4 * cubemap(cube, {-1,-1,1}*R);
}
#endif /* HAVE_CUBEMAP */