Tutorial 40: Drawing in OpenGL using jit.gl.sketch
The OpenGL capabilities of Jitter provide us with a variety of tools for creating animated scenes in three dimensions. The Jitter OpenGL objects we’ve looked at thus far each perform a fairly straightforward task, be it creating simple geometries (jit.gl.gridshape and jit.gl.plato), importing OBJ models (jit.gl.model), or manipulating the position of objects and scenes (jit.gl.handle). More complex scenes can be built by using several of these objects at once. After a certain point, however, it may make sense to work with an object that understands OpenGL commands directly and can be used to perform a large number of drawing commands at once. This object is jit.gl.sketch, and it is the subject of this Tutorial.
The majority of the messages used by jit.gl.sketch are slightly modified versions of standard functions within the OpenGL API. The entire API is outside the scope of this Tutorial; however, the standard reference for these functions (the OpenGL “Redbook”) is available online at:
Converting between OpenGL code as given in the “Redbook” and elsewhere (in the C programming language) and messages to jit.gl.sketch is reasonably straightforward if the following conventions are kept in mind:
In addition to the basic OpenGL API, jit.gl.sketch also understands a number of high-level drawing commands to instruct the object to render basic shapes and perform vector graphics-style drawing operations.
The Tutorial patch consists of a series of Jitter objects used for rendering in OpenGL: a jit.gl.render object, a jit.pwindow object, and a jit.gl.sketch object. The jit.gl.sketch object is sent messages from a bpatcher object containing message box objects containing lists of commands. Different views of the bpatcher can be obtained by selecting different offsets from the umenu object connected to it. In this Tutorial we'll step through the different sections of the bpatcher one-by-one and look at the messages contained in each view.
By starting the qmetro object, we’ve begun rendering our OpenGL scene. Our jit.gl.render object shares it’s (sketchy) with the jit.pwindow object and the jit.gl.sketch object in the patch. Because of this, the jit.pwindow will display anything drawn by the jit.gl.render object, which in turn will draw whatever the jit.gl.sketch object instructs it to draw.
The command list
A green triangle should appear in the jit.pwindow object.
reset, glcolor 0 1 0 1, glbegin line_loop, glvertex -0.5 -0.5, glvertex 0 0.5, glvertex 0.5 -0.5, glend
The jit.gl.sketch simply tells the object to clear its command list and reinitialize itself. What follows are a sequence of OpenGL commands. The command tells jit.gl.sketch to change the color it uses to draw. As with all OpenGL objects in Jitter, these colors are floating-point values in RGBA (red, green, blue, alpha) order; thus, tells jit.gl.sketch to draw in a fully opaque (alpha=1) green.message to
The jit.gl.sketch to interpret what follows as instructions defining an object. The argument to specifies the drawing primitive to use when rendering the shape. The primitive we’ve chosen, , will take a set of points (called vertices) and connect them with lines, completing the shape by connecting the last vertex to the first vertex again. Thus in a shape with four points A, B, C, and D, our object will contain lines from A to B, B to C, C to D, and D to A.message tells
The jit.gl.sketch that we’ve finished defining the shape. We could then move on to another shape (or execute other commands) if we so desired.messages contain the coordinates of the points in our shape. If only two coordinates are given, they are interpreted as x and y values. If three coordinates are given, the last value is interpreted as a z coordinate. The message tells
Step one of our patch therefore resets the object, sets the color to green, and instructs it to draw an outlined shape with three vertices connected to one another. These messages (with the exception of the jit.gl.sketch for the image we see in the jit.pwindow.message) form the command list for
More about drawing primitives
The list of commands in our message box is very similar to the first one:
reset, glcolor 0 1 0 1, glbegin triangles, glvertex -0.5 -0.5, glvertex 0 0.5, glvertex 0.5 -0.5, glend
Once again, we reset the object and then define a color (green) and an object with three vertices to be drawn. The only difference is the drawing primitive specified as an argument to the jit.gl.sketch to interpret triplets of vertices as triangles. Rather than outlining the shape as we did before, we’ve now instructed jit.gl.sketch to generate a polygon. As a result, the interior of our vertices is filled with the specified (green).message. In this example, we’re using as our drawing primitive. This tells
In this command list, we’ve embeddedcommands for each vertex of our triangle:
reset, glbegin triangles, glcolor 1 0 0 1, glvertex -0.5 -0.5, glcolor 0 1 0 1, glvertex 0 0.5, glcolor 0 0 1 1, glvertex 0.5 -0.5, glend
As a result, our jit.gl.sketch object sets our three vertices to red, green, and blue, respectively, and fills in the interior surface with a color gradient that fades smoothly between the three colors. The attribute of jit.gl.sketch allows this behavior when set to (as is the case with the jit.gl.sketch object in our Tutorial patch).
This section of the bpatcher allows us to see how a drawing primitive works to make a more complex shape. We begin our command list with the command, which tells jit.gl.sketch whether to fill in complete polygons or leave them as outlines, exposing the skeleton that defines how the vertices are connected. The first argument to defines whether we’re talking about the of the shape, the of the shape, or both ( , as we’re using here). The second argument defines whether that side of the shape will be filled ( ) or left outlined ( ). The attribute of the Jitter OpenGL objects accomplishes the same thing.
After we’ve reset and decided how we want our shape drawn (filled or outlined), we supply a shape description:
glbegin tri_strip, glcolor 1 0 0 1, glvertex -0.5 -0.5, glcolor 0 1 0 1, glvertex -0.5 0.5, glcolor 0 0 1 1, glvertex 0.5 -0.5, glcolor 1 1 1 1, glvertex 0.5 0.5, glend
As with the triangle in our previous example, we provide a jit.gl.sketch to draw the shape as a series of triangles where the last two vertices of a triangle are recycled into the next triangle. Thus a polygon with six vertices A, B, C, D, E, and F would be drawn as four triangles using the primitive: ABC, CBD, CDE, and EDF (the ordering ensures that the triangles are all drawn with the same orientation). In our code, we get a shape with two triangles that when connected and filled appear to us as a square.for each (in this example red, green, blue, and white for the four points of the square). The drawing primitive used as an argument to tells
Rotation, translation, and scaling
In addition to defining shapes containing vertices and colors, we can instruct jit.gl.sketch to manipulate these shapes as objects that can be animated and scaled. Our command list sets this up for us by supplying some additional commands:
reset, glmatrixmode modelview, glpushmatrix, glrotate $1 0 0 1,
After resetting everything, we send the jit.gl.sketch we are going to be performing perspective operations to define something called the modelview matrix. This allows us to perform rotation, translation, and scaling on the object. The message copies our current matrix onto a stack, allowing us to return to it later. This allows us to create nested translations for drawing complex objects. For example, we could draw a series of polygons, each with rotations relative to the previous polygon; in this way we could generate sequences of shapes that have the same orientation with respect to each other but could then all be rotated with respect to another group of shapes. The use of a stack for this process greatly aids in modeling as we can use simple face-on coordinates for the vertices in our shapes and then tell the renderer to compute the rotations for us. The command specifies the degrees of rotation for our shape followed by three values that specify the axis of rotation as an xyz vector. Thus we’re rotating our object around the z axis (which spins the shape along the x-y plane of the screen).message, which tells
Following the rotation, we continue with our command list as in the previous example. At the end of the shape specification, we complete our command list with the jit.gl.sketch object to the previous rotation. New commands added to the command list after that would then use the original rotation vector, not the one we’ve applied to our shape.command, which returns our
Our command list here sets up a newmatrix and performs the rotation three times in series:
reset, glmatrixmode modelview, glpushmatrix, glrotate $1 1 0 0, glrotate $2 0 1 0, glrotate $3 0 0 1,
This allows us to rotate our shape along the x axis, y axis, and z axis in sequence according to the values of the number box objects in our patch. We then create four objects using this rotation before we send a to return to our original orientation. After our square is rendered, we create three two-vertex lines in different colors oriented along the same axes as the plane:
glcolor 1 0 0 1, glbegin lines, glvertex 0 0 0, glvertex 1 0 0, glend, glcolor 0 1 0 1, glbegin lines, glvertex 0 0 0, glvertex 0 1 0, glend, glcolor 0 0 1 1, glbegin lines, glvertex 0 0 0, glvertex 0 0 1, glend, \nglpopmatrix
The drawing primitivesimply connects all the vertices in the shape (unlike , it does not connect the last vertex back to the first). Notice that because these objects (and our square) are all within the same matrix, they all share the same rotation.
In addition to image rotation, thematrix can apply translation of an image. This allows us to specify vertices for our object centered around (0, 0) and then move the object anywhere we want in the space. The message in our command list performs this function, taking as arguments the x and y offsets that will be applied to every vertex in the shape(s) affected by the transformation.
Just as we can rotate and translate a shape or shapes, we can also control their scaling with themessage. The vertices in our shape are multiplied by the arguments to the message (specified as x, y, and optional z scaling factors).
The jit.gl.sketch object gives us access to the powerful OpenGL API, allowing us to define and execute an OpenGL command list within a Max patch. Lists of vertices, specified by messages and bracketed by and messages, can be used to specify the shapes of objects. The command takes a drawing primitive as its argument that defines the algorithm by which the vertices are connected and filled. You can use the command to expose the outlined structure of a shape, and you can color entire shapes or vertices using messages. You can perform rotation, translation, and scaling of a defined shape by sending the command with the argument . Using , , and commands, you can then specify a viewing transformation. The and commands allow you to define a stack of these transformations so that you can change the rotation, position, and size of shapes either independently or in nested groups within a sequence of OpenGL commands.
|bpatcher||Embed a subpatch with a visible UI|
|jit.gl.render||Render Jitter OpenGL objects|
|jit.gl.sketch||Use drawing commands with OpenGL|
|jit.pwindow||Display Jitter data and images|