This repository contains some OpenGL examples. These examples have been written in C++ and use OpenGL 3.3.
These are not tutorials. While I have tried to document the code
as much as possible, the why and how is not explained. If you want a
tutorial on OpenGL, I suggest you try out open.gl or
learn opengl.
The point of these examples is not to teach you how 3D rendering works.
These examples merely serve as a point of reference as to how you can
implement solutions to typical problems in OpenGL 3.3.
Each example resides in its own folder. Code that is often reused, such as code for compiling and linking shader programs, is put into a seperate folder and reused in the different examples.
This code has been written with OS X as a target. No special care has been taken to make sure the code is cross-platform. While the examples might not compile out of the box on your machine, the code can still be reused in your projects. You should be able to just plug it into an OpenGL application that compiles and runs on your machine.
These examples have some dependencies:
Make sure that you have compiled the GLFW dependency for your system as a
static library. You should now have a file called libglfw3.a
. Place this
in a subdirectory lib
of the root folder (on the same level as this README.md
file).
Download the latest release of SOIL. You can try putting the binary in the lib
folder, but this
will most likely fail if your computer is from the last few years. You're best off using the supplied
makefile in the projects/makefile
folder. Note that you need to alter a line if your OS is 64 bit:
change the line that says CXX = gcc
to CXX = gcc -arch x86_64
. Run make
and you will find the new
libSOIL.a
file in the lib
subdirectory of the directory where you downloaded SOIL to.
Download the latest stable release of ASSIMP. You can find this release on their GitHub repository page. Generate build files and compile the static library. Put the resulting libassimp.a
file in the lib
folder where you put the other library files.
Now you can use the command make $EXAMPLE_NAME
(e.g., make hdr
) to compile an example. The
resulting binaries will be placed in the bin
folder.
These examples are available under the MIT License. This is because public domain is not recognized in every country. For more information, read this question and answer.
Licenses of third party libraries are available in the 3rd_party folder.
To compile all examples, you can run make
or make all
.
- Beginner
- Intermediate
- Advanced
- Expert
Level: beginner
Compile: make hello_triangle
Run: cd bin; ./01-hello-triangle.out
This example shows the minimal code needed to render a simple colored triangle.
Level: beginner
Compile: make hello_sprite
Run: cd bin; ./02-hello_sprite.out
This example shows the minimal code needed to render a textured quad. It also uses
glDrawElements
to reduce duplication of vertices.
Level: beginner
Compile: make hello_cube
Run: cd bin; ./03-hello_cube.out
This example introduces 3D transformations and perspective projection. It also uses depth testing to make sure the cube is rendered correctly.
Level: beginner
Compile: make hello_heightmap
Run: cd bin; ./04-hello_heightmap.out
This example introduces the loading and rendering of heightmaps. It also introduces smooth camera movement and looking around with the mouse. The heightmap is rendered without color or texture and in wireframe. Adding color and texture is a good exercise for the beginning reader. Finally, we also introduce backface culling.
Level: beginner
Compile: make hello_mesh
Run: cd bin; ./05-hello_mesh.out
This example uses Assimp to load a mesh. We load the material ourselves instead of using Assimp.
In this example we have moved the shader loading to a Material
class and the data loading to
a Mesh
class. We will be making changes to these classes in later examples if needed.
Level: beginner
Compile: make transparency
Run: cd bin; ./17-transparency.out
This examples shows how to use blending to render transparent objects, by sorting them back-to-front. This is just one way to render transparent objects.
Level: intermediate
Compile: make render_to_texture
Run: cd bin; ./06-render_to_texture.out
We reuse the code from the Hello Mesh example, but this time we render to an off-screen texture, and then render that texture in grayscale to the screen.
Level: intermediate
Compile: make cubemaps
Run: cd bin; ./07-cubemaps.out
This example shows how to create a skybox with cubemaps. It demonstrates loading 6 images into a cubemap, and it demonstrates how to draw the skybox behind everything else.
Level: intermediate
Compile: make instancing
Run: cd bin; ./08-instancing.out
This example draws a large number of objects in a single draw call using instancing.
Model matrices are generated in the main file, but most of the actual implementation
is in the Mesh
class. Make sure to look at the setInstances
and render
methods.
Level: intermediate
Compile: make particles
Run: cd bin; ./09-particles.out
This example uses instanced rendering to render a large amount of particles. The particle system shown is rather basic, only supporting colored particles and no textures. There are also no special particle emitter shapes or particle collision. This would complicate the example and take away from the understanding of simple particle rendering.
Level: intermediate
Compile: make sprite_batching
Run: cd bin; ./10-sprite_batching.out
This example shows how to batch draw calls for sprites. This allows you to render thousands of sprites at a high framerate. This implementation keeps collecting sprites into a buffer until the buffer is full or a different texture is encountered. Other implementations sort the buffer by texture and then render the whole buffer. This one is the easiest to implement, but has more draw calls in a worst case scenario. It does not have the overhead of sorting.
Level: intermediate
Compile: make dear_imgui
Run: cd bin; ./21-dear_imgui.out
This example shows how to use the code provided by the developers of the Dear Imgui library to display some items such as buttons and text on the window in an immediate mode way. The example is very small, which shows how easy it is to set up imgui.
Level: intermediate
Compile: make vertex_shading
Run: cd bin; ./22-vertex_shading.out
Low poly art styles are all the rage these days. Vertex shading was a huge part of the look back before 2004. Switch between smooth (fragment) shading and flat (vertex) shading using E.
Level: advanced
Compile: make morph_target_animation
Run: cd bin; ./11-morph_target_animation.out
Morph Target Animation is one of the two popular methods used to animate 3D objects.
It uses more memory than skeletal animation and is less flexible, but is very easy to implement
and can be used for complex animations such as facial animation. This example focuses on the implementation
of morph target animation and strips down any unnecessary components. It simply animates a growing and shrinking cube.
The methods used in this example can easily be updated to accomodate different meshes, textures, normals, etc.
Level: advanced
Compile: make uniform_buffer_objects
Run: cd bin; ./12-uniform_buffer_objects.out
Uniform Buffer Objects allow you to reuse uniforms easily between shader programs without having to add a lot of extra code. They also allow you to have much more uniforms. We render two cubes with different shader programs, reusing the projection and view uniform matrices.
Level: advanced
Compile: make forward_rendering
Run: cd bin; ./13-forward_rendering.out
Forward Rendering is one popular technique of rendering lit 3D scenes. This example uses the Blinn-Phong shading model to render a lit cube with several lights.
Level: advanced
Compile: make shadows
Run: cd bin; ./14-shadows.out
This is a simple example of how to do shadow mapping with percentage closer filtering for easy dynamic shadows. It only handles directional lights.
Level: advanced
Compile: make billboards
Run: cd bin; ./15-billboards.out
This example shows how to render billboards (2D sprites that are always oriented towards the camera) with a fixed size, or a size that changes depending on the distance between the object and the camera.
Level: expert
Compile: make deferred_shading
Run: cd bin; ./16-deferred_shading.out
This example shows how to render to multiple textures in a single framebuffer at a time and use this to optimize the rendering of many lights. Other optimizations not included are tile based deferred rendering, which can speed up rendering even more.
Level: expert
Compile: make hdr
Run: cd bin; ./18-hdr.out
This example shows how to combine deferred shading with HDR rendering. We render the same asteroid field, but add a single very bright light. We can see that details are still visible in the bright asteroids, as well as in the asteroids that are less lit in the background.
Level: expert
Compile: make additive_lights
Run: cd bin; ./19-additive_lights.out
This example shows how to do additive blending with a pass for each light. This is similar to how the Doom 3 renderer works. The advantages of such an approach compared
to a simple forward rendering approach are simpler shaders and the possibility to reduce the number of fragments affected by a light using a stencil test.
A disadvantage is that we need to do one pass per light, instead of one pass for all lights. This implies a lot of additional draw calls. However the complexity of a single pass can be greatly reduced.
In this example, we also do a Z pre-pass to fill the depth buffer before drawing lights, and we do some
math to calculate the screen space bounding box of a light. This is why this example got the expert label, as additive light blending
is in itself actually quite simple to implement without these extra tricks.
Level: expert
Compile: make point_shadows
Run: cd bin; ./20-point_shadows.out
This example, based on this great tutorial shows how to render shadows for point lights, by creating a cubemap depth texture with a single render pass.
The example is far from optimized, but shows that rendering point light shadows can be quite expensive and should be done only sparingly.