This is a final project of CGT 520 class. CGT 520 is a fundamental class for computer graphics, most students do not have any prior knowledge related to OpenGL and GLSL. This is my first formal class related to computer graphics and OpenGL, I tried my best to create a vivid underwater environment using OpenGL/GLSL. I applied what I’ve learnt both in-class and out-class related to real time rendering, used C++, combined with prior knowledge related to digital arts, created this visual technical demo. The final result can be seen below:
In this project, I believe I encountered several challenges which other novice graphics programmer may encounter. These chanllenges are tangent space normal mapping, depth shadow mapping, particle system, pipeline from vertex shader to fragment shader and some special visual effects rendering like transparent objects, animated sea weeds.
Tangent Space Normal Mapping
First is tangent space normal mapping. I found this challenging because I was not so familiar with this. According to my prior experience, I believe that normal mapping has been a standard for CG industry, so it should not be complex to apply it in OpenGL. Indeed, the method itself is easy to understand. You just need to make sure the normals (from the normal map), vertex, camera and light are all converted to the same coordinate system. This coordinate system can be tangent space, eye space, world space or object space. Sarcastically, I got stuck here for quite a long time, because I encountered a bug where I put the mathematic calculation outside the main function in the shader programs, this will result in weird normal mapping effects (the normal mapping is flashing and changing based on camera viewpoint). Like the picture shows below :
Depth Shadow Mapping
I believe that dynamic shadow is perhaps one of the most important features for a 3D visual demo. That’s why I spend quite a lot of time setting up my own shadows. I applied the calssical shadow mapping methods in this project.
The general idea is: in order to create shadows, I first need to know whether objects can be seen in light view (Imagining the light as a special camera and see by this camera), so I need to create a two-pass rendering. First thing I need to do is to render through the light view, in this pass, I never write any solid color values to framebuffer, instead, the only thing I record is the depth value of different objects and I save these depth values to a depth map. After I have this depth map, I can do the normal rendering pass. Basically, I still need to transfer vertex into light coordinate(frame) in this pass, when I do my normal rendering from camera perspective, I could compare the objects’ z value (in light coordinate) with pre-calculate depth map value, and get whether the objects are in shadow or not.
In order to achieve the shadows, I need to follow the following steps:
- Generate a new map which will be used to hold the depth value.
2. Generate a new buffer which will be used to calculate shadow map. After the new buffer is created, then render in this buffer and create shadow map.
3. Render in shadow frame buffer and get the final shadow map.
4. Render in normal frame buffer. And get the dynamic shadows.
Here is a picture shows the result about the shadow (In this image, I also applied poisson blending to the shadows, so the shadow is a little bit blurred):
You can see from the video, my demo scene contains a lot of fish. These fish are created by a simple command gl_DrawElementsInstanced. This draw call will draw a large number of fish at a relatively small performance cost. (In my case, all the fish are just simple geometry pieces, so the performance was not a big deal)
The main challenging for creating these particle fish was to define their behavior. I divided their behaviors into two separated patterns: the first is the fish itself. Fish need to keep swimming, so fish will have the swimming animation (swing their bodies). After the fish can swim, the next thing is to define how fish move forward. In my case, I defined two different patterns, swim in circles and straight forward (The fish are scattered first ). These involve a lot of mathematics. All these animations are loops, which means when fish hit their end, they will be teleported back to their original position, then the next run begins. I applied the similar techniques when creating bubbles.
I learnt a lot from this project and I will dive deeper in the future. Some effects can be fully refined. Future improvements may be：
- Solid fish, instead of flat fish particles
- Ocean background. I mean sea surface
- More natural lights. I only add one directional light in this scene, it’s a little bit boring and flat
- More sophsticated materials for plants and fish