-
Notifications
You must be signed in to change notification settings - Fork 144
GLUP
To display things, one needs to talk to the graphic board (GPU) through an API (OpenGL, Vulkan, DirectX, Metal). In the early days of consumers graphics (1990's, 2000's), these APIs could only do basic things (draw points, segments, triangles, with optional color interpolation and texture mapping), whereas modern GPUs can be fully programmed using their own language (shaders). A drawback of this situation is that doing a simple thing (such as drawing a couple of triangles) has became very complicated as compared to what it was in the early days. This evolution occured between OpenGL 2.x (that has the so-called "fixed functionality pipeline) and OpenGL 3.x (that declared it deprecated, and finally removed it). For this reason, Geogram comprises GLUP, an API very similar to OpenGL 2.x, implemented under the hood with OpenGL 3.x (with shaders and buffer objects). It makes it possible to draw points, segments and triangles with easy-to-use calls, like in the early days. In addition, it has volumetric primitives that did not exist in OpenGL 2.x (raytraced spheres, tetrahedra, hexahedra, pyramids, prisms) and advanced clipping modes that let you display the interior of the volumetric primitives with optional interpolated colors and texture mapping. It has other features that did not exist in the early days, such as automatic normals in flat shading, automatic mesh outline, normal mapping and indirect texture mapping.
GLUP is part of the geogram_gfx
library, that contains
both GLUP and the GUI (based on Dear ImGUI). All the API
functions of GLUP are declared in
geogram_gfx/GLUP/GLUP.h.
Somebody familiar with OpenGL 2.x will immediately feel "at home"
there.
Immediate mode is the easiest way of drawing things with GLUP (but not the most efficient). It works as follows:
glupBegin(PRIMITIVE);
glupVertex...(...);
glupVertex...(...);
...
glupVertex...(...);
glupEnd();
where PRIMITIVE
is one of GLUP_POINTS
, GLUP_LINES
,
GLUP_TRIANGLES
, GLUP_QUADS
(that work like their OpenGL 2.x
counterparts) and the new volumetric primitives
GLUP_TETRAHEDRA
, GLUP_HEXAHEDRA
, GLUP_PRISMS
, GLUP_PYRAMIDS
,
GLUP_CONNECTORS
and GLUP_SPHERES
.
Vertices will be grouped to form the primitives (2 vertices for each segment, 3 vertices for each triangle...).
Like in OpenGL 2.x, there are several variants of the
glupVertex[suffix]()
function, where [suffix]
indicates the
number of specified coordinates (1
,2
,3
or 4
), the type of the coordinates
(f
for float, d
for double), and whether coordinates are passed as individual parameters
(default) or passed through a pointer (v
). In addition, there are
overloads for vec2
,vec3
and vec4
(no suffix).
The demo_GLUP program demonstrates the different primitives. One may also refer to the Delaunay2d and Delaunay3d tutorials for simple examples. Using GLUP, it is reasonably easy to port old programs, as we did for this sphere eversion demo.
Vertices can be "decorated" with attributes (colors, texture coordinates, normals). The attributes to be used can be specified through:
glupEnable(GLUPtoggle toggle);
glupDisable(GLUPtoggle toggle);
where toggle
can be one of GLUP_VERTEX_COLORS
, GLUP_TEXTURING
, GLUP_VERTEX_NORMALS
(there are other toggles that we will see later).
Vertex attributes can be specified through glupColor...()
,
glupTexCoord...()
and glupNormal...()
(with the same variants as
glupVertex...()
depending on the number and type of the components).
They need to be specified before calling glupVertex...()
(that
keeps the latest specified attributes).
When no vertex color is specified, GLUP uses the default color, specified
by one of the glupSetColor...()
variants. Different colors can be specified
for front-facing (GLUP_FRONT_COLOR
) and back-facing (GLUP_BACK_COLOR
)
polygons. The function glupSetColor...()
can also specify the color used
to draw mesh outlines (GLUP_MESH_COLOR
), when GLUP_DRAW_MESH
is enabled.
Toggle | Description |
---|---|
GLUP_LIGHTING |
Lighting (one directional light only) |
GLUP_VERTEX_COLORS |
Per vertex colors, interpolated (Gouraud) |
GLUP_TEXTURING |
Per vertex texture coordinates |
GLUP_DRAW_MESH |
Automatic mesh outline |
GLUP_CLIPPING |
Clipping (one clipping plane only) |
GLUP_INDIRECT_TEXTURING |
Use texel value as index in a 1D texture |
GLUP_VERTEX_NORMALS |
Per vertex normals, interpolated (Phong) |
GLUP_PICKING |
Generate primitive IDs instead of colors |
GLUP_ALPHA_DISCARD |
Discard fragment if alpha != 1.0 |
GLUP_NORMAL_MAPPING |
Use texel value as normals |
In GLUP, there is a single vector light source, that can be manipulated through the following function:
void GLUP_API glupLightVector3f(GLUPfloat x, GLUPfloat y, GLUPfloat z);
In flat shading mode (that is, GLUP_VERTEX_NORMALS
not enabled), there
is no need of specifying vertex normals, GLUP automatically computes facet
normals and uses them for shading. Under the hood, it is done by the shaders.
The clipping plane equation can be specified using the following function:
void GLUP_API glupClipPlane(const GLUPdouble* eqn);
where eqn
is an array with the four coefficients a
,b
,c
,d
of the
clipping plane ax+by+cz+d=0
.
Advanced glupClipPlane
behaves like its OpenGL 2.x counterpart
(that is, the specified plane equation is pre-multiplied by the inverse
of the current modelview matrix before being stored in the state).
For volumetric primitives, one can specify different clipping modes using:
void GLUP_API glupClipMode(GLUPclipMode mode);
The effect of the different modes in the table below is shown in the figure above.
clip mode | Description |
---|---|
GLUP_CLIP_STANDARD |
(standard OpenGL) just clip the facets |
GLUP_CLIP_WHOLE_CELLS |
display all cells straddling and below clip plane |
GLUP_CLIP_STRADDLING_CELLS |
display only cells that straddle the clip plane |
GLUP_CLIP_CELLS |
display intersection between cells and clip plane |
GLUP texturing uses the standard OpenGL calls. It uses three texture units,
for 1D, 2D and 3D texture (GLUP_TEXTURE_1D_UNIT
, GLUP_TEXTURE_2D_UNIT
and
GLUP_TEXTURE_3D_UNIT
). One needs to call glActiveTexture(GL_TEXTURE0 + unit
before binding the xture. For instance, for 2D texturing, one calls
`glActiveTexture(GL_TEXTURE0 + GLUP_TEXTURE_2D_UINT).
GLUP supports three texture modes: GLUP_TEXTURE_REPLACE
, GLUP_TEXTURE_MODULATE
and GLUP_TEXTURE_ADD
, that specify how the texel value is combined with the
fragment color.
Clearly, sending the vertices and attributes one by one using glupVertex()
is not
the fastest way of drawing things with GLUP. One can use instead the following functions,
that work just like their OpenGL 2.x and 3.x counterparts, with the exception that
GLUP volumetric primitives are supported !
void glupDrawArrays(
GLUPprimitive primitive, GLUPint first, GLUPsizei count
);
void glupDrawElements(
GLUPprimitive primitive, GLUPsizei count,
GLUPenum type, const GLUPvoid* indices
);
These functions work with the currently bound vertex arrays object (VAO). To manipulate VAOs, GLUP has its own functions:
void glupGenVertexArrays(GLUPsizei n, GLUPuint* arrays);
void glupDeleteVertexArrays(GLUPsizei n, const GLUPuint *arrays);
void glupBindVertexArray(GLUPuint array);
GLUPuint glupGetVertexArrayBinding(void);
they work exactly like their OpenGL 3.x counterparts. Under the hood, they directly call OpenGL if VAO are supported, else they emulate them by querying the VBO state.
Using these functions, one can draw a clipping plane in a tetrahedral mesh with interpolated attributes using a single call to OpenGL, that will be directly executed by the GPU, without any communication with the CPU. Internally, everything is done with buffer objects and shaders.
However, not all GPUs support this mode for all primitives. One can check whether array mode works with a given primitive by calling the following function (and then fallback to a slower mode if the function returns GL_FALSE):
GLUPboolean glupPrimitiveSupportsArrayMode(GLUPprimitive prim);
Under the hood, GLUP uses different profile, depending on the detected GPU capabilities:
- The most basic profile (for OpenGL ES and WebGL) does not support array mode. It is so because we need geometry shaders for that.
- If GLSL 1.5 shaders (OpenGL 3.3) are supported, all primitives except
GLUP_HEXES
andGLUP_PYRAMIDS
support array mode. - For GLSL >= 4.4 and OpenGL >= 4.4, all primitives support array mode.
For displaying Geogram Mesh
objects, geogram_gfx
has a
MeshGfx class, that
uses the most efficient available GLUP primitive to draw the mesh (array mode if available for the GPU, or slower fallback).
It can also display mesh attributes.
It is sometimes interesting to deactivate rendering of some primitives based on an attribute. To do that,
GLUP has a GLUP_PRIMITIVE_FILTERING
toggle. It uses a 8bpp buffer texture indexed by primitive Id to
decide whether the primitive should be displayed. This texture is bound to
GLUP_TEXTURE_PRIMITIVE_FILTERING_UNIT
. Here is a simple example that uses this mechanism to display
all primitives with an odd index. Note that MeshGfx
has an easy-to-use API to do that, what follows
explains how it works internally to illustrate GLUP primitive filtering.
Step 1: create the filter that selects the primitives to be displayed
vector<Numeric::uint8> filter(mesh_grob()->facets.nb());
for(index_t f: mesh_grob()->facets) {
filter[f] = f&1;
}
Step 2: create a buffer object and send the filter to it
GLuint buffer=0;
glGenBuffers(1,&buffer);
glBindBuffer(GL_ARRAY_BUFFER,buffer);
glBufferData(
GL_ARRAY_BUFFER, mesh_grob()->facets.nb(),
filter.data(), GL_STATIC_DRAW
);
glBindBuffer(GL_ARRAY_BUFFER,0);
Step 3: create a texture and attach the buffer object to it (texture buffer).
GLuint texture=0;
glGenTextures(1,&texture);
glActiveTexture(
GL_TEXTURE0 + GLUP_TEXTURE_PRIMITIVE_FILTERING_UNIT
);
glBindTexture(GL_TEXTURE_BUFFER, texture);
glTexBuffer(GL_TEXTURE_BUFFER, GL_R8, buffer);
Step 4: Display the surface with primitive filtering
glupEnable(GLUP_PRIMITIVE_FILTERING);
gfx_.draw_surface();
glupDisable(GLUP_PRIMITIVE_FILTERING);
Step 5: Cleanup
glDeleteTextures(1,&texture);
glDeleteBuffers(1,&buffer);