CS148 Project Report

Underwater Simulation
Caustics Pattern, Shadows and interactions on Rigidbody Objects

Eugene Weng Seng Low (elws)
Jieun Veronica Kim (jieunk)
Sasinn Ounpiyodom (Mickey)


For our final project, we proposed to create an underwater scene. We have created a scene submerged underwater. The focal points are on the reflective light patterns - underwater caustics, visible on the ‘sea-floor’. Our scene can be divided into three major components: caustic light patterns seen underwater, the distortion of these patterns as it interacts with rigid body models in the scene and its shadows, and the air bubbles rising to the top of the scene to emphasize the underwater scenery.

The surface of the floor and the models in the sceneview will have a layer of caustic pattern made by the light being shone through the surface of the water. Our resulting image is a scene containing an obj file models that allows the user to see the shift in light and shadows in the caustic rendering as well as some animation through air bubbles as it rises to the surface, resulting in an underwater scene image.


Our main research comes from GPU Gems Chapter 2 written by Juan Guardado (NVIDIA) and Daniel Sánchez-Crespo (Universitat Pompeu Fabra/Novarama Technology) which can be obtained from [1] and also Water mathematics by Habib from [2] Both articles both share the same concept of creating shaders for water and generating caustics.

Ideas and Methods


Our implementation of raytracing is done in two parts: first, the idea of raytracing is applied to the fragment position, so that the rays are sent via fragment position as its origin instead of the source light.

Secondly, we want to find the intensity of the light on that fragment position. The fragment position is located beneath the ocean surface which defines the two split layers. We first trace the fragment position vertically towards the ocean surface. From there we find intersection, and refract the ray accordingly and trace it back towards the light source. We used about 3x3x3 casting of light from the fragment getting more samples per point.

As the figure above, refraction is a huge part of mathematics of caustics. Here, we introduce the next method.


For refraction of the surface, we use the vector form of Snell’s law. Since rays we implement through the raytracing method are vectors, we had to use the vector form using normals and rays to calculate refraction. This equation allows us to calculate the refractions of our rays according to the normals defined on our ocean surface.

Light map

Instead of using a point as the light, we used a circular light map. A texture is fed into the shader and positioned above the ocean to simulate the sun. The intensity of the light is obtained from the value of where it hit, 0 for no intensity and 1 for high intensity.

Sine (literal) waves

Choosing our texture we had to take into considerations of the surfaces we are trying to simulate. The ocean surface is not flat, it has dynamic waves. There are many ways we can go about to simulate the waves, but the method we choose is highlighted below. For our example we use a combination of sine wave functions.

To make our program as versatile as possible, as well as develop with efficiently, we decided to use a texture map. By using a normal map and a bump map of the waves, we have enough information to generate the waves. This allowed us to be as creative as possible and use various kinds of textures to test on, even some crazy ones.

We, however, trying it on a real ocean normal and bump map but we eventually found out that this method does not work on real randomized waves.

With the three techniques above. We manage to project water caustic onto a flat plane without needing to render the ocean volume, surface or even the light.


We used basic shadow mapping and apply it to the objects on the scene. The shadows are only applied to the rigid objects and only apply the shaders to them, ignoring the plane which represent the ocean floor and bubbles.


We did not have time to use particles to generate our bubbles as per original proposal. But to create interactivity as well as animation in our scene, we choose a simpler method of translating a spherical object.To create animation, we translate it using a sinewave with the current time of the frame, allowing it to float up towards to the top of the scene.



Demo Video

Source Code: https://github.com/darkfoxdx/cs148_final_project


  • OpenGL in C++
  • Libraries
    • ASSIMP for models
    • SOIL for textures
    • Header files taken from learnopengl.com
  • Maya for models
  • Blender for sine wave textures


There are many issues with the current result of our rendering.


Our project right now, produces the patterns of caustics but it is not realistic nor is it aesthetically pleasing to view. We have implemented all we proposed but the aesthetics can be improved on. Through experimentation, to get the right sine wave for the ocean or maybe position our lighting correctly we will be able to reach a more realistic caustics pattern as well as render a more mature scene. The effect we want to achieve is similar to the underwater scene on the front page.

Secondly, for improvement, our shadow right now is hard shadows. We haven’t had the time to implement percentage-closer filtering to get soft shadows, and other various techniques to make our shadows more realistic.

Physical Nature

Our caustics and the technique we are using are not physically accurate. Waves on real oceans aren’t sinusoidal but require a large body fluid simulation. They are more rough and dynamic as well as randomized. In order to achieve proper caustics like the one in real life, photon mapping is needed. The idea is instead of bouncing one light ray, we bounce millions of light rays. This would prove problematic for real-time rendering and therefore we did not use this method.



We had to really learn OpenGL and it was really tough since all of us are new to it. We did not know how to properly implement the library and render to texture.


We spent a good amount of time not realizing that the textures we initially used based on real oceans did not work. In order for our raytracing method to work, we required a defined surface that has distinct features to be recognized through normals and height map. The issue was that it is very hard to debug images because it doesn’t output an error. It just shows nothing or something not quite right and we are not sure which part of the program went wrong.

Next Steps

We could experiment with sine wave functions to generate waves to see if it would improve our caustics. We like the idea of using texture for oceans but maybe it is not the right way. We need to explore different techniques to make our caustics better.

Our next steps is probably to implement more mathematics into our work. We could look more into make the caustics more physically accurate and applying more techniques to make the shadows more realistic.

We also could look into how to do photon mapping and make it such that we could render it real-time. Optimizing a million different rays of light to render every second is not an easy task.

Work Division

Eugene Low

I wrote and integrated most of the main engine from various sources and libraries. Most of it I took from learnopengl.com as a foundation, with a lot of modifications. I also mostly worked on the caustics and implemented the shadows. I created the plane and solid model converted using Maya.


I wrote raytracing, experimenting with light intensity displaying on the ocean floor. I also started out with the mathematics, implementing the Snell’s equation for refractions and a circular light. Another contribution was to render and load in the spherical model to animate the translation (bubble flow) per frame time.

Sasinn Ounpiyodom (Mickey)

I created a few extra height maps and normal maps using Blender to test out the refraction. I have also read the implementation of the shadows tutorials. I also did some debugging on the implementation of the caustics shader, the shadows shader and the bubbles movement.


[1] "GPU Gems - Chapter 2. Rendering Water Caustics", Http.developer.nvidia.com, 2016. http://http.developer.nvidia.com/GPUGems/gpugems_ch02.html
[2]"Water mathematics", Habib's Water Shaders, 2008. https://habibs.wordpress.com/water-mathematics/
[2] "OpenGL-rendering of Underwater Caustics", Opengl.org, 2016. https://www.opengl.org/archives/resources/code/samples/mjktips/caustics/
[4] "Tutorial 16 : Shadow mapping", Opengl-tutorial.org, 2016. http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-16-shadow-mapping/
[5] "Doc:2.4/Tutorials/Textures/Maps/Creating a Heightmap from a Plane - BlenderWiki", Wiki.blender.org, 2016. https://wiki.blender.org/index.php/Doc:2.4/Tutorials/Textures/Maps/Creating_a_Heightmap_from_a_Plane
[6] "Render Target Lookup - Epic Wiki", Wiki.unrealengine.com, 2016. https://wiki.unrealengine.com/Render_Target_Lookup