Overview of this book

OpenGL is the leading cross-language, multi-platform API used by masses of modern games and applications in a vast array of different sectors. Developing graphics with OpenGL lets you harness the increasing power of GPUs and really take your visuals to the next level. OpenGL Development Cookbook is your guide to graphical programming techniques to implement 3D mesh formats and skeletal animation to learn and understand OpenGL. OpenGL Development Cookbook introduces you to the modern OpenGL. Beginning with vertex-based deformations, common mesh formats, and skeletal animation with GPU skinning, and going on to demonstrate different shader stages in the graphics pipeline. OpenGL Development Cookbook focuses on providing you with practical examples on complex topics, such as variance shadow mapping, GPU-based paths, and ray tracing. By the end you will be familiar with the latest advanced GPU-based volume rendering techniques.
OpenGL Development Cookbook
Credits
www.PacktPub.com
Preface
Mesh Model Formats and Particle Systems
Index

Doing a ripple mesh deformer using the vertex shader

In this recipe, we will deform a planar mesh using the vertex shader. We know that the vertex shader is responsible for outputting the clip space position of the given object space vertex. In between this conversion, we can apply the modeling transformation to transform the given object space vertex to world space position.

For this recipe, we assume that the reader knows how to set up a simple triangle on screen using a vertex and fragment shader as detailed in the previous recipe. The code for this recipe is in the `Chapter1\RippleDeformer` directory.

How to do it…

We can implement a ripple shader using the following steps:

1. Define the vertex shader that deforms the object space vertex position.

```#version 330 core
layout(location=0) in vec3 vVertex;
uniform mat4 MVP;
uniform float time;
const float amplitude = 0.125;
const float frequency = 4;
const float PI = 3.14159;
void main()
{
float distance = length(vVertex);
float y = amplitude*sin(-PI*distance*frequency+time);
gl_Position = MVP*vec4(vVertex.x, y, vVertex.z,1);
}```
2. Define a fragment shader that simply outputs a constant color.

```#version 330 core
layout(location=0) out vec4 vFragColor;
void main()
{
vFragColor = vec4(1,1,1,1);
}```
3. Load the two shaders using the `GLSLShader` class in the `OnInit()` function.

```shader.LoadFromFile(GL_VERTEX_SHADER, "shaders/shader.vert");
4. Create the geometry and topology.

```int count = 0;
int i=0, j=0;
for( j=0;j<=NUM_Z;j++) {
for( i=0;i<=NUM_X;i++) {
vertices[count++] = glm::vec3( ((float(i)/(NUM_X-1)) *2-1)* HALF_SIZE_X, 0, ((float(j)/(NUM_Z-1))*2-1)*HALF_SIZE_Z);
}
}
GLushort* id=&indices[0];
for (i = 0; i < NUM_Z; i++) {
for (j = 0; j < NUM_X; j++) {
int i0 = i * (NUM_X+1) + j;
int i1 = i0 + 1;
int i2 = i0 + (NUM_X+1);
int i3 = i2 + 1;
if ((j+i)%2) {
*id++ = i0; *id++ = i2; *id++ = i1;
*id++ = i1; *id++ = i2; *id++ = i3;
} else {
*id++ = i0; *id++ = i2; *id++ = i3;
*id++ = i0; *id++ = i3; *id++ = i1;
}
}
}```
5. Store the geometry and topology in the buffer object(s).

```glGenVertexArrays(1, &vaoID);
glGenBuffers(1, &vboVerticesID);
glGenBuffers(1, &vboIndicesID);
glBindVertexArray(vaoID);
glBindBuffer (GL_ARRAY_BUFFER, vboVerticesID);
glBufferData (GL_ARRAY_BUFFER, sizeof(vertices), &vertices[0], GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboIndicesID);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), &indices[0], GL_STATIC_DRAW);```
6. Set up the perspective projection matrix in the resize handler.

`P = glm::perspective(45.0f, (GLfloat)w/h, 1.f, 1000.f);`
7. Set up the rendering code to bind the `GLSLShader` shader, pass the uniforms and then draw the geometry.

```void OnRender() {
time = glutGet(GLUT_ELAPSED_TIME)/1000.0f * SPEED;
glm::mat4 T=glm::translate(glm::mat4(1.0f), glm::vec3(0.0f, 0.0f, dist));
glm::mat4 Rx= glm::rotate(T,  rX, glm::vec3(1.0f, 0.0f, 0.0f));
glm::mat4 MV= glm::rotate(Rx, rY, glm::vec3(0.0f, 1.0f, 0.0f));
glm::mat4 MVP= P*MV;
glDrawElements(GL_TRIANGLES,TOTAL_INDICES,GL_UNSIGNED_SHORT,0);
glutSwapBuffers();
}```
8. Delete the shader and other OpenGL objects.

```void OnShutdown() {
glDeleteBuffers(1, &vboVerticesID);
glDeleteBuffers(1, &vboIndicesID);
glDeleteVertexArrays(1, &vaoID);
}```

How it works…

In this recipe, the only attribute passed in is the per-vertex position (`vVertex`). There are two uniforms: the combined modelview projection matrix (`MVP`) and the current time (`time`). We will use the `time` uniform to allow progression of the deformer so we can observe the ripple movement. After these declarations are three constants, namely `amplitude` (which controls how much the ripple moves up and down from the zero base line), `frequency` (which controls the total number of waves), and `PI` (a constant used in the wave formula). Note that we could have replaced the constants with uniforms and had them modified from the application code.

Now the real work is carried out in the main function. We first find the distance of the given vertex from the origin. Here we use the `length` built-in GLSL function. We then create a simple sinusoid. We know that a general sine wave can be given using the following function:

Here, `A` is the wave amplitude, `f` is the frequency, `t` is the time, and `φ` is the phase. In order to get our ripple to start from the origin, we modify the function to the following:

In our formula, we first find the distance (`d`) of the vertex from the origin by using the Euclidean distance formula. This is given to us by the `length` built-in GLSL function. Next, we input the distance into the `sin` function multiplying the distance by the frequency (`f`) and (`π`). In our vertex shader, we replace the phase (`φ`) with time.

```#version 330 core
layout(location=0) in vec3 vVertex;
uniform mat4 MVP;
uniform float time;
const float amplitude = 0.125;
const float frequency = 4;
const float PI = 3.14159;
void main()
{
float distance = length(vVertex);
float y = amplitude*sin(-PI*distance*frequency+time);
gl_Position = MVP*vec4(vVertex.x, y, vVertex.z,1);
}```

After calculating the new `y` value, we multiply the new vertex position with the combined modelview projection matrix (`MVP`). The fragment shader simply outputs a constant color (in this case white color, `vec4(1,1,1,1)`).

```#version 330 core
layout(location=0) out vec4 vFragColor;
void main()
{
vFragColor = vec4(1,1,1,1);
}```

There's more

Similar to the previous recipe, we declare the `GLSLShader` object in the global scope to allow maximum visibility. Next, we initialize the `GLSLShader` object in the `OnInit()` function.

```shader.LoadFromFile(GL_VERTEX_SHADER, "shaders/shader.vert");

