pamtris

Updated: 7 August 2018
Table Of Contents

NAME

pamtris - triangle rasterizer featuring linear interpolation of generic vertex attributes and depth buffering

SYNOPSIS

pamtris -width=width -height=height -num_attribs=attributes_per_vertex [-maxval=maxval] [-tupletype=tupletype]

All options can be abbreviated to their shortest unique prefix. You may use two hyphens instead of one to designate an option. You may use either white space or an equals sign between an option name and its value.

DESCRIPTION

This program is part of Netpbm.

pamtris can be used to draw a great variety of 2D and 3D graphics by composing arbitrarily complex pictures out of separate triangles, triangle strips and triangle fans constructed on an internal frame buffer. The program reads instructions written in a simple 7-command line-oriented notation from standard input and outputs its results as a (potentially multi-image) PAM stream on standard output.

Triangle data is given by setting the appropriate drawing mode, if necessary, and then providing a list of vertices. Each vertex is also associated with a list of up to 20 so-called "generic attributes", which are integer values between 0 and a given maxval. Such attribute lists may be provided on a per-vertex basis.

The frame buffer consists of an "image buffer" and a "depth buffer". The image buffer consists of a sequence of height rows containing a sequence of width tuples. Each tuple represents an image pixel and consists of a sequence of samples whose number is equal to the number of generic attributes per vertex plus an extra so-called "alpha sample" which indicates the opacity of the pixel. Each tuple in the image buffer is associated with an integer in the depth buffer which is used to determine whether subsequent drawing operations should affect that particular pixel or not. This provides a way of depth-sorting graphical objects which is adequate for many purposes in 2D and 3D computer graphics. One prominent shortcomming of such an approach to depth-sorting, however, is that it does not automatically work with objects which are intended to appear "translucent", therefore requiring more elaborate strategies in order to incorporate said objects into pictures generated using this technique.

No matter how many generic attributes per vertex are being used at any one time, the last sample of every tuple in the image buffer holds the opacity value for the corresponding pixel. However, those samples are manipulated internally by pamtris and are always equal to either 0 or the maxval. The program does not provide direct control over the alpha image plane on the user side.

pamtris rasterizes triangles by approximating their visible area as a collection of pixels at particular positions in the frame buffer, and to every pixel it assigns a list of attributes whose values are a particular linear interpolation between the values of the corresponding attributes of each vertex of the triangle. Whenever a pixel within the area of the frame buffer is produced, it is written only to the corresponding position in the frame buffer if and only if it passes a so-called "depth test". This test works as follows: the depth value of every incoming pixel (which is itself a linear interpolation between the Z-coordinates of the vertices of the corresponding triangle) is compared against the value in the corresponding position in the depth buffer. If the depth value of the incoming pixel equals or is below the depth value already present in said position in the depth buffer, the following happens:

  1. Every sample i, where 0 ≤ i < num_attribs, of the tuple in the corresponding position in the image buffer is set to equal the value of the respective attribute of the incoming pixel; and the alpha sample (the last one) is updated to hold the maxval;
  2. The depth value in the corresponding position in the depth buffer is updated to hold the depth value of the incoming pixel.

Otherwise, that particular pixel effects no change at all in the frame buffer.

The frame buffer is initially set so that all samples in every tuple of the image buffer contain the value 0, and all entries in the depth buffer contain the maximum permitted depth value.

The generic attributes' values, and therefore the samples in the output PAM images, have no fixed interpretation ascribed to them (except for the last image plane, which is deliberately supposed to represent pixel opacity information): one may ascribe any suitable meaning to them, such as that of colors, texture coordinates, surface normals, light interaction characteristics, texture influence coefficients for multi-texturing, etc.

OPTIONS

At least -width, -height and -num_attribs must be provided.

-width=width
Sets the width of the internal framebuffer and, by extension, of the output PAM images, given in number of columns. This must be an integer in the closed interval [1, 8192].
-height=height
Sets the height of the internal framebuffer and, by extension, of the output PAM images, given in number of rows. This must also be an integer in the closed interval [1, 8192].
-num_attribs=attributes_per_vertex
Sets the number of generic attributes per vertex and, by extension, the depth of the output PAM images, which is equal to this value plus one (to accomodate the alpha plane). The argument must be an integer in the closed interval [1, 20]. This value may modified afterwards through the reset command.
-maxval=maxval
Sets the maxval of the output PAM images, which is also the maximum permitted value for each generic vertex attribute. This must be an integer in the closed interval [1, 65535]. The default value is 255. This value may also be modified afterwards through the reset command.
-tupletype=tupletype
Sets the tupletype for the output PAM images. The argument is a string which may be no longer than 255 characters. The default tupletype is the empty string. The tupletype may be modified afterwards through the reset command as well.

INSTRUCTION CODE

The input for pamtris consists of a sequence of text lines sent to it through the standard input mechanism.

Empty lines or lines that contain only white space characters are called blank lines and are ignored.

When a # is found anywhere in a line, pamtris ignores it along with every character after it. In other words, everything from the # until the end of the line receives the same treatment as white space.

