Homework #2 OpenGL and C++

Due Wednesday, September 6, 2017

Development Environment

The main part of this homework is to get your machine configured, and to get a basic understanding of geometry shaders and write a fragment shader that will compute a Gaussian texture centered in the image. The next homework will use this as a paint brush for a simple 2D paint packagel. You will also port your backdrop shaders to a C++ framework.

Below are a list of files to get you started. These provide a complete OpenGL 3.1 core (GLSL 1.3) solution to either set the background to a solid color or a mutli-colored (ugly) backdrop. You will need to download and use (locally) GLEW and FreeGLUT. The specific tasks to get you started are:

  1. Download GLEW and set-up an include and lib directory for all of your OpenGL development.
  2. Download FreeGLUT (Windows Binaries)
  3. Read through this tutorial on the OpenGL Wiki.
  4. Create a project (Empty C++ project in Visual Studio) and add the following files to it:
    1. main.cpp
    2. backdrop.vert
    3. backdrop.frag
  5. Open the Project Properties:
    1. Under C/C++->General add your include dir (with a GL dir inside it) in the Additional Include Directories
    2. Under Linker->General add your lib directory in the Additional Library Directories.
    3. Add the glew32.lib, freeglut.lib and opengl32.lib as Additional Dependencies in Linker->Input
  6. Change the use of GL_QUADS to GL_TRIANGLE_STRIP
  7. Build the project and then copy the glew and freeglut dll's to the debug (and/or Release) directories. Alternatively you can set the output to a bin dir and put the dll's there as well. Make sure you change the working directory as well.
  8. Run your project. Seek Immediate Assistance if machine smokes (or you need help :-).
  9. Read through the man pages for all of the OpenGL API calls that are in main.cpp.

Follow the steps below to get a paint brush texture ready. Here is a sample of what your result should look like:

Gaussian result  

A Gauassian function has a maximum of one at zero and slowly and smoothly decreases to zero at infinity. It has the form:

f(x) = exp(-sigma * x^2) in one dimension and

f(x,y,z,...) = exp(-sigma * r^2) in any higher dimension. The image on the left is obviously two-dimensions.

For this image, we want the value at the edges to be insignificant (aka less than 1/256). You can solve for sigma by letting r=1 at the edge and taking the natural logarithm of 1/256 or some other small number (I think I used 1/512).

 

Step 0

Set the viewport to be the entire window and change the background clear color or use one of your background shaders!

Step 1

Create a fragment shader that computes this value as the intensity. Use a vertex shader that passes an arbitrary color from the vertex shader to the fragment shader (This will be used to understand passing data through the geometry shader later). Also pass a Uniform value to the fragment shader to specify the size of your window / texture. No need to do anything fancy, you can fix it at 256 or whatever. Play with resizing the window and watch the center point move. After playing put the Window size to the value of the uniform to have a consistent view.

Step 2

Now, create a simple pass-thru geometry shader, PassThru.geom, that looks like this:

Simple Geometry Shader
#version 330 core

layout (triangles) in;
layout (triangle_strip) out;
layout (max_vertices = 3) out;
void main(void)
{
       for (int i = 0; i < gl_in.length(); i++) {
              gl_Position = gl_in[i].gl_Position;
              EmitVertex() ;
       }
       EndPrimitive() ;
}

This geometry shader simply walks thru each vertex that is passed in using the data structure gl_in, and emits the vertex without changing its value or position. No other data is passed through. The layout attributes tell OpenGL what is expected of this shader. A good reference for this is Chapter 11 of the 5th Edition of the OpenGL SuperBible. It is available on-line with Safari.

In your main.cpp, let's first change initShaders to createShader that takes as input the filenames for the vertex, fragment and geometry shaders and returns the unsigned int (GUID) of the shader program, like so:

unsigned int createShader(const char* vertexShader, const char* fragmentShader, const char* geometryShader = 0)

I set a default value for the geometryShader so that this routine can be used with only two arguements (no geometry shader) or three. Simply test if geometryShader !=0 and then load, compile and attach the geometry shader. Make these changes, and test that the program runs as in Step 1 without the geometry shader. Now, try to run it with the geometry shader. You will (or should) get black, as the color from the vertex shader is not making it to the fragment shader anymore. Actually, let's add a routine to print out any link errors. Very similar to the printShaderInfo routine, but change shader/Shader everywhere to program/Program. Call this after the link and/or inquiring about the Link status (again change Shader to Program and GL_COMPILE_STATUS to GL_LINK_STATUS. Changing your fragment shader such that it does not use the color (passed in from the vertex shader) should produce a result for you.

Step 3

Now, let's look at how to get data from the vertex shader through the geometry shader to the fragment shader. For this we need the specify the input variable coming from the vertex shader as an array since we will have access to all of the vertices for this primitive. We will output a variable of the same type only not as an array. What happens is that everytime EmitVertex is called, the out data is set for that vertex. This avoids you trying to change vertex one's color while you are working on emitting vertex 2. So, if my variable in the vertex shader is:

out vec4 baseColor;

Then in the geometry shader I would have:

in vec4 baseColor[];

out vec4 color;

The fragment shader would then have:

in vec4 color;

The geometry shader also needs to set the color variable, assuming it varied every vertex, then we would say:

color = baseColor[i];

just before the EmitVertex call in the for loop.

Summary

Make sure you have:

  1. A Gaussian fragment shader
  2. A geometry shader that passes through the positions and any vertex data.
  3. A printProgramInfo routine that prints out any error messages on linking.
  4. A modified initShader / createShader to allow us to build a few programs in the next homework.
  5. Do a Build->Clean and then zip up your work. Delete the .vs folder, any pdb files, etc.
  6. Submit your main program and shaders for the grader.