The only difference in this recipe is the addition of an additional uniform (`time`).

We generate a simple 3D planar grid in the XZ plane. The geometry is stored in the vertices global array. The total number of vertices on the X axis is stored in a global constant `NUM_X`, whereas the total number of vertices on the Z axis is stored in another global constant `NUM_Z`. The size of the planar grid in world space is stored in two global constants, `SIZE_X` and `SIZE_Z`, and half of these values are stored in the `HALF_SIZE_X` and `HALF_SIZE_Z` global constants. Using these constants, we can change the mesh resolution and world space size.

The loop simply iterates `(NUM_X+1)*(NUM_Z+1)` times and remaps the current vertex index first into the `0` to `1` range and then into the `-1` to `1` range, and finally multiplies it by the `HALF_SIZE_X` and `HALF_SIZE_Z` constants to get the range from `–HALF_SIZE_X` to `HALF_SIZE_X` and `–HALF_SIZE_Z` to `HALF_SIZE_Z`.

The topology of the mesh is stored in the indices global array. While there are several ways to generate the mesh topology, we will look at two common ways. The first method keeps the same triangulation for all of the mesh quads as shown in the following screenshot:

This sort of topology can be generated using the following code:

```GLushort* id=&indices[0];
for (i = 0; i < NUM_Z; i++) {
for (j = 0; j < NUM_X; j++) {
int i0 = i * (NUM_X+1) + j;
int i1 = i0 + 1;
int i2 = i0 + (NUM_X+1);
int i3 = i2 + 1;
*id++ = i0; *id++ = i2; *id++ = i1;
*id++ = i1; *id++ = i2; *id++ = i3;
}
}```

The second method alternates the triangulation at even and odd iterations resulting in a better looking mesh as shown in the following screenshot:

In order to alternate the triangle directions and maintain their winding order, we take two different combinations, one for an even iteration and second for an odd iteration. This can be achieved using the following code:

```GLushort* id=&indices[0];
for (i = 0; i < NUM_Z; i++) {
for (j = 0; j < NUM_X; j++) {
int i0 = i * (NUM_X+1) + j;
int i1 = i0 + 1;
int i2 = i0 + (NUM_X+1);
int i3 = i2 + 1;
if ((j+i)%2) {
*id++ = i0; *id++ = i2; *id++ = i1;
*id++ = i1; *id++ = i2; *id++ = i3;
} else {
*id++ = i0; *id++ = i2; *id++ = i3;
*id++ = i0; *id++ = i3; *id++ = i1;
}
}
}```

