TEXTURE FORMAT -------------- Imagine 3.0 Texture File Format Rev 1.0 05-22-94 S.Kirvan Copyright 1994 Impulse Inc., Mpls, MN 55444 Disclaimer: All information contained herein is subject to change without notice. Knowledge is dangerous - use at your own risk. No warranties are made or implied, and no liability or responsibility is assumed. Good luck. SCOPE: ===== This text will attempt to describe the construction of Imagine 3.0's run-time loadable texture modules. The idea behind this text is basically, "teach by example." By this, I mean that I have actually included and (perhaps overly) documented a texture module that does a basic, seen-it- way-too-many-times-in-raytraces, checkerboard (or checkerbored) texture. The code included is in C and all text in this document is in comment form. This document, of course, doesn't attempt to explain how Imagine 3.0's textures do what they do, but is instead, a basic example for understanding textures. Unfortunately, Impulse does not have the support staff available to answer technical questions about the information included in this document. Hopefully, this will be basic enough to be a reasonable starting point, and at the same time, will have enough depth to leave you with a sense of direction towards more complex subjects. Again, Good luck. GENERAL INFO: ============ The primary communications between the texture code and the renderer are handled through a "patch" structure. The patch is a collection of attributes and other information that are relevent to a particular spot where a camera ray has hit an object. This spot is refered to as the "hit point." The information passed to the texture code by the renderer (via. the patch) can be modified to change surface attributes and create textures. Information commonly modified by texture code is the color, transparency, reflect, and surface normals. The functions, fwork() and iwork(), below, are the guts of the texture code. One of these functions, fwork() for floating point and PC versions, and iwork() for Amiga integer version, is called from the renderer each time a ray hits an object with this texture on it. The patch structure gets passed to the work function along with information about the texture axis and the position of the hit point. Using this information, the patch may be modified, changing the object's attributes. The texture code doesn't get any information about the "world," or about the geometry (points/edges /faces) of the object that was hit. The entry module for the texture code is texture_init(). texture_init() sets up a structure (ttable{}) which is passed back to imagine. texture_init() also checks for software version compatability. The structure passed back to imagine has information so that imagine can find the work() function, and the interface (the texture requester) data. Imagine's rendering code uses this pointer to the work() function when it calls the texture(s). The section of texture.c (below), called "INITIALIZATIONS," sets up the data for the user interface to the textures. This section has all the information for the text, defaults, and texture axis data that can be modified by the user. Imagine's texture hook is intentionally as streamlined as possible - there is no error checking done within Imagine - it's all up to you. If objects start looking bright, dark, or translucent in ways you aren't expecting, it's probably because some value has gone out of range and Imagine has forced the errant value through the renderer. Doing this may look cool, but it could have unpredictable side effects so we recomend against doing it. Color gun values (for color, reflect, and filter) are set by the user in a range from 0 to 255. Within the patch, the associated color gun values are stored as floats (FRACTS) and must be normalized to range from 0.0 to 1.0. The surface normal within the patch must also be normalized (ie. The length of the normal vector must equal 1.0). When doing bumpy textures (like DinoSkin), you modify the surface normal to fool the renderer into putting shading and specular highlights in areas that normally wouldn't get them. As well as making sure the surface normal is normalized, you must also be sure that modifications to the surface are made relative to the local texture axis - not doing this will lead to bumps that have illogical highlights and won't animate properly. The way to see if the bumps are being applied properly is as follows: Create an animation with the texture in question on an object - have the camera and a light source associated to the object (so they all move in tandem). Put all these objects in black space, and rotate the main object throughout the animation. There should be no change in the look of the object through the length of the animation since the scene is being viewed and lit relative to the object itself. If the texture isn't doing the bumps properly, the specular highlights will crawl on the bumps. The texture, Rainbow.itx, which was include with Imaigne 3.0 is a big help in debugging and getting bumpy textures to work properly. In the patch, some of the data is read-only. This data is supplied as information that can be used by the textures but modifying the data will have no effect. When a texture is used repeatedly in the same image, it is only loaded into memory once - The important thing about this is that texture's can have static data space, but this data space will be shared by every instance of the texture. ********* COMPILING DETAILS ********* The SAS/C Compiler Ver 6.5 command line is as follows: sc nostartup code=far data=far math=68881 nostackcheck link to Texture.itx texture.c (all typed on one line...) On the PC, the texture code has to be linked as a Phar Lap 32-bit ".REX" file (relocatable executable). Impulse's textures were compiled using the Metaware High-C compiler, and linked using Pharlap's linker. hc386 -Hoff=Protection -c texture.c 386link texture.obj -relexe Texture.itx The important things to "get" about trying to compile texture code are as follows: - don't link in startup code - everything is 32 bit - ie. far pointers for both data and code - don't allow the compiler to put in any stack checking code */ /**************************************************************/ /* */ /* Texture.c - Imagine 3.0 checkerboard texture example */ /* */ /**************************************************************/ /********* CONSATANTS *********/ // constants to be used in infoflags[] #define TXTF_RED 1 #define TXTF_GRN 2 #define TXTF_BLU 4 #define TXTF_SCL 8 // version number constant - if not correct, texture won't load #define TXT_VERS 0x49545854 //#define TXT_VERS 0x54585449 // PC version #define NULL (0L) /********* DATA TYPES *********/ typedef char BYTE; typedef unsigned char UBYTE; typedef short WORD; typedef unsigned short UWORD; typedef long LONG; typedef void * APTR; typedef float FLOAT; // FRACT can be typedef'ed as a float without harm since this example is // for the floating point version only. See TDDD.DOC for an explanation // of the FRACT data type. typedef float FRACT; typedef struct _vector { FRACT X; FRACT Y; FRACT Z; } VECTOR; typedef struct _tform { VECTOR r; // texture axes position VECTOR a; // texture's X axis alignment VECTOR b; // texture's Y axis alignment VECTOR c; // texture's Z axis alignment VECTOR s; // length of each axis } TFORM; // The ttable{} structure is used as a communication link between // Imagine and the run-time loadable texture modules. This structure is // initialized and passed back to imagine by the entry module // (texture_init()). typedef struct ttable { LONG id; // version number identifier void (*init)(); // reserved - curently unused void (*cleanup)(); // reserved - curently unused void (*work)(); // hook to the texture algorithm (the "guts") BYTE **infotext; // pointer to text fields for requester UBYTE *infoflags; // pointer to data field flags for requester APTR params; // pointer to data values for requester APTR tform; // pointer to texture axis geometry info } TTABLE; // The PATCH structure contains all the (ray) hit point info. // This structure is passed to a texture function each time a // ray hits the object with a texture on it. typedef struct _patch { VECTOR ptc_pos; // global hit point - read only VECTOR ptc_nor; // surface normal (must be normalized) FRACT ptc_col[3]; // surface color (R,G,B) at hit point FRACT ptc_ref[3]; // surface color (R,G,B) at hit point FRACT ptc_tra[3]; // surface color (R,G,B) at hit point FRACT ptc_spc[3]; // surface color (R,G,B) at hit point - read only UWORD ptc_shp; // copy of SHAP flags - read only - see TDDD.DOC UWORD ptc_shd; // flag - obj can shadow itself - read only FRACT ptc_pc0; FRACT ptc_pc1; VECTOR *ptc_ray; // position/exit direction of camera ray - read only FRACT raydist; FRACT foglen; // surface foglength } PATCH; // In the patch structure, ptc_ray[0] is the (camera) ray's base point // position, and ptc_ray[1] is ray's (normalized) direction vector /********* PROTOTYPES *********/ TTABLE *texture_init (LONG); void fwork (FRACT *, PATCH *, VECTOR *, FRACT *); void iwork (FRACT *, PATCH *, VECTOR *, FRACT *); /********* INITIALIZATIONS *********/ // infotext[][] is an array of strings that contain the texture name and // all the text fields that appear in the texture requester. The // texture name file (infotext[0]) is not currently used, but we // suggest supplying a name it in case it eventually is used. BYTE *infotext[17] = { "Tutorial Texture", "Color Red", "Color Green", "Color Blue", "", "", "", "", "", "", "", "", "", "", "", "", "", }; // infoflags[] is an array of TXTF_ (texture flags) for the data // fields of the texture requester. The the TXTF flags are as // follows: // Bit 0 - alter the red gun in the requester color chip // Bit 1 - alter the green gun in the requester color chip // Bit 2 - alter the blue gun in the requester color chip // Bit 3 - auto-scale this data field when object is scaled // Bits 4 thru 7 - reserved UBYTE infoflags[16] = { TXTF_RED, TXTF_GRN, TXTF_BLU, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; // fparams[] is an array of floats that are the initial defaults // in the texture requester. The values in this array can be // modified by the user and are passed to the texture code by // the floating point version of Imagine's renderer. FLOAT fparams[16] = { 255.0, 255.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, }; // iparams[] is an array of FRACT that are the initial defaults // in the texture requester. The values in this array can be // modified by the user and are passed to the texture code by // the integer version (amiga only) of Imagine's renderer. To // use FRACT with math functions requires a little creative // slight-of-hand with math macros and type conversions - doing // this is beyond the scope of this example, so the integer version // of this example texture will be stubbed out. These are treated // as fixed point, FRACTs, stored as longs. LONG iparams[16] = { 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, }; // ftform[] is an array of floats that represent the initial TFORM data // (size and position) of the texture axis. The values in this array can // be modified by the user (via edit axis) and are passed to the texture // code by the floating point version of Imagine's renderer. FLOAT ftform[15] = { 0.0, 0.0, 0.0, // position vector 1.0, 0.0, 0.0, // X axis alignment vector 0.0, 1.0, 0.0, // Y axis alignment vector 0.0, 0.0, 1.0, // Z axis alignment vector 10.0, 10.0, 10.0, // size of each axis }; // itform[] is an array of FRACT that represent the initial TFORM data // (size and position) of the texture axis. The values in this array can // be modified by the user (via edit axis) and are passed to the texture // code by the integer version of Imagine's renderer. Again, this example // is for floating point systems only. The FRACT data type is explained // in TDDD.DOC and the integer version of this texture example will be // stubbed out. These are treated as fixed point, FRACTs, stored as longs. LONG itform[15] = { 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 10*0x10000L, 10*0x10000L, 10*0x10000L, }; // this ttable{} structure is defined earlier and initialized here. TTABLE ttable = { TXT_VERS, /* version identifier - this contstant must be used */ NULL, NULL, NULL, infotext, infoflags, }; /********* FUNCTIONS *********/ // This is the texture_init() module for the textures. This is the entry // module through which Imagine loads and calls the texture code. // texture_init() is called with a version number and a flag representing // whether this texture is being called from a floating point version or an // integer version of Imagine. On the Amiga, these two (WORD) arguments // are packed into and passed as a single LONG which, in turn, is parsed // into the two arguments and interpreted within texture_init(). If all // goes well, texture_init() then initializes the ttable{} structure // (defined above) with the proper info and returns a pointer, for the // ttable{} structure, back to Imagine. TTABLE *texture_init (LONG arg0) { // "TTABLE *texture_init (int vers, int fp)" on PC WORD vers, fp; vers = arg0 >> 16; // parse out the two arguments fp = arg0 & 0xffff; if(vers != 0x60) // look for wrong version number return 0L; if (fp) { // called from the floating point version ttable.work = fwork; ttable.params = (APTR)fparams; ttable.tform = (APTR)ftform; } else { // called from the (Amiga only) integer version ttable.work = iwork; ttable.params = (APTR)iparams; ttable.tform = (APTR)itform; } return &ttable; } void fwork(params, pt, v, t) // this is the floating point work function. FRACT *params; // pointer to user definable numbers in requester PATCH *pt; // pointer to patch structure - defined above VECTOR *v; // hit position - relative to texture axis FRACT *t; // info about texture axis (TFORM array - 15 floats) { FLOAT X, Y, Z; X = v->X / t[12]; // note that the texture axis is used for scaling Y = v->Y / t[13]; Z = v->Z / t[14]; if (X < 0.0) X -= 1.0; // this eliminates duplication across the axes if (Y < 0.0) Y -= 1.0; // as the chex go from the posative to the if (Z < 0.0) Z -= 1.0; // negative regions if (((int)X + (int)Y + (int)Z) % 2) { // check for even numbers pt->ptc_col[0] = params[0] * .00392157; // use the params from pt->ptc_col[1] = params[1] * .00392157; // the requester on pt->ptc_col[2] = params[2] * .00392157; // these chex. } else { pt->ptc_col[0] = 0.0; // force the others to be blue pt->ptc_col[1] = 0.0; pt->ptc_col[2] = 0.5; } } void iwork(params, pt, v, t) // This is the integer version work function. FRACT *params; PATCH *pt; VECTOR *v; FRACT *t; { ; // stub... }