{{FULL_COURSE}} Homework 4 - OpenGL Basics


Overview ----- You will learn how to work with the OpenGL API by writing most of the C++ code necessary to assemble a basic rendering pipeline. This process will include creating the vertex data for a 2D polygon, writing GLSL shader code to draw your polygon, and setting up the data buffering and draw calls necessary to communicate between CPU and GPU. Supplied Code --------- Click here to access the homework's Github repository. This code provides you with a basic Qt GUI containing a `QOpenGLWidget` to draw your scene. Tips to Get Started ------------- Before you begin writing code, take a moment to familiarize yourself with the base code of the project: The class `MyGL` represents the Qt GUI element that will perform all of your OpenGL rendering. It is given to you with several member variables and functions; carefully read all of the comments that describe these variables and functions so that you understand how to work with them. Whenever your program is run, `MyGL` automatically calls a few functions; after `MyGL`'s constructor has been invoked, `initializeGL()` is then called. We have written code in this function for you that creates a connection between MyGL and the GPU (`initializeOpenGLFunctions()`), and some more code that sets up the VAO, shader program, shader program variable handles, and geometry data. You will have to complete the implementation of some of these functions, as outlined in the instructions below. After `initializeGL()` has completed, `MyGL` then calls `resizeGL()` in order to allow you to receive up-to-date screen width and height data. Finally, `MyGL` calls `paintGL()` once after it is initialized. Should you wish to call `paintGL` again to repaint the screen, you should instead call `MyGL::update()`, which makes the appropriate internal updates to `MyGL`'s `QOpenGLWidget` superclass. __You should not call `paintGL` directly!__ Debugging your OpenGL API calls ---------- We have also provided you with a function to call whenever you want to check the error messages from OpenGL: `MyGL::printGLErrorLog()`, located in `mygl_debugging_functions.cpp`. Call this function just after each OpenGL API call that you think may be causing a problem with your program, then place a breakpoint within the body of the `if` statment of `printGLErrorLog`. Assuming you have called `printGLErrorLog` after __each__ OpenGL API call, then whichever API call was made just before your breakpoint was triggered is the cause of your error. Shader program errors ----------- When writing GLSL shader code, do not place any faith in Qt Creator's in-editor error highlighter. It doesn't know how to parse GLSL code, so it may give you false errors. Instead, in order to check whether or not your shader code compiles, you must run your C++ program. Shader code is compiled when your C++ code is run, not when your project is built. If your vertex or fragment shader have any errors, they will be printed in the console output of your program, and will list the line number in the program at which they exist. Conceptual Questions (Due Tuesday, February 20 at 11:59 PM) ------------- Before you begin the programming portion of this homework assignment, read and answer the following conceptual questions. Your answers should be submitted to Canvas. * (5 pts) In the OpenGL Shading Language (GLSL), what is a uniform variable? What is an "in" variable? What is an "out" variable? How does a vertex shader pass data to a fragment shader? * (5 pts) `MyGL` contains two member variables that are `std::unordered_map`s. What do the `GLuints` that these maps store represent? How are they assigned values in the first place? Help Log (5 points) ------- Maintain a log of all help you receive and resources you use. Make sure the date and time, the names of everyone you work with or get help from, and every URL you use, except as noted in the collaboration policy. Also briefly log your question, bug or the topic you were looking up/discussing. Ideally, you should also the answer to your question or solution to your bug. This will help you learn and provide a useful reference for future assignments and exams. This also helps us know if there is a topic that people are finding difficult. If you did not use external resources or otherwise receive help, please submit a help log that states you did not receive external help. You may submit your help log as an ASCII (plain) text file or as a PDF. Refer to the Policies section of the course web site for more specifications. Code Requirements (Due Thursday, February 22 at 11:59 PM) ------- We __strongly__ recommend making liberal use of the provided debugging function, `printGLErrorLog()`, as you implement each of the following requirements. Make sure to follow the instructions in the "Debugging your OpenGL API calls" section above in order to test your code. It is __not__ advisable to write a large block of code and then try running your program; you might consider test-running after each OpenGL API call you write so that you limit the search space for any errors you encounter. ### Creating vertex buffer object data for your polygon (20 points) ### Implement the function `initializeAndBufferGeometryData()` so that it creates vertex position data representing a convex polygon with at least twenty sides (when rendered, it will look more or less like a circle). Its vertices should fit within in the range [-1,1] in X and Y, and its Z coordinate should be 1 (technically its W since it's going to be 2D geometry). Additionally, write code that sets up index data to triangulate that shape using the fan method, as you did in the previous homework assignment. Finally, make some OpenGL API calls to pass this data from the CPU to the GPU, associated with the CPU-side data handles `bufferPosition` and `bufferIndex`. There are comments provided in this function to help you choose which OpenGL functions you need to call. ### Acquiring shader program variable handles (10 points) ### Implement the function `getHandlesForShaderVariables()` so that it queries your shader program for the unique IDs of the variables written in the provided GLSL files. Store these `GLuint` IDs as the values within the `shaderUniformVariableHandles` and `shaderAttribVariableHandles` members of MyGL. The keys of these maps should be the names of the variables the IDs correspond to. After you add your own code to the vertex and fragment shaders, you might have to come back to this function to write additional variable ID queries for any new `in`s or `uniform`s you add. ### Drawing your geometry (20 points) ### Follow the instructions left in the comments within the function `MyGL::drawGeometry()` draw the vertex data you buffered in `initializeAndBufferGeometryData()`. Refer to the lecture slides on CPU-side OpenGL coding for examples on how to configure the arguments for the functions listed in the comments. Once you have correctly implemented this step, you will be able to run your program and see something like the following shape on your screen (its size may differ depending on how large you made your polygon; this shape has a radius of 0.5): ![](cpu_code_complete.PNG) Note how because the screen has a non-uniform aspect ratio, the polygon is stretched horizontally. You will correct for this in the next step. ### Passing screen dimensions to the shader (5 points) ### Within `resizeGL`, you should call the `glUniform2i` OpenGL API function, giving it the ID handle associated with `"u_ScreenDimensions"`, and the `w` and `h` values passed into `resizeGL`. Then, in `passthrough.vert.glsl`, divide your vertex's X coordinate by your aspect ratio: ```(float(u_ScreenDimensions.x) / u_ScreenDimensions.y)```. Once you have correctly adjusted for the aspect ratio, your image should look something like this: ![](aspect_ratio_corrected.PNG) ### Applying interesting fragment colors (20 points) ### Inside `coloring.frag.glsl`, add code that sets `out_Col` to a color value somehow dependent on the fragment's location on screen, such as by using `gl_FragCoord` or passing the vertex position to be interpolated in the fragment shader via a pair of `in/out` variables. How you choose this coloring is entirely up to you; it just has to be dependent on the fragment location in some way. You could even choose to alter the color based on the `u_Time` variable. ### Applying an animated model matrix (25 points) ### Within `drawGeometry()`, use the OpenGL API function `glUniformMatrix3fv` to set the `u_Model` variable in your vertex shader to a transformation matrix. The transformation should depend on `MyGL's` `currTime` member, which you should increment in `tick()`. The animation can be anything you like; we suggest adding some additional member variables to `MyGL` to help you update the movement, such as a variable for position or velocity. Below is one example of an animation, __but you do NOT need to make anything like it if you have other ideas__. ![](bouncing_ball.gif) Extra Credit (Maximum 15 points) --------- We will grant you extra credit if we deem your interesting fragment colors or animated object to be particularly complex or interesting. Take time to experiment with different functions, both in your vertex shader and fragment shader, to produce visually interesting results. If you want to spend the time, you could even hard-code some sort of artistically-driven animation of your polygon based on the `u_Time` variable that all your shaders support. We have also provided you with the `mouseMoveEvent` function should you wish to add mouse interactivity to your shader. Submission -------- We will grade the code you have pushed to your GitHub repository, so make sure that you have correctly committed all of your files! Once you have pushed your finished project to GitHub, submit a .zip of your project to Canvas, and add a comment with a link to your repository.