Lines which are not blank must contain a sequence of strings separated by white space, called tokens. The first such token must be one of the commands recognized by pamtris, and all further tokens are interpreted as the arguments for that command, if it takes any. When an insufficient number of arguments is provided for a command, the line is considered invalid and is given the same treatment as a blank line. The same happens when an out of range argument or one of a kind different of what is expected is given (for example, when you give a string of letters where a numerical value is expected), or when an unrecognized command/argument is found. When a number of arguments greater than that required for a particular command is provided, only the portion of the line up to the last required argument is considered and any further tokens are ignored.

pamtris is case-insensitive. That is, mode, MODE, mODe, etc. are all treated the same way.

The commands recognized by pamtris are:

quit
mode
attribs
vertex
print
clear
reset

Instead of a full command name, it is also permissible to give a minimum unique abbreviation in its place, which has the same effect. An exclamation mark (!) may be used in place of the print command name. Likewise, an asterisk (*) may be used in place of the clear command name.

The respective operation of each command is explained below.

quit

Terminates pamtris. The program will not read any more lines of input when this command is found, and shall ignore any further tokens on the same line as it.

mode { triangles | strip | fan }

Makes pamtris enter a new drawing mode. The argument is a word which specifies the mode to change to. Instead of a full argument name, it is permissible to provide a minimum unique abbreviation, which has the same effect. The drawing mode will remain the same until the next mode command is given.

This command also resets the current vertex list, which is (re)initialized to an empty state after the command is executed. One may add new vertices to this list through successive invocations of the vertex command (see below). You do not have to worry about providing "too many" vertices, since the vertex list is virtualized: pamtris maintains only the state necessary to hold information pertaining to three vertices at any one time. The current vertex list is initially empty.

It is permissible to give pamtris a mode command which instructs it to enter a drawing mode it is currently already in. One might use this approach to reset the current vertex list without changing the current drawing mode.

Regardless of the current drawing mode, a new triangle is immediately rasterized into the frame buffer as soon as the necessary vertices for it are provided through the current vertex list.

In the following descriptions of each drawing mode, triangles' and vertices' indices are 0-based.

The triangles argument instructs pamtris to enter the "TRIANGLES" drawing mode. While in this mode, a series of separate triangles is constructed. Every three vertices pushed into the current vertex list specify a new triangle. Formally, this means that every Nth triangle is specified by vertices 3 * N, 3 * N + 1 and 3 * N + 2. This is the default initial mode and is therefore not required to be set explicitly before drawing any triangles.

The strip argument instructs pamtris to enter the "STRIP" drawing mode. While in this mode, a so-called "triangle strip" is constructed. That is, the first three vertices pushed into the current vertex list specify the first triangle, and every new vertex pushed after that specifies, together with the previous two, the next triangle. Formally, this means that every Nth triangle is specified by vertices N, N + 1 and N + 2.

The fan argument instructs pamtris to enter the "FAN" drawing mode. While in this mode, a so-called "triangle fan" is constructed. That is, the first three vertices pushed into the current vertex list specify the first triangle, and every new vertex pushed after that specifies, together with the previous vertex and the first one, the next triangle. Formally, this means that every Nth triangle is specified by vertices 0, N + 1 and N + 2.

attribs a0 ... anum_attribs - 1

Updates the current attribute values list. This command takes as arguments a sequence of num_attribs integers which represent the values of the generic attributes to be associated with the next vertex. This sequence of values is the just mentioned "current attribute values list".

Each ith argument, where 0 ≤ i < num_attribs, indicates the value to be assigned to the ith attribute of the current attribute values list. All arguments must be integer values in the closed interval [0, maxval]. If a number of arguments less than the current value of num_attribs is given, the command is considered invalid and is therefore ignored.

The current attribute values list remains unchanged until the next valid attribs or reset command is given. The attribs command allows one to change the values of each attribute individually, while the reset command is not specifically designed for that function, but it has the side effect of setting all values in the current attribute values list to the maxval (see below).

All values in the current attribute values list are initially set to the maxval.

vertex x y z

Adds a new vertex to the current vertex list (see the mode command above), assigning the values of the arguments to its respective coordinates, and the values in the current attribute values list (see the attribs command above) to the respective entries in the generic attribute list associated with the vertex.

The x and y arguments must be integer values in the closed interval [-32767, 32767], and they represent, respectively, the column and row of the pixel which corresponds to the location of the vertex. Such values may correspond to pixels outside the limits of the frame buffer. The origin of the coordinate system is at the top-left pixel of the frame buffer. The X-axis goes from left to right, and the Y-axis from top to bottom. A negative value for x indicates a column that many pixels to the left of the leftmost column of the frame buffer. Likewise, a negative value for y indicates a row that many pixels above the uppermost row of the frame buffer.

The z argument represents the Z-coordinate of the vertex, which is used to compute depth values for pixels within the areas of rasterized triangles. This must be an integer value in the closed interval [0, 1073741823].

print

Appends a PAM image to standard output whose raster is a copy of the current contents of the image buffer. The values of the WIDTH and HEIGHT fields are the same as the width and height, respectively, of the frame buffer, which were given on the command line during program invocation. The MAXVAL field is equal to the current maxval; the DEPTH field is equal to the current value of num_attribs + 1; and the TUPLTYPE field is equal to the current tupletype.

