home *** CD-ROM | disk | FTP | other *** search
- .\"
- .\" Brief tutorial on adding textures to rayshade.
- .\" Craig Kolb 10/89
- .\"
- .\" $Id: texture.ms,v 3.0.1.2 90/02/12 12:44:41 craig Exp $
- .\"
- .\" $Log: texture.ms,v $
- .\" Revision 3.0.1.2 90/02/12 12:44:41 craig
- .\" patch4: Fixed minor formatting problems.
- .\"
- .\" Revision 3.0.1.1 89/11/27 18:49:26 craig
- .\" patch2: Example texture now uses colormap to scale ambient & diffuse
- .\" patch2: components of surface color.
- .\"
- .\" Revision 3.0 89/10/23 16:42:57 craig
- .\" Baseline for first official release.
- .\"
- .de D(
- .DS
- .nr PS 8
- .ps 8
- .nr VS 12p
- .vs 12p
- .cs 1 20
- ..
- .de D)
- .DE
- .nr PS 10
- .ps 10
- .nr VS 18p
- .vs 18p
- .cs 1
- ..
- .DS
- .ND October, 1989
- .RP
- .de ]H
- .nr PS 10
- .ps 10
- 'sp |0.5i-1
- .tl 'Rayshade Texture Tutorial'-%-'Draft'
- .ps
- .ft
- 'sp |1.0i
- .ns
- ..
- .wh0 ]H
- .pn 2
- .LP
- .TL
- Adding Textures to Rayshade
- .TL
- \fR\fIDraft\fR
- .AU
- Craig Kolb
- .AI
- Department of Mathematics
- Yale University
- New Haven, CT 06520
- .sp .5i
- .nr VS 18pts
- .PP
- This tutorial describes the process of adding new textures to
- rayshade. Although the texturing interface is relatively
- straight-forward, it is difficult to see the "big picture" by
- studying the source code, as changes must be made in a
- number of different files. While this tutorial is primarily
- meant for those interested getting their hands dirty, and
- assumes familiarity with solid texturing,
- it provides a overview of the design of at least one portion
- of rayshade and thus may be of interest even if you are not
- planning on making modifications.
- .LP
- Adding new textures to rayshade involves modifying at least
- four source files:
- .NH
- texture.h
- .LP
- A numerical type is given to the texture. The routines to
- create a reference to the new texture and the texturing function
- itself are declared.
- .NH
- texture.c
- .LP
- At least two new routines are added. The first creates and
- returns a reference to a texture of the given type, and the
- second performs the actual texturing. In addition, an
- array of pointers to functions is modified to include the
- new texturing function.
- .NH
- input_lex.l
- .LP
- The keyword used to identify the new texture is added to
- the list of recognized keywords.
- .NH
- input_yacc.y
- .LP
- The new texture is added to the list of textures parsed by
- the yacc grammar. The added code will call the routine
- which creates and returns a reference to the texture type
- which was defined in texture.c
- .PP
- In this tutorial, a new texture,
- .I mountain,
- is added to rayshade. This
- texture will modify the diffuse component of a surface as a function of the
- Z component of the point of intersection. If the name of a colormap
- is given, an index into
- the colormap is computed and the corresponding color is used to scale
- the ambient and diffuse components of the surface.
- Otherwise, the ambient and diffuse components of the surface are simply scaled.
- To avoid strictly horizontal boundaries
- between colors when using a colormap, we add a bit of "noise" to the
- Z component of the point of intersection. The magnitude and nature of the
- noise
- is defined by the user.
- .br
- .NR PS 12
- .ps 12
- .sp .5
- \fBThe Texture type\fR
- .nr PS 10
- .ps 10
- .sp .5
- .LP
- All textures in rayshade are referenced using a single Texture structure.
- This structure is defined as:
- .D(
- typedef struct Texture {
- char type; /* Texture type */
- Surface *surf1; /* Alternate surface */
- double size; /* Scale/size factor */
- double *args; /* Random arguments. */
- Color *colormap; /* Colormap */
- Trans *trans; /* Transformation matrices. */
- struct Texture *next; /* Pointer to next texture. */
- } Texture;
- .D)
- .LP
- The
- .I type
- field is used by apply_textures() to determine which texturing
- function to call. The
- .I trans
- field and
- .I next
- field are used internally
- by rayshade.
- .LP
- The rest of the fields are for use by the texturing functions.
- .LP
- The
- .I args
- field provides a pointer to space for storing an arbitrary
- number of floating-point parameters. The
- .I size
- field is a handy
- general-purpose floating-point value (the idea being that you get
- one parameter "free"
- with every Texture structure).
- The
- .I colormap
- field is generally used to store an array of Colors read from
- an ascii colormap file.
- The
- .I surf1
- field is often set to point to a secondary surface. This is
- useful if the texture performs some sort of interpolation between two
- surfaces -- the first being the surface assigned to the object being textured
- and the second specified as an argument to the texture
- (e.g., the blotch texture).
- for an example).
- .NH 0
- Modifying texture.h
- .LP
- The file texture.h contains a list of #defines which look something like:
- .D(
- #define CHECKER 0 /* Checkerboard */
- #define BLOTCH 1 /* Color blotches */
- #define BUMP 2 /* Bump mapping */
- #define MARBLE 3 /* marble texture */
- #define FBM 4 /* fBm texture */
- #define FBMBUMP 5 /* fBm bump map */
- #define WOOD 6 /* wood-like texture */
- .D)
- .LP
- These numerical types are used to identify the type of a given
- texture structure (via the
- .I type
- field in the Texture structure).
- We need to add a similar definition for our new texture.
- After the WOOD definition, we add:
- .D(
- #define MOUNTAIN 7 /* bad mountain texture */
- .D)
- .LP
- In addition, we must declare the two new functions which we will
- add to texture.c. The first function, NewMountainText(), will
- return a pointer to a Texture structure:
- .D(
- Texture *NewMountainText();
- .D)
- The second routine, MountainText, returns nothing, but needs to be
- declared:
- .D(
- int MountainText();
- .D)
- .NH
- Modifying texture.c
- .LP
- Firstly, we must include the new texturing function in the array of
- texturing functions used by rayshade. This array, indexed by texture
- type, is used by apply_textures() to call the correct texturing function.
- .LP
- So, we modify the textures[] definition to read:
- .D(
- int (*textures[])() =
- {CheckerText, BlotchText, BumpText, MarbleText, fBmText, fBmBumpText,
- WoodText, MountainText};
- .D)
- .LP
- Note that MOUNTAIN was defined to be 7, and that MountainText is texture
- number 7 (starting from 0) in the array.
- .LP
- Next, we need to write NewMountainText(), which will create and return a
- reference to the texture. Our new texture will be a function of 5 parameters:
- .D(
- scale amount to scale \fINoise()\fR by
- omega, lambda fBm parameters
- octaves number of octaves of \fINoise()\fR in fBm
- colormap name of colormap file, if any
- .D)
- Thus, we add to the end of texture.c:
- .D(
- Texture *
- NewMountainText(scale, omega, lambda, octaves, mapname)
- double scale, omega, lambda;
- int octaves;
- char *mapname;
- {
- /*
- * Pointer to new texture.
- */
- Texture *text;
-
- /*
- * Allocate new texture of type MOUNTAIN
- */
- text = new_texture(MOUNTAIN);
- /*
- * Allocate space to store omega, lambda and octaves.
- */
- text->args = (double *)Malloc(3 * sizeof(double));
- text->args[0] = omega;
- text->args[1] = lambda;
- text->args[2] = (double)octaves;
- /*
- * scale is stored in 'size'.
- */
- text->size = scale;
- /*
- * If a colormap name was specified, read it into 'colormap'.
- */
- if (mapname != (char *)0)
- text->colormap = read_colormap(mapname);
- /*
- * All done -- return new texture.
- */
- return text;
- }
- .D)
- .LP
- Thus, NewMountainText is called with the desired parameters and a
- new Texture is returned.
- .LP
- Finally, we must write the routine which actually performs the texturing.
- Each texturing function is called by apply_textures() with the following
- arguments:
- .D(
- text
- a pointer to the Texture being applied
- pos
- a pointer to the coordinates of the point of intersection
- norm
- a pointer to the surface normal at the point of intersection
- surf
- the pointer to a copy of the surface of the object being
- textured (a copy is used so that the surface can be
- modified for a given shading calculation without affecting
- subsequent calculations).
- .D)
- .LP
- Thus, we write:
- .D(
- MountainText(text, pos, norm, surf)
- Texture *text;
- Vector *pos, *norm;
- Surface *surf;
- {
- double val;
- int index;
-
- /*
- * Compute value of fBm (fractional Brownian motion) for
- * the given point.
- */
- val = fBm(pos, text->args[0], text->args[1], (int)text->args[2]);
- /*
- * Scale the result as requested and add in the Z component of
- * the point of intersection. Note that in a better texture
- * we'd probably have additional parameters to afford
- * greater control of val.
- */
- val = pos->z + text->size * val;
-
- if (text->colormap) {
- /*
- * If we're using a colormap, compute an index into
- * the colormap and use the appropriate color as the
- * diffuse component of the surface.
- */
- index = 255. * val;
- if (index > 255)
- index = 255;
- if (index < 0)
- index = 0;
- surf->diff.r *= text->colormap[index].r;
- surf->diff.g *= text->colormap[index].g;
- surf->diff.b *= text->colormap[index].b;
- surf->amb.r *= text->colormap[index].r;
- surf->amb.g *= text->colormap[index].g;
- surf->amb.b *= text->colormap[index].b;
- } else {
- /*
- * If there's no colormap, simply scale the diffuse
- * component.
- */
- ScaleColor(val, surf->diff, &surf->diff);
- ScaleColor(val, surf->amb, &surf->amb);
- }
- }
- .D)
- .LP
- .NH
- input_lex.l
- .LP
- Now that we have the hard parts written, all that is left is making
- the parser recognize the new texture. To do this, we first need to
- add a keyword for our texture to the list of keywords recognized by
- rayshade. This is done by editing input_lex.l.
- .LP
- The file input_lex.l contains, among other things, an alphabetical list of
- rayshade's keywords. To add a new keyword, one simply follows the
- example of the other keywords. Thus, we add the line:
- .D(
- mountain {return(tMOUNTAIN);}
- .D)
- between the lines defining 'mist' and 'object'. This line directs
- lex to return the token tMOUNTAIN whenever the string "mountain" is
- encountered in an input file.
- .NH
- input_yacc.y
- .LP
- Finally, we need to write the code which will actually create an instance
- of the new texture by calling NewMountainText(). This is done in
- input_yacc.y.
- .LP
- In input_yacc.y, there are a series of lines which look something like:
- .D(
- %token tBACKGROUND tBLOTCH tBOX tBUMP tCONE tCYL tDIRECTIONAL
- %token tENDDEF tEXTENDED tEYEP tFBM tFBMBUMP tFOG tFOV tGRID
- %token tHEIGHTFIELD tLIGHT tLIST tLOOKP tMARBLE tMAXDEPTH tMIST
- %token tOBJECT tOUTFILE
- %token tPLANE tPOINT tPOLY tROTATE
- %token tSCALE tSCREEN tSPHERE tSTARTDEF tSUPERQ tSURFACE tRESOLUTION
- %token tTHRESH tTRANSLATE tTRANSFORM tTRIANGLE tUP tENDFILE
- %token tTEXTURE tCHECKER tWOOD
- .D)
- .LP
- These lines declare the tokens returned by lex. We need to declare
- tMOUNTAIN in a similar manner. So, we change the last line to
- read:
- .D(
- %token tTEXTURE tCHECKER tWOOD tMOUNTAIN
- .D)
- Next, we need to call NewMountainText() in the proper place. In input_yacc.y,
- there is a production which reads something like:
- .D(
- Texturetype : tCHECKER String
- {
- $$ = NewCheckText($2);
- }
- | ...
- ...
- | tWOOD
- {
- $$ = NewWoodText();
- }
- ;
- .D)
- .LP
- These productions invoke the proper texture creation routine when appropriate.
- For example, when the keyword corresponding to tCHECKER is followed by
- a String, yacc will invoke NewCheckText() with the string (the name of
- a surface, in this case) as an argument. The Yacc grammar understands
- the following datatypes, among others:
- .D(
- String a series of alphanumerics surrounded by
- white space (i.e., the string need not be quoted)
- Fnumber a floating-point number
- tINT an integer
- Vector a vector (x, y, z)
- Color a color (r, g, b)
- .D)
- To add a texture to the list of recognized textures, we change:
- .D(
- ...
- | tWOOD
- {
- $$ = NewWoodText();
- }
- ;
- .D)
- to:
- .D(
- | tWOOD
- {
- $$ = NewWoodText();
- }
- | tMOUNTAIN Fnumber Fnumber Fnumber tINT
- {
- $$ = NewMountainText($2, $3, $4, $5, (char *)0);
- }
- | tMOUNTAIN Fnumber Fnumber Fnumber tINT String
- {
- $$ = NewMountainText($2, $3, $4, $5, $6);
- }
- ;
- .D)
- .LP
- The first new production invokes NewMountainText() when the keyword
- associated with tMOUNTAIN ("mountain") appears in an appropriate place
- in the input file followed by
- .I four
- parameters (scale, omega,
- lambda, and octaves). In this case, NewMountainText is passed a
- NULL pointer as the name of the colormap to use.
- This code creates a reference to a mountain texture that does
- .I not
- make
- use of a colormap. So, this production is invoked whenever a line
- such as the following is encountered in the input file:
- .D(
- texture mountain 0.2 0.5 2.0 6
- .D)
- .LP
- The second production works in a similar manner, except that it passes
- a colormap name to NewMountainText(). It handles lines such as:
- .D(
- texture mountain 0.2 0.5 2.0 6 mountain.map
- .D)
- .NH
- Compiling
- .LP
- That, in theory, is all there is to it. Run 'make' to recompile
- input_lex.o, input_yacc.o, and texture.o.
- .NH
- Testing
- .LP
- A good test input file for the new texture might be something like:
- .D(
- screen 512 512
- eyep 0 -10 0
- lookp 0 0 0
-
- fov 20.
-
- light 1.0 directional 1. -1. 1.
- surface boring .1 .1 .1 .8 .8 .8 0 0 0 0 0 0 0
-
- sphere boring 1. 0. 0. 0. texture mountain 0.2 0.5 2.0 6 planet.map
- .D)
-