PowerPoint Presentation
Getting Started with OpenGL:
Hello Triangle
Computer Graphics
Instructor: Sungkil Lee
2
Today
• Draw a colored triangle on the screen in a window
Overview
4
Event-Driven Programming
• OpenGL rendering is performed event-driven way
• Similar to GUI programming (e.g., Windows API, QT, Web)
• After initializations, the program enters an infinite event loop.
• The program is terminated with a TERM signal that user sent.
• See an example of Javascript-driven web programming
5
Event-Driven Programming
• An OpenGL program has a similar structure.
• GLFW registers callback functions for various window-related events.
• For each event, GLFW tries to call its registered callback function.
// event-driven callback functions
void update(){ … } // callback for per-frame update
void render(){ … } // callback for per-frame rendering
void reshape(){ … } // callback for window resizing
void keyboard(){ … } // callback for keyboard input
void mouse(){ … } // callback for mouse clicks
void motion(){ … } // callback for mouse movements
// init functions
void init_shaders(){ … }
void user_init(){ … }
// main function
int main(){ … }
6
Rendering Modes for OpenGL
• Immediate mode (old style)
• Put all vertex and attribute data in array
• Send array to GPU to be rendered immediately
• Problem is we would have to send the array over each time when
we need another render of it
• Do not use immediate mode for this course.
• Retained mode (modern style: better in performance)
• Send array over and store on GPU
• Reuse it for multiple renderings
• This is basic in modern-style GL.
7
OpenGL Pipeline in Retained Mode
Fragment
Shader
Vertex
Shader
gl_Position, varyings
Uniform
Variables
Attributes
gl_FragColor
glUniform
glBufferData
GPU
Memory
Vertex
Buffer
CPU
Memory
Vertex
Specification
Frame
Buffer
8
Preview of Shader Program
• Vertex shader in GLSL
• A vertex shader outputs the position of a single input vertex.
• It can also generate an additional output variable (e.g., vertex_color).
• Outputs will be passed as inputs to the next shaders.
in vec3 position; // vertex input attribute (you may use vec3/vec4 too)
in vec3 normal; // vertex input attribute
out vec3 vertex_color; // output of vertex shader
void main()
{
// gl_Position: builtin output variable that must be written as a screen position of vertex
gl_Position = vec4( position, 1 );
…
// another output passed via input variable
vertex_color = normal;
}
Vertex
Shader out
in
gl_Position
9
Preview of Shader Program
• Fragment shader in GLSL
• A fragment shader outputs the color of the single input pixel.
• fragColor is defined in [0,1], which maps later to [0,255] for 8 bpp color.
// the second input from vertex shader
in vec3 vertex_color;
// define output variable to be shown in the display
out vec4 fragColor;
// shader’s global variables, called the uniform variables
// Uniform variables will be shared among all the fragments (like a global variable).
uniform bool bUseSolidColor;
uniform vec4 solid_color;
void main()
{
// fragColor: you must specify it to produce color
fragColor = bUseSolidColor ? solid_color :vec4(vertex_color,1);
}
Fragment
Shader
Uniform Variables
in fragColor
10
Coordinate Systems in OpenGL
• Camera (eye-space) coordinate system
• OpenGL places a camera at the origin in object space pointing in the
negative z direction
• Use right-hand rule (RHS).
11
Coordinate Systems in OpenGL
• Normalized device coordinate (NDC) system
• Canonical view volume (the default viewing volume) is a box centered at
the origin with sides of 2: x: [-1,1], y: [-1,1], z: [-1,1]
• Be careful that NDC uses LHS convention for the definition
• Depth (z-axis) goes far from your eye
• This is intended for depth test, which maintains objects with the smallest
depths as visible.
• c.f., Eye-coordinate space uses the RHS convention .
(-1, -1, -1)
y
z
(0, 0, 0)
(1, 1, 1)
x
Closer look into Code
13
Common Headers
• cgmath.h (or cgmath-min.h)
• slee’s basic math library
• cgut.h
• slee’s OpenGL utility library
• defines common data structures
• defines many utility functions including:
struct vertex; // structure for vertices
struct mesh; // collection of vertices and indices for rendering
cg_create_window();
cg_init_extensions();
cg_create_program();
cg_destroy_window();
14
Global variable definitions
• All the examples will use variables similar to below.
// global constants
const char* window_name= “cgbase – Hello triangle!”;
const char* vert_shader_path = “../bin/shaders/hello.vert”;
const char* frag_shader_path = “../bin/shaders/hello.frag”;
// window objects
GLFWwindow* window = nullptr; // default GLFW window
ivec2 window_size = ivec2( 720, 720 ); // initial window size
// OpenGL objects holding IDs generated from OpenGL
GLuint program = 0; // ID holder for GPU program
GLuint vertex_buffer = 0; // ID holder for vertex buffer
// global variables
int frame = 0; // index of rendering frames
vec4 solid_color = vec4( 1.0f, 0.5f, 0.5f, 1.0f );
bool bUseSolidColor = false;
15
main()
• Initialization
• After initializing GLFW, window is created via cg_create_window()
• To retrieve function points of OpenGL extensions, call cg_init_extensions()
• OpenGL extensions are dynamically obtained from driver (OpenGL32.dll)
• Error checks are not shown here, but in the real example.
void main( int argc, char* argv[] )
{
// initialization
glfwInit();
window = cg_create_window( window_name, window_size.x, window_size.y );
cg_init_extensions( window ); // init OpenGL extensions
16
main()
• Initialization and validations of GLSL program
• cg_create_program() compiles vertex and fragment shaders and links
them together for a single GLSL program.
• See details in cgut.h
• user_init(): user-defined initialization
• will be explained later
…
// initializations and validations of GLSL program
program = cg_create_program( vert_shader_path, frag_shader_path );
user_init(); // user initialization
…
17
main()
• Registration of event callbacks
• glfwSet(*)Callback registers callback functions to the associated window
events.
• There are four types of callbacks:
• window resizing, keyboard press/release, mouse clicks, mouse movements
• Each callback function will be explained later.
…
// register event callbacks
glfwSetWindowSizeCallback( window, reshape ); // window resizing events
glfwSetKeyCallback( window, keyboard ); // keyboard events
glfwSetMouseButtonCallback( window, mouse ); // mouse click inputs
glfwSetCursorPosCallback( window, motion ); // mouse movements
…
18
main()
• Event loop
• When glfwWindowShouldClose() returns true, the loop is terminated.
• glfwPollEvents() processs events and their registered callbacks.
• User-defined update() and render() functions are called in a row.
• These functions are for per-frame update and rendering.
…
// enters rendering/event loop
for( frame=0; !glfwWindowShouldClose(window); frame++ )
{
glfwPollEvents(); // at this time, event callbacks are called
update(); // per-frame update
render(); // per-frame render
}
…
19
main()
• Termination
• user_finalize() do user-defined cleans.
• cg_destroy_window() calls glfwTerminate().
…
// normal termination
user_finalize();
cg_destroy_window(window);
}
20
user_init()
• User-defined initialization are placed here
• print usage of the applications
• initialize basic GL states
• clear color, back face culling, depth test, …
• define host-side vertex attributes
• here, a triangle of three vertices
• create vertex buffers
• This also copies CPU data to GPU, making available from GPU programs.
• any other things that you need to initialize
21
user_init()
• First, show usage on console
• This is highly recommended when you do not have text rendering.
• Initialize GL states
• This is nearly default for all the other examples
bool user_init()
{
// log hotkeys
print_help();
// init GL states
glClearColor( 39/255.0f, 40/255.0f, 34/255.0f, 1.0f ); // set clear color
glEnable( GL_CULL_FACE ); // turn on backface culling
glEnable( GL_DEPTH_TEST ); // turn on depth test
…
return true;
}
22
user_init()
• Vertex definition
• Here, we use norm to store vertex color (for compatibility with other
examples), though it’s intended for normal vectors.
• Create a vertex array on host (CPU) side
struct vertex // will be used for all the course examples
{
vec3 pos; // position
vec3 norm; // normal vector; use this for vertex color for this example
vec2 tex; // texture coordinate; ignore this for the moment
};
…
// create a vertex array for triangles in default view volume: [-1~1, -1~1, -1~1]
vertex vertices[] =
{
{ vec3(0.433f,-0.25f,0), vec3(1,0,0), vec2(0.0f) }, // {position, red color, …}
{ vec3(0.0f,0.5f,0), vec3(0,1,0), vec2(0.0f) }, // {position, green color, …}
{ vec3(-0.433f,-0.25f,0), vec3(0,0,1), vec2(0.0f) } // {position, blue color, …}
…
23
user_init()
• Create vertex buffers
• OpenGL buffer objects allow us to efficiently transfer large amounts of
data to the GPU.
• Vertex buffers are the input to the vertex shader of a GPU program.
• You can access it via buffer ID (e.g., vertex_buffer in the code).
• There is no way of directly accessing GL objects.
bool user_init()
{
…
// create and update vertex buffer
glGenBuffers(1, &vertex_buffer ); // generate a buffer object
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer); // notify GL using the buffer
…
24
user_init()
• Allocate GPU buffer memory
• glBufferData() allocates GPU memory.
• A host-side vertex array is compiled to GPU vertex buffers.
• When nullptr is given, there is no data copy to GPU.
• Remember GL buffers are only the copies of host-side data.
• We use GL_STATIC_DRAW, since our example has constant content.
• When vertices are dynamic, use GL_DYNAMIC_DRAW, but dynamic buffers are
usually not recommend, because they incur significant data transfer overhead
between CPU and GPU.
// create and update vertex buffer
glBufferData( GL_ARRAY_BUFFER, sizeof(vertices), vertices,
GL_STATIC_DRAW ); // allocate GPU memory and copy vertices to it
25
user_finalize ()
• You can locate some clean-up, here.
• But, it’s empty at present.
void user_finalize()
{
// some clean-up code here
};
update() and render()
27
update()
• Update simulation
• Here, we compute rotation based on elapsed time.
• You may put more simulations and transformations here.
void update()
{
// update simulation factor by time
float theta = float(glfwGetTime())*0.5f;
…
}
28
update()
• Uniform variables
• Read-only global variables in GPU program, which can be accessed when
processing any vertices and fragments.
• Uniform variables are similar to the concept of global variables in C.
Fragment shaderVertex shader
out/in
Uniform
Variables
in gl_FragColor
CPU
Variables
glUniform
29
update()
• Update uniform variables
• First query the index of uniform variables using its name.
• Returning -1 means that there is no such name or is not used in the program.
• Declare but non-used variables also returns -1.
• glUniform*() copies CPU variables to GPU’s uniform variable objects.
void update()
{
…
// update uniform variables in vertex/fragment shaders
GLint uloc;
uloc = glGetUniformLocation( program, “theta” );
if(uloc>-1) glUniform1f( uloc, theta );
uloc = glGetUniformLocation( program, “bUseSolidColor” );
if(uloc>-1) glUniform1i( uloc, bUseSolidColor );
uloc = glGetUniformLocation( program, “solid_color” );
if(uloc>-1) glUniform4fv( uloc, 1, solid_color );
}
30
render()
• First, clear the framebuffer (screen) with the clear color
• Clear color was configured in user_init()
• Notify GL that we use our own program
void render()
{
// clear screen (with background color) and clear depth buffer
glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
// notify GL that we use our own program
glUseProgram( program );
…
}
31
render()
• Bind vertex attributes to GPU program
• Now we need to let OpenGL know the structure of our vertex buffers
• First, define vertex attribute information of their names and memory size
in bytes.
// bind vertex attributes to your shader program
const char* vertex_attrib[] = { “position”, “normal”, “texcoord” };
size_t attrib_size[] =
{ sizeof(vertex().pos), sizeof(vertex().norm), sizeof(vertex().tex) };
32
render()
• Bind vertex attributes to GPU program
• glGetAttribLocation() retrieves the index of each attribute using its name in
vertex shader. If location is -1, it may not be used or not exist.
• After enabling the attribute using glEnableVertexAttribArray(), connect the
our vertex buffer object to the vertex attribute location using glBindBuffer()
for( size_t k=0,byte_offset=0; k<3; k++, byte_offset+=attrib_size[k-1] ) { GLuint loc = glGetAttribLocation( program, vertex_attrib[k] ); if(loc>=kn) continue; // -1 in GLuint == 0xFFFFFFFF
glEnableVertexAttribArray( loc );
glBindBuffer( GL_ARRAY_BUFFER, vertex_buffer );
…
}
}
33
render()
• Bind vertex attributes to GPU program
• We notify GL the memory layout of vertex buffer.
• Memory layout of C/C++ structures
glVertexAttribPointer(
loc, // attribute location
attrib_size[k]/sizeof(GLfloat), // number of elements (e.g., 3 for vec3)
GL_FLOAT, // type of elements (e.g., float for vec3)
GL_FALSE, // fixed-point normalization; never use it
sizeof(vertex), // stride: size of structure
(GLvoid*) byte_offset ); // byte offset of each attribute
pos
norm
vertices[0]
stride = sizeof(vertex)
= 32 bytes
byte_offset: 0
byte_offset: 12 = sizeof(vertex().pos)
byte_offset: 24 = sizeof(vertex().pos) + sizeof(vertex().norm)
tex
34
render()
• Finally trigger GPU program by calling
• glDrawArrays( GL_TRIANGLES, 0, 3 )
• Double-buffer swapping
• In double buffering, we are filling pixels in the back buffer, but we see the
image of the front buffer.
• glfwSwapBuffers() notifies to GLFW to swap back and front buffers.
• At this point, the system waits for vertical refresh of a monitor.
void render()
{
…
// render vertices: trigger shader programs to process vertex data
glDrawArrays(GL_TRIANGLES,0,3); // (topology, start offset, no. vertices)
// swap front and back buffers, and display to screen
glfwSwapBuffers( window );
}
Callback Functions
36
reshape()
• callback for when a window is resized.
void reshape( GLFWwindow* window, int width, int height )
{
// set current viewport in pixels (win_x, win_y, win_width, win_height)
// viewport: the window area that are affected by rendering
window_size = ivec2(width,height);
glViewport( 0, 0, width, height );
}
37
keyboard()
• callback for when a user pressed/released a key.
• You can handle all the low-level keyboard events here.
• key is defined by GLFW and action indicates the press/release
• mods are logical OR of ctrl/shift/alt modifier.
void keyboard( GLFWwindow* window, int key, int scancode, int action, int mods )
{
if(action==GLFW_PRESS)
{
if(key==GLFW_KEY_ESCAPE||key==GLFW_KEY_Q) glfwSetWindowShouldClose(window,GL_TRUE);
else if(key==GLFW_KEY_H||key==GLFW_KEY_F1) print_help();
else if(key==GLFW_KEY_D) …
}
else if(action==GLFW_RELEASE)
{
…
}
}
38
mouse()/motion()
• mouse(): callback for mouse button clicks
• button indicates which button is pressed/released.
• action can be either of GLFW_PRESS or GLFW_RELEASE
• The mouse position can be found using glfwGetCursorPos()
• motion(): callback for mouse movements
void mouse( GLFWwindow* window, int button, int action, int mods )
{
if( button==GLFW_MOUSE_BUTTON_LEFT && action==GLFW_PRESS )
{
dvec2 pos; glfwGetCursorPos( window, &pos.x, &pos.y );
printf( “> Left mouse button pressed at (%d, %d)\n”, int(pos.x), int(pos.y) );
}
}
void motion( GLFWwindow* window, double x, double y )
{
}
OpenGL Shader Programming
40
Shader Programs
• Create shader program
• “hello.vert” and “hello.frag” are the program sources of GPU programs.
• Programs are specified in terms of vertex and fragment shaders.
• Shader sources are complied/linked at run time.
// initializations and validations of GLSL program
program = cg_create_program( vert_shader_path, frag_shader_path );
Vertex
shader
Fragment
shader
Vertices Pixels
Primitive
assembler
Clipper Rasterizer
41
OpenGL Shader Execution Model
hello.vert
Application OpenGL Driver
Shader object
Compile
hello.frag
Program object
Linker
Graphics HW
Provided by application developer using OpenGL API
Provided by graphics hardware vender (NVIDIA, AMD, and Intel)
Shader object
42
Vertex and Fragment Shaders
• A vertex processor transforms a single input vertex at a time.
• But, this is also performed in parallel by multiple vertex processors.
• Then, they are combined to primitives (here, to triangles).
• Then, the rasterizer converts the primitives to pixels on the screen.
• A fragment processor processes a single fragment at a time.
• This is also performed in parallel by multiple fragment processors.
Vertex
shader
Fragment
shader
Vertices Pixels
Primitive
assembler
Clipper Rasterizer
43
Example Data Flow: Revisited
vec3(0.5,-0.5,0)
vec3(1,0,0)
vec2(0,0)
vec3(-0.5,-0.5,0)
vec3(0,0,1)
vec2(0,0)
vec3(0,0.6,0)
vec3(0,1,0)
vec2(0,0)
fragment fragment fragment fragment… fragment
vertex vertex vertex
Vertex
shader
Fragment
shader
Primitive
assembler
Clipper
Rasterizer
Vertex
shader
Vertex
shader
Fragment
shader
Fragment
shader
Fragment
shader
Fragment
shader
44
Vertex Shader: hello.vert
• A vertex shader outputs the position of the single vertex
#version 130 // use “300 es” or “320 es” for OpenGL ES
// input attributes of vertices
in vec3 position;
in vec3 normal; // we are using this for vertex color
// the second output = input to fragment shader
out vec3 vertex_color;
// uniform variables
uniform float theta;
void main()
{
// gl_Position: a built-in output variable that should be written in main()
gl_Position = vec4( position, 1 );
// rotation vector
vec2 r = vec2(cos(theta),sin(theta));
// rotate the vertices
gl_Position.x = dot( position.xy, vec2(r.x,-r.y) );
gl_Position.y = dot( position.xy, r.yx ); // swizzling for easy access
// another output passed via varying variable
vertex_color = normal; // pass the color in norm to the vertex color output
}
45
Fragment Shader: hello.frag
• A fragment shader outputs the color of the single input pixel.
• Pixel output (fragColor) is defined in [0,1], which maps later to [0,255] for
8-bit color depth.
#version 130 // use “300 es” or “320 es” for OpenGL ES
// the second input from vertex shader
in vec3 vertex_color;
// define pixel output variable instead of old-style gl_FragColor
out vec4 fragColor;
// shader’s global variables, called the uniform variables
uniform bool bUseSolidColor;
uniform vec4 solid_color;
void main()
{
fragColor = bUseSolidColor ? solid_color : vec4(vertex_color,1);
}
46
Fragment Shader: hello.frag
• (OpenGL ES only) Precision modifier:
• We may use faster arithmetics for floating-point numbers by providing
hints in vertex/fragment shaders .
• This is quite useful for OpenGL ES for mobile platform.
• Possible options and usage:
• highp: vertex positions
• mediump: normal vectors / texture coordinates
• lowp: colors
precision mediump float;
…
47
Final Output
Any questions on GL or GLSL?