Now let's make some experiments with mental ray. For light shaders please read chapter 3.13 of the book [5], for material shaders please read chapter 3.8 of the same book. Here is a simple scene similar to the RenderMan example in mental ray's MI file format:
verbose off
link "base.so"
$include <base.mi>
link "contour.so"
$include <contour.mi>
link "physics.so"
$include <physics.mi>
options "opt"
samples -1 2
contrast 0.1 0.1 0.1
object space
end options
camera "Camera"
frame 1
output "rgb" "test.rgb"
focal 12.0
aperture 32.0
aspect 1.33333333333
resolution 508 380
end camera
instance "Camera_inst" "Camera"
transform
1.0 0.0 0.0 0.0
0.0 1.0 0.0 0.0
0.0 0.0 1.0 0.0
0.0 0.0 0.0 1.0
end instance
light "Lamp"
"mib_light_point" (
"color" 1.0 1.0 1.0,
"shadow" false,
"factor" 1,
"atten" false,
"start" 0,
"stop" 1
)
origin 0.0 0.0 0.0
end light
instance "Lamp_inst" "Lamp"
transform
1.0 0.0 0.0 0.0
0.0 1.0 0.0 0.0
0.0 0.0 1.0 0.0
0.0 0.0 0.0 1.0
end instance
material "mtl"
opaque
"mib_illum_lambert" (
"ambient" 0.0 0.0 0.0,
"diffuse" 0.8 0.8 0.8,
"ambience" 0.0 0.0 0.0,
"mode" 1,
"lights" [ "Lamp_inst" ]
)
end material
object "Plane"
visible trace shadow
tag 1
group
1.0 1.0 0.0
1.0 -1.0 0.0
-1.0 -1.0 0.0
-1.0 1.0 0.0
v 0
v 1
v 2
v 3
p "mtl" 0 3 2 1
end group
end object
instance "Plane_inst" "Plane"
transform
1.0 0.0 0.0 0.0
0.0 1.0 0.0 0.0
0.0 0.0 1.0 0.0
0.0 0.0 1.0 1.0
end instance
instgroup "rootgrp"
"Camera_inst"
"Lamp_inst"
"Plane_inst"
end instgroup
render "rootgrp" "Camera_inst" "opt"
You should check that this MI file renders fine with your local mental
ray installation before you proceed. Here is a Makefile which
can be used to render the scene by simply typing make in your
Unix shell. Later we will add commands to compile the shaders etc.
but for now the Makefile looks like this:
# Makefile
MENTALRAY = /usr/local/mentalray
RENDER = $(MENTALRAY)/linux-x86/bin/ray
INCLUDE = $(MENTALRAY)/common/include
LDPATH = $(MENTALRAY)/linux-x86/shaders
EXT = so
all: test.rgb
test.rgb: test.mi
$(RENDER) -include_path $(INCLUDE) -ld_path $(LDPATH) test.mi
clean:
-rm -f *~ *.$(EXT) test.rgb
Let's change the light shader first. This way we are sure that the material shader does call the light shader and later we can replace the material shader as well. We don't need shadows at the moment. The light shader looks like this:
/* myLight.c */
#include <shader.h>
struct myLight
{
miColor color; /* color of the light source */
};
DLLEXPORT miBoolean
myLight(
register miColor* result,
register miState* state,
register struct myLight *paras
)
{
*result = *mi_eval_color(¶s->color);
return(miTRUE);
}
To compile the shader I changed the Makefile:
# Makefile
MENTALRAY = /usr/local/mentalray
RENDER = $(MENTALRAY)/linux-x86/bin/ray
INCLUDE = $(MENTALRAY)/common/include
LDPATH = $(MENTALRAY)/linux-x86/shaders
OBJEXT = o
SOEXT = so
CC = gcc
LINK = ld
DEFINES = -DLINUX -DLINUX_X86 -DX86 -DEVIL_ENDIAN -D_GNU_SOURCE \
-D_REENTRANT -DSYSV -DSVR4 -Dinline=__inline__
CFLAGS = -ansi -fPIC -O3 -mpentiumpro -fexpensive-optimizations \
-finline-functions -funroll-loops -fomit-frame-pointer -frerun-cse-after-loop \
-fstrength-reduce -fforce-mem -fforce-addr $(DEFINES)
INCPATH = -I. -I$(INCLUDE)
all: test.rgb
myLight.$(OBJEXT): myLight.mi myLight.c
$(CC) -c $(CFLAGS) $(INCPATH) -o myLight.$(OBJEXT) myLight.c
myLight.$(SOEXT): myLight.$(OBJEXT)
$(LINK) -shared -export-dynamic -o myLight.$(SOEXT) myLight.$(OBJEXT)
test.rgb: test.mi myLight.$(SOEXT)
$(RENDER) -include_path $(INCLUDE) -ld_path $(LDPATH) test.mi
clean:
-rm -f *~ *.$(SOEXT) *.$(OBJEXT) test.rgb
One thing I forgot to mention is that you need a header file for your
shader as well. In this case I named it myLight.mi and it looks
(so far) like this:
# myLight.mi
declare shader
color "myLight" (color "color")
version 1
apply light
end declare
We are ready to use the light shader now. So please edit the original MI file and add respectively change the following lines:
...
link "myLight.so"
$include <myLight.mi>
...
light "Lamp"
"myLight" (
"color" 1.0 1.0 1.0
)
origin 0.0 0.0 0.0
end light
...
Compile the shader with make myLight.so and copy (or softlink)
the files myLight.so and myLight.mi to a place where
mental ray can find it before you render. For my local installation
the compiled files go to /usr/local/mentalray/linux-x86/shaders
and the header files can be found in
/usr/local/mentalray/common/include. You could also specify the
full path to your files in the MI file.
It's time to change the material shader. We add a few more lines to
the Makefile:
...
mySurface.$(OBJEXT): mySurface.mi mySurface.c
$(CC) -c $(CFLAGS) $(INCPATH) -o mySurface.$(OBJEXT) mySurface.c
mySurface.$(SOEXT): mySurface.$(OBJEXT)
$(LINK) -shared -export-dynamic -o mySurface.$(SOEXT) mySurface.$(OBJEXT)
test.rgb: test.mi myLight.$(SOEXT) mySurface.$(SOEXT)
$(RENDER) -include_path $(INCLUDE) -ld_path $(LDPATH) test.mi
...
The header file for the new material shader looks like this:
# mySurface.mi
declare shader
color "mySurface" (
color "ambient",
color "diffuse",
color "specular",
scalar "spec_exp",
integer "mode",
array light "lights"
)
version 1
apply material
end declare
The material shader takes three colors as parameters for the ambient, diffuse, and specular color contributions. In the RenderMan shader I just set them in the latest version of the shader without giving the user the choice to change the values from outside (in the RIB file). Go back and make the modifications needed, if you want to. The mental ray material shader itself needs a bit of explanation:
/* mySurface.c */
#include <shader.h>
struct mySurface
{
miColor ambient; /* ambient color */
miColor diffuse; /* diffuse color */
miColor specular; /* specular color */
miScalar spec_exp; /* Phong exponent */
int mode; /* light mode: 0..2 */
int i_light; /* index of first light */
int n_light; /* number of lights */
miTag light[1]; /* list of lights */
};
DLLEXPORT miBoolean
mySurface(
miColor* result,
miState* state,
struct mySurface* paras
)
{
int i;
int n_light; /* number of light sources */
int i_light; /* offset of light sources */
int samples;
miColor* diffuse;
miColor* specular;
miColor color;
miColor sum;
miScalar spec_exp;
miScalar dot_nl;
miScalar phong_specular;
miTag* light;
miVector dir;
/* ambient */
*result = *mi_eval_color(¶s->ambient);
diffuse = mi_eval_color(¶s->diffuse);
specular = mi_eval_color(¶s->specular);
spec_exp = *mi_eval_scalar(¶s->spec_exp);
n_light = *mi_eval_integer(¶s->n_light);
i_light = *mi_eval_integer(¶s->i_light);
light = mi_eval_tag(paras->light) + i_light;
for (i = 0; i < n_light; i++)
{
sum.r = sum.g = sum.b = 0;
samples = 0;
while (mi_sample_light(&color, &dir, &dot_nl, state, *light, &samples))
{
/* diffuse */
sum.r += dot_nl * diffuse->r * color.r;
sum.g += dot_nl * diffuse->g * color.g;
sum.b += dot_nl * diffuse->b * color.b;
/* specular */
phong_specular = mi_phong_specular(spec_exp, state, &dir);
if (phong_specular > 0.0)
{
sum.r += phong_specular * specular->r * color.r;
sum.g += phong_specular * specular->g * color.g;
sum.b += phong_specular * specular->b * color.b;
}
} /* while (mi_sample_light(...)) */
if (samples)
{
result->r += sum.r / samples;
result->g += sum.g / samples;
result->b += sum.b / samples;
} /* if (samples) */
}
return(miTRUE);
}
First of all you will notice a difference between the header file and the structure used in the C file. Every parameter translates easily from the description in the header file to the C equivalent. Except the light array which results in three variables in C.
The ambient contribution does not come from a call to evaluate all ambient light sources like in RenderMan but is simply an input parameter. Which means that every material shader which does take ambient contribution into account should have a parameter to let the user set the color etc.
The material shader loops over a light list which comes from the input
parameter as an array of lights. There is no such looping mechanism as
the illuminate statement in RenderMan.
Area lights are taken into account by the loop using
mi_sample_light because that function must be called in a loop
until it returns miFALSE. Every sample of the light is summing
up within the loop. That's why you have to divide by the number of
samples. For lights which are not area lights there should be only one
sample taken. But be careful not to divide by zero.
The result of the call mi_sample_light to the light shader will
be in the variable color. There are two other return values
provided if the pointers are nonzero: dir is the direction from
the current intersection point in the state to the light,
dot_nl is the dot product of this direction and the normal in
the state.
For the specular contribution I took the Phong factor but there are other choices22:
dir, the specular exponent
spec_exp, and the state variables normal and
dir.
ns based on the illumination direction dir, the
specular exponent spec_exp, the inside and outside indices of
refraction ior_in and ior_out, and the state variables
normal and dir.
result according to the Cook-Torrance reflection model for
the incident direction dir_in and the reflection direction
dir_out at a surface with normal normal. The
roughness is the average slope of surface micro-facets.
ior is the relative index of refraction for three
wavelengths.
mi_cooktorr_specular,
but only for one wavelength.
mi_blinn_specular, but implements a hybrid of Blinn and Phong
shading instead of true Blinn shading. It is included separately to
support the Softimage Blinn shading model.
Finally we make some adjustments to the MI file to render a sphere:
...
link "myLight.so"
$include <myLight.mi>
link "mySurface.so"
$include <mySurface.mi>
...
light "Lamp"
"myLight" (
"color" 1.0 1.0 1.0
)
origin 0.0 0.0 0.0
end light
...
material "mtl"
opaque
"mySurface" (
"ambient" 1.0 0.0 0.0,
"diffuse" 0.0 1.0 0.0,
"specular" 0.0 0.0 1.0,
"spec_exp" 40,
"mode" 1,
"lights" ["Lamp_inst"]
)
end material
...
$include "sphere.mi"
instance "sphere_inst" "sphere"
transform
0.2 0.0 0.0 0.0
0.0 0.2 0.0 0.0
0.0 0.0 0.2 0.0
0.0 0.0 2.0 0.2
material "mtl"
end instance
instgroup "rootgrp"
"Camera_inst"
"Lamp_inst"
"sphere_inst"
end instgroup
render "rootgrp" "Camera_inst" "opt"
The MI file sphere.mi comes with the examples of the book
[4] and you can download the examples and all
shaders from mental images' FTP site. I made a softlink to this file
at a location where mental ray can find it. In contrast to
RenderMan and other renderers mental ray does not
support other primitives beside the usual polygons, NURBS surfaces,
and subdivision surfaces. There is one exception as you can see in
figure 4.