slides

Transcription

slides
Introduction To Modern OpenGL
How OpenGL Works
•
OpenGL uses a series of matrices to control the position and way
primitives are drawn
• OpenGL 1.x - 2.x allows these primitives to be drawn in two ways
• immediate mode
• core profile (this mode uses buffer objects to retain the data and is
sometimes called retained mode)
Immediate Mode
•
In immediate mode the vertices to be processed are sent to the GPU
(Graphics Card) one at a time.
• This means there is a bottleneck between CPU and GPU whilst data is
sent
•
Immediate
mode
has
become
deprecated
as
part
of
OpenGL
3.x
and
is
•
This method is easy to use but not very efficient
not available in OpenGL ES
Core Profile Mode
•
In this mode the data to be drawn is sent to the GPU and stored in graphics
memory.
• A pointer to this object is returned to the user level program and this
object reference is called when drawing etc.
•
This requires a little more setup but does improve the Frame Rate
considerably.
• For most of the examples we will use this mode.
• This is also the way DirectX works
GLSL
•
It
allows
us
to
program
directly
on
the
GPU
and
can
be
used
for
Shading
•
OpenGL Shading Language is a high level shading language based on C
calculations for vertex and fragment based processing.
• We will look at this in more detail later but to use the full OpenGL 3.x
functionality we need to also create a GLSL shader and process the
vertices on the GPU
• Using GLSL we can also manipulate the actual vertices of the system.
Immediate Mode
• OpenGL provides tools for drawing many different primitives, including Points,
Lines Quads etc.
• Most of them are described by one or more vertices.
• In OpenGL we describe a list of vertices by using the glBegin() and
glEnd() functions.
• The argument to the glBegin() function determines how the vertices passed
will be drawn.
• We will not be using this mode and most examples on the web will use this
OpenGL Profiles
• The current OpenGL version is 4.5, it has full compatibility
with OpenGL ES 3.0 for embedded devices
•
Core
Profile
•
Compatibility
profile
•
There are two profiles associated with this version
OpenGL 4.5 Core
•
Released 2014 (see https://www.opengl.org/wiki/
History_of_OpenGL)
• The Core profile is very new and many GPU’s do not fully
support this
•
This profile doesn’t contain any of the immediate mode GL
elements and has no backward compatibility
• Will not be fully available in drivers / GPU cores for a while
OpenGL 4 Compatibility
•
The compatibility profile contains lots of OpenGL immediate mode
elements as well as earlier GLSL structures and components
• This means legacy code will still work with the drivers but may not be
optimal
•
This year the ngl library has been ported to work with only OpenGL
3.2 core profile.
• This needs Mac OSX Lion, linux or window with the latest drivers.
There is now also a port for iOS 5 / OpenGL ES2.0 and Raspberry Pi /
EGL
Compatibility Profile
•
The main problem with the compatibility profile is there are
no clear guidelines on the implementation
• Different vendors behave differently
• Usually the whole system will fall back to the “software
implementation” (not on the GPU)
Programmers view of OpenGL
• To the programmer, OpenGL is a set of commands that allow the
specification of geometric objects in two or three dimensions, together
with commands that control how these objects are rendered into the
framebuffer.
•
A typical program that uses OpenGL begins with calls to open a window
into the framebuffer. (in our case using Qt)
• Once a GL context is allocated, the programmer is free to issue
OpenGL commands.
Programmers view of OpenGL
•
Some calls are used to draw simple geometric objects (i.e. points, line
segments, and polygons)
• Others affect the rendering of these primitives including how they are lit
or coloured and how they are mapped from the user’s two- or threedimensional model space to the two-dimensional screen.
• There are also calls to effect direct control of the framebuffer, such as
reading and writing pixels.
Client Server Model
•
That
is,
a
program
(the
client)
issues
commands,
and
these
commands
•
The model for interpretation of OpenGL commands is client-server.
are interpreted and processed by the OpenGL (the server).
• The server may or may not operate on the same computer as the client.
• In this sense, the GL is “network-transparent.”
• A server may maintain a number of OpenGL contexts, each of which is
an encapsulation of current GL state.
OpenGL Context
• before we can use OpenGL we need to generate an OpenGL
context
•
This
is
not
the
responsibility
of
OpenGL
to
create
this
and
•
This initialises the GL state and other elements of the library
varies in the different Operating systems
• There are many ways to create the context but I will
concentrate on two methods
•
SDL
2
•
Qt 5
OpenGL Context
• Both Qt and SDL work well on multiple platforms to create an
OpenGL context
• Qt is best for GUI / Windowed based systems
• SDL is perfect for games and also has Joystick / Gamepad
interfaces
•
As Modern OpenGL is quite complex I will start with creating
a compatibility profile OpenGL Demo and expand to core
profile once we learn some more OpenGL
SDL
•
To quote the SDL website ( www.libsdl.org)
"Simple DirectMedia Layer is a cross-platform multimedia library designed to provide low
level access to audio, keyboard, mouse, joystick, 3D hardware via OpenGL, and 2D video
framebuffer. It is used by MPEG playback software, emulators, and many popular games,
including the award winning Linux port of "Civilization: Call To Power."
•
SDL is ideal for game so if you want to do some games programming use
SDL over Qt
sdl2-config
•
When sdl is installed a script called sdl2-config is placed in the /usr/bin
directory
•
This script can be run to get the correct build flags for sdl there is also an
sdl-config for earlier SDL 1.x libs as well
sdl2-config --cflags --libs
-I/usr/local/include/SDL2 -I/usr/X11R6/include -D_THREAD_SAFE
-L/usr/local/lib -lSDL2
#include <SDL.h>
#if defined (LINUX) || defined (WIN32)
#include <GL/gl.h>
#include <GL/glu.h>
#endif
#ifdef DARWIN
#include <unistd.h>
#include <OpenGL/gl.h>
#include <OpenGL/glu.h>
#endif
Depending upon platform gl.h is in different directories
use Compiler -D switch to define OS for example
-D DARWIN will use mac includes
SDL Window
// now create our window
SDL_Window *window=SDL_CreateWindow("SDLNGL",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
rect.w/2,
rect.h/2,
SDL_WINDOW_OPENGL |
SDL_WINDOW_RESIZABLE
);
// check to see if that worked or exit
if (!window)
{
SDLErrorExit("Unable to create window");
}
// Create our opengl context and attach it to our window
SDL_GLContext glContext=createOpenGLContext(window);
if(!glContext)
{
SDLErrorExit("Problem creating OpenGL context");
}
// make this our current GL context (we can have more than
one window but in this case not)
SDL_GL_MakeCurrent(window, glContext);
/* This makes our buffer swap syncronized with the monitor'
s vertical refresh */
SDL_GL_SetSwapInterval(1);
}
OpenGL Context
SDL_GLContext createOpenGLContext(SDL_Window *window)
{
// Request an opengl 3.2 context first we setup our attributes, if you need any
// more just add them here before the call to create the context
// SDL doesn't have the ability to choose which profile at this time of writing,
// but it should default to the core profile
// for some reason we need this for mac but linux crashes on the latest nvidia drivers
// under centos
#ifdef DARWIN
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY);
SDL_GL_SetAttribute(SDL_GL_ACCELERATED_VISUAL, 1);
#endif
// set multi sampling else we get really bad graphics that alias
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES,4);
// Turn on double buffering with a 24bit Z buffer.
// You may need to change this to 16 or 32 for your system
// on mac up to 32 will work but under linux centos build only 16
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
// enable double buffering (should be on by default)
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
//
return SDL_GL_CreateContext(window);
}
SDL_GLContext glContext=createOpenGLContext(window);
if(!glContext)
{
SDLErrorExit("Problem creating OpenGL context");
}
// make this our current GL context (we can have more than one window but in this case not)
SDL_GL_MakeCurrent(window, glContext);
/* This makes our buffer swap syncronized with the monitor's vertical refresh */
SDL_GL_SetSwapInterval(1);
// now setup a basic camera for viewing
glClear(GL_COLOR_BUFFER_BIT);
glMatrixMode(GL_PROJECTION);
gluPerspective(45,(float)rect.w/rect.h,0.5,100);
glMatrixMode(GL_MODELVIEW);
gluLookAt(2,2,2,0,0,0,0,1,0);
loop {
... process events
// now clear screen and render
glClear(GL_COLOR_BUFFER_BIT);
// draw a triangle
drawTriangle();
// swap the buffers
SDL_GL_SwapWindow(window);
}
void drawTriangle()
{
static int rot=0;
glPushMatrix();
glRotated(rot,0,1,0);
glBegin(GL_TRIANGLES);
glColor3f(1,0,0);
glVertex3d(0,1,0);
glColor3f(0,1,0);
glVertex3d(1,-1,0);
glColor3f(0,0,1);
glVertex3d(-1,-1,0);
glEnd();
glPopMatrix();
++rot;
}
Qt 5.5
•
•
Qt 4 had a specific GLWidget for creating an OpenGL context
•
Qt5 uses a QSurface as an abstraction for a rendering surface all we have
to do is setup an OpenGL context and create a surface
•
Qt5 also has a number of OpenGL functions encapsulated into Qt itself
we will not be using this as the ngl:: library already has these.
Qt 5 improves on this by extending the basic window to allow the usage
of OpenGL rendering (as well as others)
// create an OpenGL format specifier
QSurfaceFormat format;
// set the number of samples for multisampling
// will need to enable glEnable(GL_MULTISAMPLE); once we
have a context
format.setSamples(4);
#if defined( __APPLE__)
// at present mac osx Mountain Lion only supports GL3.2
// the new mavericks will have GL 4.x so can change
format.setMajorVersion(4);
format.setMinorVersion(1);
#else
// with luck we have the latest GL version so set to that
format.setMajorVersion(4);
format.setMinorVersion(5);
#endif
// now we are going to set to CoreProfile OpenGL so we can'
t use and old Immediate mode GL
format.setProfile(QSurfaceFormat::CoreProfile);
// now set the depth buffer to 24 bits
format.setDepthBufferSize(24);
// set that as the default format for all windows
QSurfaceFormat::setDefaultFormat(format);
}
QOpenGLWindow / Widget
•
We inherit from these classes and implement our own OpenGL
functions.
•
The Window is a stand alone window class with an OpenGL context /
drawing area
•
The Widget is used within another window to allow use to develop GUI
and OpenGL components together.
•
Both are very similar and have the same virtual methods for us to
override.
initalizeGL
•
This is called once when the OpenGL context has been created (after
the window has been created but before the first call to paintGL)
•
We usually use this for initialisation of OpenGL specific things (loading
shaders, creating geo etc) which will need to have an active context.
•
Always start timers in this method and not the ctor as if the timer
function called paint GL it may crash if not valid context is present.
•
This is also where we will need to load extensions etc (usually using
GLEW or ngl::NGLInit::instance();
resizeGL(int _w, int _h)
•
This is merely a convenience function in order to provide an API that is
compatible with QOpenGLWidget. Unlike with QOpenGLWidget, derived
classes are free to choose to override resizeEvent() instead of this
function.
•
Avoid issuing OpenGL commands from this function as there may not be a
context current when it is invoked. If it cannot be avoided, call
makeCurrent().
•
Also worth noting we may need to take into account devicePixelRatio() for
retina displays (for SDL you have to use platform specific function calls)
resizeGL(QResizeEvent *_event)
•
Use this event to set width and height attributes (we can call width() /
height())
•
Usually I set a class attribute then call glViewport each frame before
drawing
m_width=_event->size().width()*devicePixelRatio();
m_height=_event->size().height()*devicePixelRatio();
paintGL
•
This method is called every time update() is called on the window (and is
usually synched to vsynch of the monitor but this can be changed).
•
•
This is usually used for drawing our scene and other operations.
Depending upon advanced OpenGL usage we may need to think about
which context we are using as well as which RenderBuffer etc as Qt uses
it’s own internal states as well.
GL Command Syntax
• GL commands are functions or procedures.
• Various groups of commands perform the same operation but differ in
how arguments are supplied to them.
•
GL
commands
are
formed
from
a
name
which
may
be
followed,
•
To specify the type of parameter GL uses a specific syntax
depending on the particular command, by a sequence of characters
describing a parameter to the command.
•
If present, a digit indicates the required length (number of values) of the
indicated type.
• Next, a string of characters making up one of the type descriptors
GL Command Syntax
• A final v character, if present, indicates that the command
takes a pointer to an array (a vector) of values rather than a
series of individual arguments.
GL Command Syntax
gl Library
1
2
3
4
5
command
name
4f
4 floats
void glUniform4f( int location, float v0, float v1, float v2, float v3 );
void glGetFloatv( enum value, float *data );
v array
pointer
Block Diagram of OpenGL
•
To aid learning we will
concentrate on each of
the elements in turn
• Ignoring the others and
assuming they just work
out of the box without
setup
•
Finally we shall put the
whole system together
Transform
Feedback
Vertex
Data
Vertex
Shading and
Per-Vertex
Operations
Primitive
Assembly
and
Rasterization
Pixel
Data
Texture
Memory
Pixel
Pack / Unpack
Fragment
Shading and
Per-Fragment
Operations
Framebuffer
Block Diagram of OpenGL
Transform
Feedback
Vertex
Data
Vertex
Shading and
Per-Vertex
Operations
Primitive
Assembly
and
Rasterization
Fragment
Shading and
Per-Fragment
Operations
Pixel
Data
Texture
Memory
Pixel
Pack / Unpack
•
Commands enter the GL on the left. Some commands specify
geometric objects to be drawn while others control how the
objects are handled by the various stages. Commands are effectively
sent through a processing pipeline.
• The first stage operates on geometric primitives described by
vertices: points, line segments, and polygons.
•
In this stage vertices may be transformed and lit, followed by
assembly into geometric primitives, which may optionally be used by
the next stage, geometry shading, to generate new primitives.
Framebuffer
Block Diagram of OpenGL
Transform
Feedback
Vertex
Data
Vertex
Shading and
Per-Vertex
Operations
Primitive
Assembly
and
Rasterization
Fragment
Shading and
Per-Fragment
Operations
Framebuffer
Pixel
Data
Texture
Memory
Pixel
Pack / Unpack
•
The final resulting primitives are clipped to a viewing volume in
preparation for the next stage, rasterization.
• The rasterizer produces a series of framebuffer addresses and values
using a two-dimensional description of a point, line segment, or polygon.
•
Each fragment produced is fed to the next stage that performs
operations on individual fragments before they finally alter the
framebuffer.
•
Finally, values may also be read back from the framebuffer or copied
from one portion of the framebuffer to another.
Primitives and Vertices
coordinates
Vertex
Shader
Execution
Shaded
Vertices
Point,
Line Segment, or
Triangle
(Primitive)
Assembly
Point culling,
Line Segment
or Triangle
Clipping
varying
outputs
Generic
Vertex
Attributes
Primitive type
(from DrawArrays or
DrawElements mode)
• In the OpenGL, most geometric objects are drawn by
specifying a series of generic attribute sets using DrawArrays
or one of the other drawing commands.
• Points, lines, polygons, and a variety of related geometric
objects can be drawn in this way.
Rasterization
Primitives and Vertices
• Each vertex is specified with one or more generic vertex attributes.
• Each attribute is specified with one, two, three, or four scalar values.
•
Generic vertex attributes can be accessed from within vertex
shaders and used to compute values for consumption by later
processing stages.
•
For example we may set vertex colour, vertex normals, texture coordinates or generic vertex attributes used by the processing shader
Primitive types
• OpenGL has a number of primitive types that can be specified to
DrawArrays and other primitive drawing commands
•
• Points, Line Strips, Line Loops, Separate Lines,Triangle Strips,Triangle
These are
Fans, Separate Triangles, Lines with adjacency, Line Strips with
Adjacency,Triangles with Adjacency,Triangle Strips with Adjacency,
Separate Patches
• We will investigate these elements later for now we will
concentrate on drawing some points
Vertex Arrays
• Vertex data is placed into arrays that are stored in the server’s
address space (GPU).
•
Blocks of data in these arrays may then be used to specify multiple
geometric primitives through the execution of a single OpenGL
command.
•
The client may specify up to the value of MAX_VERTEX_ATTRIBS
arrays to store one or more generic vertex attributes.
Vertex Arrays
• Vertex arrays are a simple way of storing data for models so that
Vertices, normals and other information may be shared.
•
This allows a more compact representation of data and reduces the
number of calls OpenGL needs to make and the reduction of data
stored.
•
We can create the data in a series of arrays either procedurally or by
loading it from some model format
• The data may then be stored either by downloading it to the GPU or
storing it on the client side and telling the GPU to bind to it.
Example Drawing Points
•
The following example will draw a series of
points to the OpenGL context
• The data is stored on the client side (our
program)
•
We need to create the data (3xGLfloat
values) in an array
• Then tell OpenGL where this data is and
draw it
m_points = new GLfloat[3*s_numPoints];
x
y
z
x
Create data
y
z
.....
Enable Vertex
Arrays
and Draw
Create the Data
GLfloat *m_points;
in OpenGLWindow.h
const static int s_numPoints=10000;
// first we generate random point x,y,z values
m_points = new GLfloat[2*s_numPoints];
for( int i=0; i<2*s_numPoints; ++i)
{
m_points[i]= -1.0f + (float)rand()/((float)
RAND_MAX/(1.0f- -1.0f));
}
in ctor we allocate
some random x,y,z
values
Drawing the Data
void OpenGLWindow::render()
{
// usually we will make this context current and render
m_context->makeCurrent(this);
// clear the screen and depth buffer
glClear(GL_COLOR_BUFFER_BIT| GL_DEPTH_BUFFER_BIT);
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(2, GL_FLOAT, 0, m_points);
glDrawArrays(GL_POINTS,0,s_numPoints);
glDisableClientState(GL_VERTEX_ARRAY);
// finally swap the buffers to make visible
m_context->swapBuffers(this);
tell GL we are using
Vertex Arrays
tell GL where the data
is on the client
}
Now Draw
glVertexPointer
1
void glVertexPointer( GLint size,GLenum type,GLsizei stride,const GLvoid *pointer);
• size :- the number of coordinates per vertex. Must be 2, 3, or 4. The initial
value is 4.
•
type :- the data type of each coordinate in the array. Symbolic constants
GL_SHORT, GL_INT, GL_FLOAT, or GL_DOUBLE are accepted. default
: GL_FLOAT.
•
stride :- the byte offset between consecutive vertices. If stride is 0, the
vertices are understood to be tightly packed in the array. default : 0
• pointer :- a pointer to the first coordinate of the first vertex in the array.
default : 0.
glVertexPointer
•
At
present
it
works
but
the
new
method
of
drawing
is
a
lot
more
•
glVertexPointer has been marked for deprecation
complex and requires different shaders to be developed.
• We usually do not use client side data anyway so this is just serving as an
example before we add to the overall GL construction required for GL
3.x functionality
• We will expand on the new functions later
glDrawArrays
1
void glDrawArrays(
GLenum mode,GLint first,GLsizei count);
• mode :- what kind of primitives to render.
• Symbolic constants GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP,
GL_LINES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN,
GL_TRIANGLES, GL_QUAD_STRIP, GL_QUADS, and GL_POLYGON
are accepted.
•
count
:the
number
of
indices
to
be
rendered.
•
first :- the starting index in the enabled arrays.
ARB and GLEW
•
ARB stands for Architecture review board and are extensions
authorised by the OpenGL standards group
• Most of these extensions are part of the OpenGL driver for the GPU
installed and we need to make link between the driver binary library
and the OpenGL code we are writing
• This process is quite complex and to make it easier we can use GLEW
(not required on Mac OSX)
•
The ngl::Init class contains the following code to do this
Initialising GLEW
1
2
3
4
5
6
7
8
9
10
11
12
13
// we only need glew on linux mac ok (should add a windows ref as well)
#if defined(LINUX) || defined(WIN32)
{
GLenum err = glewInit();
if (GLEW_OK != err)
{
/* Problem: glewInit failed, something is seriously wrong. */
std::cerr<< "Error: "<<glewGetErrorString(err)<<std::endl;
exit(EXIT_FAILURE);
}
std::cerr<<"Status: Using GLEW "<<glewGetString(GLEW_VERSION)<<std::endl;
}
#endif
Adding Extensions
1
2
3
4
5
6
7
8
9
•
// make sure you've included glext.h
extern PFNGLISRENDERBUFFEREXTPROC glIsRenderbufferEXT;
and in one c/cpp file:
PFNGLISRENDERBUFFEREXTPROC glIsRenderbufferEXT;
// now bind the address from the driver to our function pointer
glIsRenderbufferEXT = glXGetProcAddressARB((const GLubyte*)"glIsRenderbufferEXT");
For every function we need to access we have to write some
code similar to the lines above
• This is quite tedious so GLEW does it for us
Adding Colour
•
We
create
a
single
array
of
per-vertex
RGB
values
•
We
then
create
a
pointer
to
this
and
draw
using
the
same
draw
•
The process of adding Colour is similar to that of setting the vertices
command
m_colours = new GLfloat[3*s_numPoints];
for( int i=0; i<3*s_numPoints; ++i)
{
m_colours[i]= 0.0f + (float)rand()/((float)RAND_MAX/(1.0f));
}
Adding Colour
void OpenGLWindow::render()
{
// usually we will make this context current and render
m_context->makeCurrent(this);
// clear the screen and depth buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glVertexPointer(2, GL_FLOAT, 0, m_points);
glColorPointer(3,GL_FLOAT,0,m_colours);
glDrawArrays(GL_POINTS, 0, s_numPoints);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
// finally swap the buffers to make visible
m_context->swapBuffers(this);
}
Other Primitives
void OpenGLWindow::render()
{
// usually we will make this context current and render
m_context->makeCurrent(this);
// clear the screen and depth buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);
glVertexPointer(2, GL_FLOAT, 0, m_points);
glColorPointer(3,GL_FLOAT,0,m_colours);
glDrawArrays(GL_TRIANGLES, 0, s_numPoints/3);
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
// finally swap the buffers to make visible
m_context->swapBuffers(this);
}
Problems
•
Each
frame
the
client
(our
program)
has
to
send
the
data
to
•
Although this method works, it is still slow.
the server (GPU)
• If we increase the s_numPoints constant to about 3000 the
program slows to a halt
•
We really need to create a faster method of doing things
Vertex Buffer Objects
• The idea behind VBOs is to provide regions of memory (buffers) accessible
through identifiers.
• A buffer is made active through binding, following the same pattern as other
OpenGL entities such as display lists or textures.
• Data is effectively stored on the GPU for execution and this greatly increases
the speed of drawing.
VBO Allocation and process
Draw VBO (many times)
Create VBO (once)
Enable Arrays
Get a Buffer ID
Data deleted
once bound
Vertex Data
Bind Buffer
Bind to Buffer
set pointer
Stored on
GPU
draw
disable Arrays
Data Packing
• We can pack the data in a number of ways for passing to the VBO
• The two simplest schemes are
• All Vertex Data - All Normal Data - All Texture Data etc etc
• Alternatively we can pack the data by interleaving the data in a number
of pre-determined GL formats
• For the following examples we will use the 1st format.
Grid
in OpenGLWindow.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
/// @brief a simple draw grid function
/// @param[in] _size the size of the grid (width and height)
/// @param[in] _step sxstep the spacing between grid points
/// @param[out] o_dataSize the size of the buffer allocated
/// @returns a pointer to the allocated VBO
GLuint MakeGrid(
GLfloat _size,
int _steps,
int &o_dataSize
);
/// @brief a pointer to our VBO data
GLuint m_vboPointer;
/// @brief store the size of the vbo data
GLint m_vboSize;
1
2
const static float gridSize=1.5;
const static int steps=24;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// allocate enough space for our verts
// as we are doing lines it will be 2 verts per line
// and we need to add 1 to each of them for the <= loop
// and finally muliply by 12 as we have 12 values per line pair
o_dataSize= (_steps+2)*12;
float *vertexData= new float[o_dataSize];
// k is the index into our data set
int k=-1;
// claculate the step size for each grid value
float step=_size/(float)_steps;
// pre-calc the offset for speed
float s2=_size/2.0f;
// assign v as our value to change each vertex pair
float v=-s2;
// loop for our grid values
for(int i=0; i<=_steps; ++i)
{
// vertex 1 x,y,z
vertexData[++k]=-s2; // x
vertexData[++k]=v; // y
vertexData[++k]=0.0; // z
Vertex 0 x,y,z
Vertex 1 x,y,z
vertexData[]
V0 X
// vertex 2 x,y,z
vertexData[++k]=s2; // x
vertexData[++k]=v; // y
vertexData[++k]=0.0; // z
V0 Y
// vertex 3 x,y,z
vertexData[++k]=v;
vertexData[++k]=s2;
vertexData[++k]=0.0;
V1 Y
// vertex 4 x,y,z
vertexData[++k]=v;
vertexData[++k]=-s2;
vertexData[++k]=0.0;
// now change our step value
v+=step;
}
Creating Data for
vertices
V0 Z
V1 X
V1 Z
.....
Binding the data
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// now we will create our VBO first we need to ask GL for an Object ID
GLuint VBOBuffers;
// now create the VBO
glGenBuffers(1, &VBOBuffers);
// now we bind this ID to an Array buffer
glBindBuffer(GL_ARRAY_BUFFER, VBOBuffers);
// finally we stuff our data into the array object
// First we tell GL it's an array buffer
// then the number of bytes we are storing (need to tell it's a sizeof(FLOAT)
// then the pointer to the actual data
// Then how we are going to draw it (in this case Statically as the data will not change)
glBufferData(GL_ARRAY_BUFFER, o_dataSize*sizeof(GL_FLOAT) , vertexData, GL_STATIC_DRAW);
// now
delete
// now
return
we can delete our client side data as we have stored it on the GPU
[] vertexData;
return the VBO Object pointer to our program so we can use it later for drawing
VBOBuffers;
• Now we bind the data onto the GPU and once this is done we can
delete the client side data as it’s not needed
Building the Grid
• We must have a valid GL context before we can call this function,
and if required we must initialise GLEW
void OpenGLWindow::initialize()
{
m_context->makeCurrent(this);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
m_vboPointer=makeGrid(gridSize,steps,m_vboSize);
}
Drawing the buffer
void OpenGLWindow::render()
{
if(!m_initialized)
{
initialize();
m_initialized=true;
}
// usually we will make this context current and render
m_context->makeCurrent(this);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// enable vertex array drawing
glEnableClientState(GL_VERTEX_ARRAY);
// bind our VBO data to be the currently active one
glBindBuffer(GL_ARRAY_BUFFER, m_vboPointer);
// tell GL how this data is formated in this case 3 floats
tightly packed starting at the begining
// of the data (0 = stride, 0 = offset)
glVertexPointer(3,GL_FLOAT,0,0);
// draw the VBO as a series of GL_LINES starting at 0 in the
buffer and _vboSize*GLfloat
glDrawArrays( GL_LINES, 0, m_vboSize);
// now turn off the VBO client state as we have finished with it
glDisableClientState(GL_VERTEX_ARRAY);
// finally swap the buffers to make visible
m_context->swapBuffers(this);
}
Vertex arrays
• To enable the use of vertex arrays we need very few steps as shown below
1. Invoke the function glEnableClientState(GL_VERTEX_ARRAY); to
activate the vertex-array feature of OpenGL
2. Use the function glVertexPointer to specify the location and data
format for the vertex co-ordinates
3. Display the scene using a routine such as glDrawArraysInstancedARB
A Cube
• This cube has vertex colours and vertex normals
Cube Vertices
1
2
3
4
5
6
7
8
9
// vertex coords array
GLfloat vertices[] = {
1, 1, 1, -1, 1, 1, -1,-1, 1, 1,-1, 1,
1, 1, 1, 1,-1, 1, 1,-1,-1, 1, 1,-1,
1, 1, 1, 1, 1,-1, -1, 1,-1, -1, 1, 1,
-1, 1, 1, -1, 1,-1, -1,-1,-1, -1,-1, 1,
-1,-1,-1, 1,-1,-1, 1,-1, 1, -1,-1, 1,
1,-1,-1, -1,-1,-1, -1, 1,-1, 1, 1,-1
};
//
//
//
//
//
//
v0-v1-v2-v3
v0-v3-v4-v5
v0-v5-v6-v1
v1-v6-v7-v2
v7-v4-v3-v2
v4-v7-v6-v5
V6
V5
•
V1
The array above stores the
vertices for a unit cube in
face order of quads
V0
V7
V4
V2
V3
Vertex Normals
1
2
3
4
5
6
7
8
9
// normal array
GLfloat normals[] = {
0, 0, 1,
1, 0, 0,
0, 1, 0,
-1, 0, 0,
0,-1, 0,
0, 0,-1,
};
0, 0,
1, 0,
0, 1,
-1, 0,
0,-1,
0, 0,
1, 0, 0, 1, 0, 0, 1,
0, 1, 0, 0, 1, 0, 0,
0, 0, 1, 0, 0, 1, 0,
0, -1, 0, 0, -1, 0, 0,
0, 0,-1, 0, 0,-1, 0,
-1, 0, 0,-1, 0, 0,-1
//
//
//
//
//
//
v0-v1-v2-v3
v0-v3-v4-v5
v0-v5-v6-v1
v1-v6-v7-v2
v7-v4-v3-v2
v4-v7-v6-v5
• This array stores the normals for each vertex
Vertex Colour Array
1
2
3
4
5
6
7
8
9
10
// color array
GLfloat colours[] =
{
1,1,1,
1,1,1,
1,1,1,
1,1,0,
0,0,0,
0,0,1,
1,1,0,
1,0,1,
0,1,1,
0,1,0,
0,0,1,
0,0,0,
1,0,0,
0,0,1,
0,1,0,
0,0,0,
1,0,1,
0,1,0,
1,0,1,
0,1,1,
1,1,0,
1,0,0,
1,0,0,
0,1,1
//
//
//
//
//
//
v0-v1-v2-v3
v0-v3-v4-v5
v0-v5-v6-v1
v1-v6-v7-v2
v7-v4-v3-v2
v4-v7-v6-v5
};
• Array of colour values for each vertex these will be
interpolated across the faces
Assigning the data
• In this case we have 3 different arrays which we are going to
combine into one VBO buffer.
•
Vertices
->
Normal
->
Colour
•
First
we
have
to
allocate
enough
space
for
all
3
arrays
•
The data will be packed in the format
void GLWindow::createCube(GLfloat _scale,GLuint &o_vboPointer)
{
// first we scale our vertices to _scale
for(int i=0; i<24*3; ++i)
{
vertices[i]*=_scale;
}
// now create the VBO
glGenBuffers(1, &o_vboPointer);
// now we bind this ID to an Array buffer
glBindBuffer(GL_ARRAY_BUFFER, o_vboPointer);
// this time our buffer is going to contain verts followed by normals
// so allocate enough space for all of them
glBufferData(GL_ARRAY_BUFFER, 72*3*sizeof(GL_FLOAT) , 0, GL_STATIC_DRAW);
// now we copy the data for the verts into our buffer first
glBufferSubData(GL_ARRAY_BUFFER,0,24*3*sizeof(GL_FLOAT),vertices);
// now we need to tag the normals onto the end of the verts
glBufferSubData(GL_ARRAY_BUFFER,24*3*sizeof(GL_FLOAT),24*3*sizeof(GL_FLOAT),normals);
// now we need to tag the colours onto the end of the normals
glBufferSubData(GL_ARRAY_BUFFER,48*3*sizeof(GL_FLOAT),24*3*sizeof(GL_FLOAT),colours);
}
void OpenGLWindow::render()
{
// usually we will make this context current and render
m_context->makeCurrent(this);
GLubyte indices[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23};
// this macro is used to define the offset into the VBO data for our normals etc
// it needs to be a void pointer offset from 0
#define BUFFER_OFFSET(i) ((float *)NULL + (i))
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
static int xrot=0;
static int yrot=0;
glRotatef(xrot++,1,0,0);
glRotatef(yrot++,0,1,0);
// enable vertex array drawing
glEnableClientState(GL_VERTEX_ARRAY);
// enable Normal array
glEnableClientState(GL_NORMAL_ARRAY);
// enable the colour array
glEnableClientState(GL_COLOR_ARRAY);
// bind our VBO data to be the currently active one
glBindBuffer(GL_ARRAY_BUFFER, m_vboPointer);
// we need to tell GL where the verts start
glVertexPointer(3,GL_FLOAT,0,0);
// now we tell it where the nornals are (thes are basically at the end of the verts
glNormalPointer(GL_FLOAT, 0,BUFFER_OFFSET(24*3));
// now we tell it where the colours are (thes are basically at the end of the normals
glColorPointer(3,GL_FLOAT, 0,BUFFER_OFFSET(48*3));
glDrawElementsInstancedARB(GL_QUADS,24,GL_UNSIGNED_BYTE,indices,1);
// now turn off the VBO client state as we have finished with it
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glPopMatrix(); // finally swap the buffers to make visible
m_context->swapBuffers(this);
}
Vertex Array Objects
• Internally ngl:: uses Vertex Array Objects (VAOs) to store and draw all of
the graphical elements
•
ngl::VertexArrayObject class encapsulates all the functionality for creating
these objects and then binding and drawing them
• This class may also be used to batch data for drawing in user programs and
is the main way of accessing drawing in ngl
Vertex Array Object
•
The OpenGL spec defines a VAO as
This extension introduces named vertex array objects which encapsulate vertex array state on the client side. These
objects allow applications to rapidly switch between large sets of array state. In addition, layered libraries can
return to the default array state by simply creating and binding a new vertex array object.
• VAOs are a collection of state, like all OpenGL Objects.
• Unlike texture objects or Buffer Objects,VAOs are pure state objects; they do
not contain any large blocks of data or anything.
•
VAO
If you were to define the VAO state in terms of a C structs, it would look
like the following
struct VertexAttribute
{
bool bIsEnabled = GL_FALSE;
//This is the number of elements in this attribute, 1-4.
int iSize = 4;
unsigned int iStride = 0;
VertexAttribType eType = GL_FLOAT;
bool bIsNormalized = GL_FALSE;
bool bIsIntegral = GL_FALSE;
void * pBufferObjectOffset = 0;
BufferObject * pBufferObj = 0;
};
struct VertexArrayObject
{
BufferObject *pElementArrayBufferObject = NULL;
VertexAttribute attributes[GL_MAX_VERTEX_ATTRIB];
}
VAO
•
Once
allocated
we
can
use
less
commands
to
re-activate
the
state
and
•
VAO basically store the state and attribute information for other buffers
draw (usually 3)
Generate VAO
•
Once
bound
subsequent
operations
are
applied
to
the
currently
bound
•
GenVertexArrays generates n empty vertex array objects
object
GLuint m_vaoID[2];
// Two VAOs allocation
glGenVertexArrays(2, &m_vaoID[0]);
// First VAO setup
glBindVertexArray(m_vaoID[0]);
VOA
glGenBuffers(1, &m_vboID[2]);
glBindBuffer(GL_ARRAY_BUFFER, m_vboID[2]);
glBufferData(GL_ARRAY_BUFFER, 9*sizeof(GLfloat), vert2, GL_STATIC_DRAW);
glVertexAttribPointer((GLuint)0, 3, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
glBindVertexArray(0);
• Normal VBO operations are done now the VAO is bound, all state
allocated in this operations is retained in the VAO
Drawing
// select first VAO
glBindVertexArray(m_vaoID[0]);
// draw first object
glDrawArrays(GL_TRIANGLES, 0, 3);
•
Fist we bind the VAO which will set the stored attribute state into the
current state
• Then draw as usual
ngl::VertexArrayObject
VertexArrayObject
- m_drawMode : GLenum
- m_indicesCount : GLenum
- m_id : GLuint
- m_allocated : bool
- m_indexed : bool
- m_bound : bool
- VertexArrayObject(_mode : GLenum)
- VertexArrayObject(_v : VertexArrayObject)
- ~VertexArrayObject()
+ createVOA(_mode : GLenum ) : VertexArrayObject *
+ bind()
+ unbind()
+ removeVOA()
+ getID() : GLuint
+ isBound() : bool
+ isAllocated() : bool
+ setData(_size : uint, &_data : float, _mode=: GLenum )
+ setIndexedData(_size : uint, &_data : float, _indexSize : uint, &_indexData : GLubyte , _mode: GLenum )
+ setVertexAttributePointer(_id : GLuint,_size : GLint,_type : GLenum, _stride : GLsizei,_dataOffset : uint,_normalise : bool)
+ draw()
+ setNumIndices(_n : GLuint)
ngl::VertexArrayObject
•
First thing you will notice about this class is that the constructor and
destructor are private
• This means it is not possible for the user to instantiate the class directly
• We need to do this as the lifetime of the VAO needs to be controlled and
not be destroyed when it goes out of scope
•
We also need to control the construction so it passes the correct values
to the VAO and this is not possible using a ctor
Building a VAO
•
per
vertex
data
•
indexed
data
•
The
first
example
demonstrates
using
the
VAO
to
build
a
cube
using
•
There are two methods of using the VAO class
Triangles, by setting the vertex and vertex colour as attributes
data
Index values point into the vert
and colour arrays indicating
which elements make up the
face triangles
const static GLubyte indices[]=
{
0,1,5,0,4,5, // back
3,2,6,7,6,3, // front
0,1,2,3,2,0, // top
4,5,6,7,6,4, // bottom
0,3,4,4,7,3, // Left
1,5,2,2,6,5 // Right
};
GLfloat vertices[] = {
-1,1,-1,
1,1,-1,
1,1,1,
-1,1,1,
-1,-1,-1,
1,-1,-1,
1,-1,1,
-1,-1,1
};
GLfloat colours[]={
1,0,0,
0,1,0,
0,0,1,
1,1,1,
0,0,1,
0,1,0,
1,0,0,
1,1,1
};
// create a vao as a series of GL_TRIANGLES
m_vao= ngl::VertexArrayObject::createVOA(GL_TRIANGLES);
m_vao->bind();
// in this case we are going to set our data as the vertices above
m_vao->setIndexedData(8*sizeof(ngl::Vec3),vertices[0],sizeof(indices),indices[0]);
// now we set the attribute pointer to be 0 (as this matches vertIn in our shader)
m_vao->setVertexAttributePointer(0,3,GL_FLOAT,0,0);
m_vao->setIndexedData(8*sizeof(ngl::Vec3),colours[0],sizeof(indices),indices[0]);
// now we set the attribute pointer to be 0 (as this matches vertIn in our shader)
m_vao->setVertexAttributePointer(1,3,GL_FLOAT,0,0);
m_vao->setNumIndices(sizeof(indices));
// now unbind
m_vao->unbind();
shader->bindAttribute("ColourShader",0,"inVert");
shader->bindAttribute("ColourShader",1,"inColour");
#version 150
/// @brief Model View Projection Matrix
uniform mat4 MVP;
in vec3 inVert;
in vec3 inColour;
out vec3 vertColour;
void main(void)
{
// calculate the vertex position
gl_Position = MVP*vec4(inVert, 1.0);
vertColour=inColour;
}
Shaders
attribute 0 in Vert
attribute 1 in Colour
MVP calculated in app and
passed to shader
vertColour passed to frag shader
#version 150
in vec3 vertColour;
out vec4 outColour;
void main ()
{
// set the fragment colour
outColour = vec4(vertColour,1);
}
Drawing
// clear the screen and depth buffer
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// build our transform stack
ngl::Transformation trans;
// set the mouse rotation
trans.setRotation(m_spinXFace,m_spinYFace,0);
// set this in the TX stack
m_transformStack.setGlobal(trans);
ngl::ShaderLib *shader=ngl::ShaderLib::instance();
(*shader)["ColourShader"]->use();
ngl::Matrix MVP;
MVP=m_cam->getVPMatrix() * m_transformStack.getCurrAndGlobal().getMatrix();
shader->setShaderParamFromMatrix("MVP",MVP);
m_vao->bind();
m_vao->draw();
m_vao->unbind();
Creating a vertex structure
•
This example passes the following structure to the VAO class and creates
a textured Sphere
struct vertData
{
GLfloat u;
GLfloat v;
GLfloat nx;
GLfloat ny;
GLfloat nz;
GLfloat x;
GLfloat y;
GLfloat z;
};
// first we grab an instance of our VOA class as a TRIANGLE_STRIP
m_vaoSphere= ngl::VertexArrayObject::createVOA(GL_TRIANGLE_STRIP);
// next we bind it so it's active for setting data
m_vaoSphere->bind();
// the next part of the code calculates the P,N,UV of the sphere for
tri_strips
int buffSize;
float theta1 = 0.0;
float theta2 = 0.0;
float theta3 = 0.0;
float radius=1.0;
float precision=80;
// a std::vector to store our verts, remember vector packs contiguously so
we can use it
buffSize = (precision/2) * ((precision+1)*2);
std::vector <vertData> data(buffSize);
// calculate how big our buffer is
// Disallow a negative number for radius.
if( radius < 0 )
{
radius = -radius;
}
// Disallow a negative number for _precision.
if( precision < 4 )
{
precision = 4;
}
// now fill in a vertData structure and add to the data list for our sphere
vertData d;
unsigned int index=0;
for( int i = 0; i < precision/2; ++i )
{
theta1 = i * ngl::TWO_PI / precision - ngl::PI2;
theta2 = (i + 1) * ngl::TWO_PI / precision - ngl::PI2;
for( int j = 0; j <= precision; ++j )
{
theta3 = j * ngl::TWO_PI / precision;
d.nx = cosf(theta2) * cosf(theta3);
d.ny = sinf(theta2);
d.nz = cosf(theta2) * sinf(theta3);
d.x = radius * d.nx;
d.y = radius * d.ny;
d.z = radius * d.nz;
d.u
d.v
= (j/(float)precision);
= 2*(i+1)/(float)precision;
data[index++]=d;
d.nx = cosf(theta1) * cosf(theta3);
d.ny = sinf(theta1);
d.nz = cosf(theta1) * sinf(theta3);
d.x = radius * d.nx;
d.y = radius * d.ny;
d.z = radius * d.nz;
d.u = (j/(float)precision);
d.v = 2*i/(float)precision;
data[index++]=d;
} // end inner loop
}// end outer loop
use a std::vector to store the data
as we pre-calculate the size we
can just allocate and treat as
normal array
loop and create the data values
as a tri strip
// now we have our data add it to the VAO, we need to tell the VAO the following
// how much (in bytes) data we are copying
// a pointer to the first element of data (in this case the address of the first element
of the
// std::vector
m_vaoSphere->setData(buffSize*sizeof(vertData),data[0].u);
//
//
//
//
//
//
//
//
//
in this case we have packed our data in interleaved format as follows
u,v,nx,ny,nz,x,y,z
If you look at the shader we have the following attributes being used
attribute vec3 inVert; attribute 0
attribute vec2 inUV; attribute 1
attribute vec3 inNormal; attribure 2
so we need to set the vertexAttributePointer so the correct size and type as follows
vertex is attribute 0 with x,y,z(3) parts of type GL_FLOAT, our complete packed data is
sizeof(vertData) and the offset into the data structure for the first x component is 5
(u,v,nx,ny,nz)..x
m_vaoSphere->setVertexAttributePointer(0,3,GL_FLOAT,sizeof(vertData),5);
// uv same as above but starts at 0 and is attrib 1 and only u,v so 2
m_vaoSphere->setVertexAttributePointer(1,2,GL_FLOAT,sizeof(vertData),0);
// normal same as vertex only starts at position 2 (u,v)-> nx
m_vaoSphere->setVertexAttributePointer(2,3,GL_FLOAT,sizeof(vertData),2);
// now we have set the vertex attributes we tell the VAO class how many indices to draw
when
// glDrawArrays is called, in this case we use buffSize (but if we wished less of the
sphere to be drawn we could
// specify less (in steps of 3))
m_vaoSphere->setNumIndices(buffSize);
// finally we have finished for now so time to unbind the VAO
m_vaoSphere->unbind();
example
• As the data stored in a std::vector is always contiguous we can use it to
pass our data to the VAO
•
In
this
case
we
can
pre-calculate
the
size
of
the
array
needed
which
is
•
We just need to pass the address of the first element in the structure
quicker than using push_back
What Next
• We have used several deprecated features in this lecture but
they serve to easily demonstrate the OpenGL process
•
Next time we will investigate the OpenGL shading language
and begin to learn some other elements of the pipeline
• We can then combine them to produce a full Core profile
OpenGL application
references
•
http://www.opengl.org/wiki/Vertex_Array_Object
•
http://www.opengl.org/registry/specs/ARB/vertex_array_object.txt
References
• Segal M, Akeley K The OpenGL
Graphics System: A Specification (Version 4.0
(Core Profile) - March 11, 2010)
• F S. Hill Computer Graphics Using Open GL (3rd Edition)
• Shreiner Et Al OpenGL Programming Guide: The Official Guide to Learning
OpenGL
• Foley & van Dam Computer Graphics: Principles and Practice in C (2nd
Edition)
• (Redbook online) http://fly.cc.fer.hr/~unreal/theredbook/