Introduction to OpenGL#
OpenGL#
OpenGL is a cross-language, cross-platform application programming interface (API) for rendering 2D and 3D vector graphics. The API is typically used to interact with a graphics processing unit (GPU), to achieve hardware-accelerated rendering.
GLUT and FreeGLUT#
The OpenGL Utility Toolkit (GLUT) is a library of utilities for OpenGL programs, which primarily perform system-level I/O with the host operating system. Functions performed include window definition, window control, and monitoring of keyboard and mouse input. Routines for drawing a number of geometric primitives (both in solid and wireframe mode) are also provided, including cubes, spheres and the Utah teapot. GLUT also has some limited support for creating pop-up menus.
FreeGLUT is an open-source alternative to the OpenGL Utility Toolkit (GLUT) library. GLUT (and hence FreeGLUT) allows the user to create and manage windows containing OpenGL contexts on a wide range of platforms and also read the mouse, keyboard and joystick functions. FreeGLUT is intended to be a full replacement for GLUT, and has only a few differences.
Freeglut has some limitations comparing to GLFW [1]. However, in this course, we only need to use freeglut to draw some simple animations. Therefore, we won’t use more complicated libraries.
The freeglut project can be found on this repository.
Getting Started#
Linux#
Install glu
and freeglut
library.
MacOS#
Maybe you don’t need to install.
Windows#
If you are using MSYS2
, you can try:
pacman -S mingw-w64-x86_64-freeglut
Hello Teapot#
In this part, we will try to figure out how to write a Hello world program for OpenGL.
Copy the following code and save it to main.cpp
.
/* macos */
#include <GLUT/glut.h>
/* windows */
#include <GL/freeglut.h>
#include <windows.h>
/* linux */
#include <GL/freeglut.h>
void display() {
/* clear window */
glClear(GL_COLOR_BUFFER_BIT);
/* draw scene */
glutWireTeapot(.5);
/* flush drawing routines to the window */
glFlush();
}
int main(int argc, char *argv[]) {
/* initialize GLUT, using any command line parameters passed to the program */
glutInit(&argc, argv);
/* setup the size, position, and display mode for new windows */
glutInitWindowSize(500, 500);
glutInitWindowPosition(0, 0);
glutInitDisplayMode(GLUT_RGB);
/* create and set up a window */
glutCreateWindow("hello, teapot!");
glutDisplayFunc(display);
/* tell GLUT to wait for events */
glutMainLoop();
}
First, you have to change the code. You have to add some macros to check the OS.
After that, to compile it, run:
Linux#
g++ -Wall -Wextra -Wpedantic -Werror -std=c++17 -o teapot main.cpp -lglut -lGL -lGLU
MacOS#
g++ -Wall -Wextra -Wpedantic -Werror -std=c++17 -o teapot main.cpp -framework OpenGL -framework GLUT
Windows#
g++ -Wall -Wextra -Wpedantic -Werror -std=c++17 -o teapot main.cpp -lglu32 -lfreeglut -lopengl32
However, to make our life easier, we have to write a Makefile
or CMakeLists.txt
to help us compile.
Makefile
#
Since we have to compile the program on different OS, we have to change the CFLAGS
separately.
But, how to add different CFLAGS
for different OS?
CC=g++
CFLAGS=-Wall -Wextra -Wpedantic -Werror -std=c++17
OUTPUT=teapot
SOURCE=main.cpp
ifeq ($(OS),Windows_NT)
# Windows
CFLAGS+= -lglu32 -lfreeglut -lopengl32
else
# Others
OS := $(shell uname)
ifeq ($(OS),Linux)
# Linux
CFLAGS+= -lglut -lGL -lGLU
endif
ifeq ($(OS),Darwin)
# Mac
CFLAGS+= -framework OpenGL -framework GLUT
endif
endif
.PHONY: clean
teapot:$(SOURCE)
$(CC) $(CFLAGS) -o $(OUTPUT) $(SOURCE)
clean:
rm -rf $(OUTPUT)
run:teapot
./$(OUTPUT)
CMakeLists.txt
#
Things become easier in CMake
. We only need to find the libraries and add them. We don’t have to add the flags by hand.
An example:
cmake_minimum_required(VERSION 3.5)
project(helloteapot)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS "-pedantic -Wall -Wextra -Wvla")
set(OpenGL_GL_PREFERENCE "GLVND")
# Find the OpenGL library
find_package(OpenGL REQUIRED)
include_directories(${OPENGL_INCLUDE_DIR})
# Find the GLUT library
find_package(GLUT REQUIRED)
include_directories(${GLUT_INCLUDE_DIR})
add_executable(teapot main.cpp)
# Link OpenGL, GLUT
target_link_libraries(teapot ${GLUT_LIBRARIES} ${OPENGL_LIBRARY})
Understanding the Code#
glutInit(&argc, argv);
This function will initialize GLUT.
glutInitWindowSize(500, 500);
glutInitWindowPosition(0, 0);
glutInitDisplayMode(GLUT_RGB);
These two lines set the size, position and display mode. Generally, using GLUT_RGB
is enough. But if you want to make your animation more fluent, you can use double buffer mode (GLUT_DOUBLE
).
Hint
What is Double Buffer Mode?
Double buffering provides two complete color buffers for use in drawing. One buffer is displayed while the other buffer is being drawn into. When the drawing is complete, the two buffers are swapped so that the one that was being viewed is now used for drawing. The swap is almost instantaneous, and thus provides a means of performing animation, like the way a sequence of still photographs, rapidly displayed, appear to depict a moving object. [2]
When using GLUT_DOUBLE
, your picture will have two buffers. One of them is always visible, the other one is not. You always render to the buffer that is not currently visible. When you’re done rendering the frame, you swap the two buffers, making the one you just rendered visible. The one that was previously visible is now invisible, and you use it for rendering the next frame. So the role of the two buffers is reversed each frame. [3]
In short, you will need to add glutSwapBuffers();
after glFlush();
.
glutCreateWindow("hello, teapot!");
glutDisplayFunc(display);
glutMainLoop();
glutDisplayFunc
will tell GLUT which function is used to display.
glutMainLoop
will tell GLUT wait for events.
Rendering a triangle#
After running hello teapot, we want to draw something new. The most basic element is triangle.
glColor3f(1, 0, 1);
glBegin(GL_TRIANGLES);
glVertex2d(-0.5, 0.5);
glVertex2d(-1, 0.5);
glVertex2d(-0, -0.5);
glEnd();
First, set the color. Then, we use gl_Begin()
to draw a triangle.
Write your own triangle!
Animation#
I will introduce timer()
and glutMainLoop()
.
See the following example:
void timer(int v) {
if (v != 0) {
return;
}
glutPostRedisplay();
glutTimerFunc(1000 / 60, timer, 0);
}
void display() {
/* clear window */
glClear(GL_COLOR_BUFFER_BIT);
/* draw scene */
int &num = test::get_instance().get_number();
float num_f = static_cast<float>(num);
glColor3f(0, 0, 1);
glBegin(GL_TRIANGLES);
glVertex2d(-0.5, 0.5);
glVertex2d(std::cos(num_f / 10) / 2, std::sin(num_f / 10) / 2);
glVertex2d(-0.5, -0.5);
glEnd();
num++;
glFlush();
glutSwapBuffers();
}
int main(int argc, char *argv[]) {
/* initialize GLUT, using any command line parameters passed to the program */
glutInit(&argc, argv);
/* setup the size, position, and display mode for new windows */
glutInitWindowSize(500, 500);
glutInitWindowPosition(0, 0);
glutInitDisplayMode(GLUT_DOUBLE);
/* create and set up a window */
glutCreateWindow("hello, teapot!");
glutDisplayFunc(display);
glClearColor(1, 1, 1, 1);
/* tell GLUT to wait for events */
timer(0);
glutMainLoop();
}
You can assume that variable num
is somehow a global variable. (it’s only for me to do the demonstration. do not use global variables!)
Singleton Template#
You can see this part after you learn C++ Class.
Define your class:
class test {
private:
test() {}
// Define your methods and variables here
public:
static test &get_instance() {
static test i;
return i;
}
test(test const &) = delete;
void operator=(test const &) = delete;
};
You can get this instance elsewhere by directly using test::get_instance()
.