This command has no effect upon the current drawing state. E. g. it does not modify the current drawing mode, the current vertex list, etc.

clear [ image | depth ]

Clears the frame buffer. That is, all samples in the image buffer are once again set to 0, and all entries in the depth buffer are once again set to the maximum permitted depth value.

Optionally, one may provide an argument to only clear either the image buffer or the depth buffer individually, while leaving the other intact. With the image argument, only the image buffer is cleared; with the depth argument, only the depth buffer is cleared. Instead of full argument names, one may provide a minimum unique abbreviation, which has the same effect. The single character z is also accepted as an alias for depth.

Like the print command, this command has no effect upon the current drawing state either.

reset maxval num_attribs [tupletype]

Updates the current maxval and number of generic attributes per vertex (num_attribs), resetting the image buffer with a new maxval and number of samples per tuple while at it. The parameter maxval must be an integer in the closed interval [1, 65535], and num_attribs must be an integer in the closed interval [1, 20].

Optionally, after the second argument, one may provide a string to be assigned to the current tupletype. The string goes from the first character after the second argument which is not white space and continues until (and including) the last character before the end of the line which is not white space. If a new tupletype is not provided, or the provided string is longer than 255 characters, the empty string is assigned to the current tupletype.

The side effects of running this command are:

  1. The new image buffer is completely cleared once the command is executed.
  2. All values in the current attribute values list are set to the new maxval.
  3. The current vertex list is reset.

However, it does not touch the depth buffer: it is left the same way as it was found before the command. Also the drawing mode remains the same (e. g. if pamtris was in FAN mode, it will continue in that same mode, etc.).

If this command is given with an invalid value for maxval or num_attribs, it is ignored and therefore none of the above side effects apply, nor do the current maxval, num_attribs or tupletype change at all.

It is permissible to give a value for maxval and num_attribs equal to the current maxval and num_attribs, respectively, although the above side effects will still apply even in such cases.

Since this command deals with memory allocation, it may fail to execute successfully. If that happens, no lines of input will be read anymore and pamtris will be terminated as if the quit command was given.

TIPS

Texturing

It is possible to apply so-called "textures" to images produced with pamtris by using a pair of generic vertex attributes as texture coordinates, then using pamchannel to select the appropriate channels in the output image(s), and finally processing the result through pamlookup, providing the desired texture file as a "lookup table". Textures applied this way are naturally perspective-correct.

You might want to consider using pnmtile to make textures which are inteded to be "repeated" along triangle meshes.

Anti-aliased edges

pamtris performs no anti-aliasing on triangle edges by itself. However, it is possible to obtain anti-aliased images through a super-sampling approach: draw your image(s) at a size larger than the desired final size, and then, when all post-processing is done, down-scale the final image(s) to the desired size.

Drawing images with twice the desired width and height, then down-scaling them to the desired size using a quadratic filter, might produce results which are often good enough. You might want to use pamscale for the down-scaling part, but if that's not fast enough you might consider using another scaler. For example, if you are piping frames into ffmpeg to make a movie out of them, you might want to use its own scaler.

EXAMPLE

When the output of the below C program is the Standard Input of


pamtris -w 256 -h 256 -n 3 -t RGB_ALPHA | pamscale -xscale 0.5 -yscale 0.5 -f quadratic | pamrgbatopng

the following picture of NAMCO's Pac-Man is produced:

Pac-Man

#include <stdio.h>
#include <math.h>

#define PI		3.141592
#define PI2		(2.0 * PI)

#define WIDTH		256
#define HEIGHT		WIDTH

#define SEGMENTS	(PI * WIDTH)

int main()
{
        const int center_x = 0.5 * WIDTH;
        const int center_y = 0.5 * HEIGHT;

        const double radius = 0.48 * WIDTH;

        puts("mode triangles");

        int y_leg = tan(PI / 4.0) * center_x;

        printf("vertex %d %d 0\n", center_x, center_y);
        printf("vertex %d %d 0\n", WIDTH, center_y - y_leg);
        printf("vertex %d %d 0\n", WIDTH, center_y + y_leg);

        puts("clear image");
        puts("mode fan");
        puts("attribs 255 255 0");
        printf("vertex %d %d 1\n", center_x, center_y);
        puts("attribs 255 128 0");

        double ang = 0.0;
        const double ang_step = PI2 / SEGMENTS;

        for(int i = 0; i <= SEGMENTS; i++)
        {
                int x = round(cos(ang) * radius + center_x);
                int y = round(sin(ang) * radius + center_y);

                printf("vertex %d %d 1\n", x, y);

                ang += ang_step;
        }

        puts("print");

        return 0;
}

SEE ALSO

pampick pamchannel pamstack pamlookup pamarith pamscale pamdepth pamexec pam

AUTHOR

pamtris was written by Lucas Brunno Luna. The author is grateful to Bryan Henderson for offering suggestions regarding usability.

HISTORY

pamtris was new in Netpbm 10.84 (September 2018).


Table Of Contents