SL is probably best describe'd as a combination of Renderman's globally-aware shading language, and the easily hardware-implementable DirectX 8 pixel shading language. SL shaders are run inside of a virtual machine, and with the exception of two global-illumination knowledgeable operations, there is little to distinguish an SL VM from a restricted SIMD processor. There are a total of 38 registers available, of which 26 store vector data, and 12 store scalar data. All registers are 32-bit floatint point format. Below is a description of SL's interface with LRT, all the registers, available access modes, opcodes, and several shader examples.
SL takes a very straight-forward approach to interfacing with LRT: it replaces the BRDF and material classes. When defining a surface, instead of using one of LRT's built-in strings, you provide the name of a shader (sans .slc extension) located in your LRT_SHADER directory. Then, after an intersection with a shader-enabled surface, all the geometry-based registers (as listed below) are initialized to appropriate values. Any subsequent calls to the returned BRDF's fr() method will execute the shading program -- any variable values (e.g., incident light direction) will be modified prior to executing the program. This allows SL shaders to run with very few changes to the provided integrator classes. Currently, only the WhittedIntegrator class has been modified to be SL-aware; however, modifying the other integrators to support SL should be straight-forward.
Of the total registers in an SL machine, only 10 are available for
read/write access (5 vector, 5 scalar). The other 28 registers are
read-only, and initialized by LRT prior to executing your program.
Below is a list of all registers (case-sensitive):
C0 | Primary Color | Surface color attribute | vector | No |
C1 | Secondary Color | Specular color attribute | vector | No |
L | Light | Object-space light vector | vector | No |
V | View | Object-space view direction (Wo) | vector | No |
Pw | Point | World-space intersection point | vector | No |
xy | Point | object-space intersection point | vector | No |
ds* | Surface derivative | Object-space derivative of surface with respect to S texture coord | vector | No |
dt* | Surface derivative | Object-space derivative of surface with respect to T texture coord | vector | No |
du* | Surface derivative | Object-space derivative of surface with respect to U parameter | vector | No |
dv* | Surface derivative | Object-space derivative of surface with respect to V parameter | vector | No |
uv | Surface parameters | Surface parameters of intersection point | vector | No |
N | Surface normal | Object-space surface normal at intersection point | vector | No |
dE | Incident light | Light intensity (0 for shadowed sources) for this light direction | vector | No |
t0 - t7 | Texture samples | Texture samples for current (s,t) | vector | No |
r0 - r3 | General purpose | See below | vector | Yes |
v0 | Reflected color | (0,0,0) | vector | Yes |
s0 - s3 | General purpose | See below | scalar | Yes |
v1 | Scalar return value | 0 | vector | Yes |
Kd | Diffuse reflectance coefficient | Surface's Kd,or 0.5 | scalar | No |
Ks | Specular reflectance coefficient | Surface's Ks,or 0.5 | scalar | No |
S | Specular roughness | 8./roughness, or 80 | scalar | No |
Kr | Reflection coefficient | Surface's Kr,or 0 | scalar | No |
Kt | Transmission coefficient | Surface's Kt,or 0 | scalar | No |
Ka | Ambient reflectance coefficient | Surface's Ka,or 0 | scalar | No |
I | Index of refraction | old index / new index | scalar | No |
I have added new tokens to the LRT parser to enable initialization of the texture and general purpose SL registers. Now, when you create a surface with the Surface "<name>" "Kd" <float> ... attribute, the tokens "t0" through "t7", "r0" through "r3", and "s0" through "s3" are recognized.
If "t1" is associated with a texture generated by a call to MakeBump, it will be initialized with the perturbed normal vector, not the texel value
There are 24 opcodes defined for SL virtual machines, which allow shaders to emulate a significant portion of the shaders supported by RenderMan's shading language, and a superset of the shaders supported by Microsoft's DirectX8 pixel shader language. Syntax is similar to MIPS assembly language, with each operation having (at most) 3 operands: 2 source, and 1 destination. These opcodes are converted into 64-bit bytecodes by the assembler, for use in LRT.
nop | Nothing | |||
ret | Return immediately | |||
rnd | scal | dst = random value | ||
turb | scal | scal | dst = 1-D noise | |
turb | scal | vec | dst = 3-D noise | |
mov | vec | vec | dest[x,y,z] = src[x,y,z] | |
mov | scal | scal | dest = src | |
movc * | scal | vec | dest = src[*] | |
lc * | vec | scal | dest[*] = src | |
li | scal | float | dest = val | |
liv * | vec | float | dest[*] = val | |
add | vec | vec | vec | dest = src0+src1 |
add | scal | scal | scal | dest = src0+src1 |
sub | vec | vec | vec | dest = src0-src1 |
sub | scal | scal | scal | dest = src0-src1 |
mul | vec | vec | vec | dest = cross(src0, src1) |
mul | vec | vec | scal | dest = src0 * src1 |
mul | scal | scal | scal | dest = src0 * src1 |
dp3 | scal | vec | vec | dest = dot(src0, src1) |
div | vec | vec | scal | dest = src0 / src1 |
div | scal | scal | scal | dest = src0 / src1 |
clamp | scal | scal | scal | dest = (dest<src0)?src0: (dest>src1)?src1:dest |
floor | scal | scal | dest=floor(src) | |
ceil | scal | scal | dest=ceil(src) | |
norm | vec | vec | dest = src / |src| | |
pow | scal | scal | scal | dest = src0src1 |
exp | scal | scal |   | dest = esrc |
jmp | short | Jump to rel. address** | ||
blt | scal | scal | short | Jump to rel. address if src0<src1** |
trace | vec | vec | vec | dest receives
value from a ray traced from world-space point src0, object-space direction src1 |
lookup | vec | tN | vec | dest receives
texels from texture N, coords src1 |
Shader source files (.sl) begin with an identification mnemonic (sl 1.0), followed by 1 or more instructions, 1 per line. Comments can be included in source files by putting a hash ('#') character at the start of a comment line. Below are some extremely simple example shaders.
sl 1.0
# constant.sl - super simple shader. returns the un-attenuated
# surface color
mov v0, C0
sl 1.0
# CosAttenuated.sl - assumes all lights intensities to be (1, 1, 1)
dp3 s0, N, L
mul v0, C0, s0
sl 1.0
# FullAttenuated.sl - performs dE * Color * (Dot(N,H)^s*Ks + Dot(N,L)*Kd)
dp3 s0, N, L
mul s0, s0, Kd
add r0, V, L
norm r0, r0
dp3 s1, r0, N
pow s1, s1, S
mul s1, s1, Ks
add s0, s0, s1
mul r0, C0, s0
#now perform component-wise multiply
movc r s0, r0
movc r s1, dE
mul s0, s0, s1
lc r r0, s0
movc g s0, r0
movc g s1, dE
mul s0, s0, s1
lc g r0, s0
movc b s0, r0
movc r s1, dE
mul s0, s0, s1
lc b r0, s0
#and return the result
mov v0, r0
sl 1.0
# BumpMapping.sl - simple diffuse bump mapping example
# first, rotate light into point's tangent space
dp3 s3, L, ds
dp3 s2, L, dt
dp3 s1, L, N
lc x r1, s3
lc y r1, s2
lc z r1, s1
norm r1, r1
# and dot this by the normal contained in the bump-map
dp3 s0, r1, t1
# and use this value to modulate a color provided by another texture
mul v0, t0, s0
The provided assembler for the shading language is extremely particular about whitespaces. Any number of lines may be skipped; however, in between a component operator (i.e., lc, movc, liv) and the component specifier (i.e., x, y, z, w), there must be only one space. Also, a space must be placed between all operands (so "mul x,y,z" is not legal, but "mul x, y, z" is).