After filling the vertices and indices arrays, we push this data to the GPU memory. We first create a vertex array object (`vaoID`) and two buffer objects, the `GL_ARRAY_BUFFER` binding for vertices and the `GL_ELEMENT_ARRAY_BUFFER` binding for the indices array. These calls are exactly the same as in the previous recipe. The only difference is that now we only have a single per-vertex attribute, that is, the vertex position (`vVertex`). The `OnShutdown()` function is also unchanged as in the previous recipe.

The rendering code is slightly changed. We first get the current elapsed time from freeglut so that we can move the ripple deformer in time. Next, we clear the color and depth buffers. After this, we set up the modelview matrix. This is carried out by using the matrix transformation functions provided by the `glm` library.

```glm::mat4 T=glm::translate(glm::mat4(1.0f),
glm::vec3(0.0f, 0.0f, dist));
glm::mat4 Rx= glm::rotate(T,  rX, glm::vec3(1.0f, 0.0f, 0.0f));
glm::mat4 MV= glm::rotate(Rx, rY, glm::vec3(0.0f, 1.0f,  0.0f));
glm::mat4 MVP= P*MV;```

Note that the matrix multiplication in `glm` follows from right to left. So the order in which we generate the transformations will be applied in the reverse order. In our case the combined modelview matrix will be calculated as `MV = (T*(Rx*Ry))`. The translation amount, `dist`, and the rotation values, `rX` and `rY`, are calculated in the mouse input functions based on the user's input.

After calculating the modelview matrix, the combined modelview projection matrix (`MVP`) is calculated. The projection matrix (`P`) is calculated in the `OnResize()` handler. In this case, the perspective projection matrix is used with four parameters, the vertical fov, the aspect ratio, and the near and far clip plane distances. The `GLSLShader` object is bound and then the two uniforms, `MVP` and `time` are passed to the shader program. The attributes are then transferred using the `glDrawElements` call as we saw in the previous recipe. The `GLSLShader` object is then unbound and finally, the back buffer is swapped.

In the ripple deformer main function, we attach two new callbacks; `glutMouseFunc` handled by the `OnMouseDown` function and `glutMotionFunc` handled by the `OnMouseMove` function. These functions are defined as follows:

```void OnMouseDown(int button, int s, int x, int y) {
if (s == GLUT_DOWN)  {
oldX = x;
oldY = y;
}
if(button == GLUT_MIDDLE_BUTTON)
state = 0;
else
state = 1;
}```

This function is called whenever the mouse is clicked in our application window. The first parameter is for the button which was pressed (`GLUT_LEFT_BUTTON` for the left mouse button, `GLUT_MIDDLE_BUTTON` for the middle mouse button, and `GLUT_RIGHT_BUTTON` for the right mouse button). The second parameter is the state which can be either `GLUT_DOWN` or `GLUT_UP`. The last two parameters are the `x` and `y` screen location of the mouse click. In this simple example, we store the mouse click location and then set a state variable when the middle mouse button is pressed.

The `OnMouseMove` function is defined as follows:

```void OnMouseMove(int x, int y) {
if (state == 0)
dist *= (1 + (y - oldY)/60.0f);
else {
rY += (x - oldX)/5.0f;
rX += (y - oldY)/5.0f;
}
oldX = x; oldY = y;
glutPostRedisplay();
}```

The `OnMouseMove` function has only two parameters, the `x` and `y` screen location where the mouse currently is. The mouse move event is raised whenever the mouse enters and moves in the application window. Based on the state set in the `OnMouseDown` function, we calculate the zoom amount (`dist`) if the middle mouse button is pressed. Otherwise, we calculate the two rotation amounts (`rX` and `rY`). Next, we update the `oldX` and `oldY` positions for the next event. Finally we request the freeglut framework to repaint our application window by calling `glutPostRedisplay()` function. This call sends the repaint event which re-renders our scene.

In order to make it easy for us to see the deformation, we enable wireframe rendering by calling the `glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)` function in the `OnInit()` function.

Tip

There are two things to be careful about with the `glPolygonMode` function. Firstly, the first parameter can only be `GL_FRONT_AND_BACK` in the core profile. Secondly, make sure that the second parameter is named `GL_LINE` instead of `GL_LINES` which is used with the `glDraw*` functions. To disable the wireframe rendering and return to the default fill rendering, change the second parameter from `GL_LINE` to `GL_FILL`.

Running the demo code shows a ripple deformer propagating the deformation in a mesh grid as shown in the following screenshot. Hopefully, this recipe should have cleared how to use vertex shaders, especially for doing per-vertex transformations.