Работа с OpenGL 3.3 с использованием библиотек freeglut и glew

Важной особенностью OpenGL 3.2 и выше является то, что для работы с ним необходимо создать специальный контекст. Для создания контекста при помощи расширений WGL_ARB_create_context/GLX_ARB_create_context предоставляются функции wglCreateContextAttribsARB/glXCreateContextAttribsARB, позволяющие создать контекст с заданными атрибутами. Однако для того, чтобы получить эти функции (вводимые через расширения OpenGL) необходимо уже иметь готовый контекст OpenGL.

Поэтому сейчас обычно используется следующая схема - сначала стандартными средствами создается обычный контекст OpenGL, при помощи которого получается функция wglCreateContextAttribsARB или glXCreateContextAttribsARB, в зависимости от используемой операционной системы. После этого при помощи полученной функции создается нормальный контекст со всеми необходимыми атрибутами и делается текущим, а старый контекст просто уничтожается - он больше уже не нужен.

При создании контекста при помощи wglCreateContextAttribsARB/glXCreateContextAttribsARB необходимо задать ряд атрибутов, таких требуемая версия OpenGL, используемый профиль (compatibility или core) и дополнительные свойства (поддержка отладочной информации и поддержка deprecated-функционала).

Есть хорошая серия статей, разбирающая весь этот путь и реализацию ряда эффектов непосредственно идя этим путем, прочитать их будет весьма полезно. В этой же статье упор будет сделан на использование уже существующих библиотек для быстрого написания кроссплатформенного кода.

Очень большое количество примеров на OpenGL традиционно пишется с использованием библиотеки glut. К сожалению код этой библиотеки закрыт, она уже давно не обновляется и не поддерживает создание контекста для OpenGL 3.2 и выше.

Зато есть открытая библиотека freeglut, полностью включающая в себя как функциональность библиотеки glut (т.е. если заменить glut на нее, то все будет компилироваться и работать). При этом в состав библиотеки freeglut входит ряд дополнительных возможностей, в том числе и поддержка создания контекста для OpenGL версии 3.2 и выше. При этом библиотека остается кроссплатформенной и позволяет легко писать приложения, компилирующиеся и работающие как под Windows, так и под Linux.

Ниже будут рассмотрены некоторые из дополнительный функций freeglut, а также создание простейших OpenGL 3.3 приложений.

Дополнительные возможности библиотеки freeglut

Библиотека freeglut предоставляет много дополнительных возможностей, здесь будут рассмотрены только некоторые из них. Полное описание содержится в документации к самой библиотеке.

Прежде всего были добавлены функции, позволяющие изменить цикл обработки сообщений и сделать нормальную деинициализацию приложения. В основе этого лежат функции glutMainLoopEvent и glutLeaveMainLoop.

void glutMainLoopEvent ();
void glutLeaveMainLoop ();

Функция glutMainLoopEvent обрабатывает все имеющиеся в очереди сообщения и возвращает управление. Тем самым весь цикл обработки сообщений, реализуемый функцией glutyMainLoop можно реализовать как явный цикл, вызывающий функцию и glutMainLoopEvent.

while ( !done )
    glutMainLoopEvent ();
При таком подходе можно легко в любой момент времени выйти из цикла обработки сообщений и освободить взятые ресурсы. Другую возможность выхода из цикла обработки сообщений предоставляет функция glutLeaveMainLoop - после ее вызова, функция glutMainLoop возвращает управление.

Следующая группа функций добавляет новые callback'и к уже существующим.

void glutMouseWheelFunc ( void (*callback)( int, int, int, int ) );
void glutEntryFunc      ( void (*callback)( int ) );
void glutKeyboardUpFunc ( void (*callback)( unsigned char, int, int ) );
void glutCloseFunc      ( void (*callback)( void ) );

Функция glutMouseWheelFunc позволяет установить обработчик колесика мышки. Функция-обработчик имеет следующий вид:

void mouseWheel ( int wheel, int dir, int mouseX, int mouseY )
{

}

Параметр wheel содержит номер колесика мыши, вызвавше7го данное событие (обычно он равен нулю), а параметр dir задает направление в котором было повернуто колесико и принимает значения +1 и -1.

Функция glutEntryFunc позволяет установить обработчик, который будет вызываться при входе курсора мыши в окно и выходе из него.

void mouseEntry ( int state )
{
    if ( state == GLUT_ENTEERED )
	   .  .  .
    else                 // GLUT_LEFT
	   .  .  .
}

Функция glutKeyboardUpFunc является парной к функции glutKeyboardFunc и позволяет установить обработчик отпускания клавиши, генерирующей ASCII-код.

Функция glutCloseFunc служит для установки обработчика, который будет вызываться при закрытии окна.

Следующая (и наиболее важная для нас) группа функция служит для "заказа" требуемого контекста OpenGL. За счет их использования мы сразу получаем требуемый контекст и можем начать с ним работать.

void glutInitContextVersion ( int majorVersion, int minorVersion );
void glutInitContextProfile ( int profile );
void glutInitContextFlags   ( int flags );

Функция glutInitContextVersion задает для какой версии OpenGL нужно создать контекст. Функция glutInitContextProfile задает тип профиля, который должен быть создан для этого контекста. В качестве этого параметра могут выступать значения GLUT_CORE_PROFILE и GLUT_COMPATIBILITY_PROFILE. Функция glutInitContextFlags позволяет задать дополнительные свойства для профиля. Свойства задаются как набор битов и допустимыми на данный момент являются GLUT_DEBUG и GLUT_FORWARD_COMPATIBLE. Последнее значение позволяет создать профиль, в котором весь deprecated функционал будет выключен.

Ниже приводится простейшая программа, использующая freeglut для создания контекста для core profile с отключенным deprecated-функционалом и создающий пустое окно.

#include	<freeglut.h>
#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	"../glext.h"

void init ()
{
    glClearColor ( 0, 0, 0, 1 );
    glEnable     ( GL_DEPTH_TEST );
    glDepthFunc  ( GL_LEQUAL );
}

void display ()
{
    glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    glutSwapBuffers ();
}

void reshape ( int w, int h )
{
    glViewport ( 0, 0, (GLsizei)w, (GLsizei)h );
}

void key ( unsigned char key, int x, int y )
{
    if ( key == 27 || key == 'q' || key == 'Q' )	//	quit requested
        exit ( 0 );
}

int main ( int argc, char * argv [] )
{
                                // initialize glut
    glutInit            ( &argc, argv );
    glutInitDisplayMode ( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH );
    glutInitWindowSize  ( 500, 500 );

                                // prepare context for 3.3
    glutInitContextVersion ( 3, 3 );
    glutInitContextFlags   ( GLUT_FORWARD_COMPATIBLE | GLUT_DEBUG );
    glutInitContextProfile ( GLUT_CORE_PROFILE );

                                // create window
    glutCreateWindow ( "GL 3.3/4.1 demo" );

                                // register handlers
    glutDisplayFunc  ( display );
    glutReshapeFunc  ( reshape );
    glutKeyboardFunc ( key     );


    const char * slVer = (const char *) glGetString ( GL_SHADING_LANGUAGE_VERSION );
    printf ( "GLSL Version: %s\n", slVer );
	
    glutMainLoop ();

    return 0;
}

Использование библиотеки glew

Для того, чтобы получить доступ ко всем функциям OpenGL 3.0 и выше необходимо аналогично механизму расширений получить адреса всех необходимых функций по их именам. Хотя ничего технически сложного в этом и нет, тем не менее это довольно кропотливая работа. Существует несколько уже готовых библиотек с открытым кодом, которые берут всю эту работу на себя.

Одной из лучших и наиболее известных таких библиотек является glew. Подключив ее, мы сразу получаем доступ ко всем новым функциям. Кроме того, также можно работать с расширениями (что собственно и является основной задачей библиотеки glew).

Для подключения библиотеки достаточно включить заголовочные файлы (основной файл glew.h и два дополнительных файлы, зависящих от конкретной платформы - wglew.h и glxew.h).

#include	<GL/glew.h>

#ifdef	_WIN32
    #include <GL/wglew.h>
#else
    #include <GL/glxew.h>
#endif

Также необходимо явно проинициализировать библиотеку при помощи вызова функции glewInit. Перед этим вызовом желательно выставить флаг glewExperimental в GL_TRUE - в противном случае glew иногда считает некоторые расширения экспериментальными и не инициализирует их поддержку.

glewExperimental = GL_TRUE;

glewInit ();

Также можно использовать ряд встроенных переменных glew для проверки поддержки той или иной версии OpenGL (GLEW_VERSION_3_3) или расширения (GLEW_ARB_vertex_array_object):

if ( !GLEW_VERSION_3_3 )
{
    printf ( "OpenGL 3.3 not supported.\n" );
    exit   ( 1 );
}

Работа с OpenGL 3.3

Важной особенностью OpenGL 3.2 при работе с core profile является полное отсутствие FFP и связанного с ним функционала. Убраны функции glBegin, glEnd, glVertex*, убраны стандартные атрибуты вершин ( glNormal*, glTexCoord), все вершины теперь задаются через вершинные массивы.

Убраны некоторые типы примитивов - GL_QUADS, GL_QUAD_STRIP и GL_POLYGON. Все команды glTexImage* не принимают число компонент в качестве значения для внутреннего формата. Также убран режим приведения текстурных координат GL_CLAMP (допустимыми режимами являются только GL_CLAMP_TO_EDGE, GL_CLAMP_TO_BORDER, GL_REPEAT и GL_MIRRORED_REPEAT). Кроме того параметр border в glTexImage* должен быть равен нулю.

Убран альфа-тест и функция glAlphaFunc - это надо делать в шейдере. Убрана поддержка aux и accumulation буферов, дисплейных списков, ряда hint'ов, стеки атрибутов.

Весь вывод теперь осуществляется при помощи шейдеров, т.е. без них теперь вообще ничего не нарисовать. Рассматриваемый в одной из статей WebGL очень близок к этому - всю работу с матрицами необходимо делать руками, весь вывод - только через шейдеры и т.д.

Ниже приводится пример простой программы, выводящей на экран треугольник, при этом колесиком мыши можно управлять масштабированием. Ряд элементов программы специально упрощены (в частности шейдеры не читаются из внешних файлов, а хранятся в самом тексте программы), также не всюду стоят проверки результата.

#include	<GL/glew.h>

#ifdef	_WIN32
	#include	<GL/wglew.h>
#else
	#include	<GL/glxew.h>
#endif

#include	<freeglut.h>
#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>

#define	NUM_VERTICES	3
#define	VERTEX_SIZE		(3*sizeof(float))

GLuint 	vbo, vao;
GLuint	program;
float	scale = 0.3;

const char * vertexSource = // vertex shader
"#version 330 core\n" \
"uniform float scale;\n"
"in vec3 position;\n" \
"void main(void)\n" \
"{\n" \
"	gl_Position   = vec4 ( scale * position, 1.0 );\n" \
"}\n";

const char * fragmentSource = // fragment shader
"#version 330 core\n" \
"out vec4 color;\n"   \
"void main(void)\n"   \
"{\n"                 \
"	color = vec4 ( 1.0);\n" \
"}\n";


static const float vertices [] = 
{
    -2.0f, -2.0f, -1.0f, 
    0.0f,  2.0f, -1.0f, 
    2.0f, -2.0f, -1.0f
};

GLuint	loadShader ( const char * source, GLenum type )
{
    GLuint shader = glCreateShader ( type );
    GLint   len   = strlen ( source );
    GLint   compileStatus;

    glShaderSource ( shader, 1, &source,  &len );

                                        // compile the shader
    glCompileShader ( shader );

    glGetShaderiv ( shader, GL_COMPILE_STATUS, &compileStatus );

    if ( compileStatus == 0 )           // some error
    {
        printf ( "Error comping shader.\n" );

        return 0;
    }

    return shader;
}

GLuint	createProgram ()
{
    GLuint	program = glCreateProgram ();
    GLuint	vs      = loadShader ( vertexSource, GL_VERTEX_SHADER );
    GLuint	fs      = loadShader ( fragmentSource, GL_FRAGMENT_SHADER );
    GLint	linked;
	
    glAttachShader ( program, vs );
    glAttachShader ( program, fs );
    glLinkProgram  ( program );
    glGetProgramiv ( program, GL_LINK_STATUS, &linked );

    if ( !linked )
    {
        printf ( "Error linking program\n" );
		
        return 0;
    }
	
    return program;
}

bool	setAttrPtr ( GLuint program, const char * name, int numComponents, GLsizei stride, void * ptr, GLenum type = GL_FLOAT, bool normalized = false )
{
    int	loc = glGetAttribLocation ( program, name );

    if ( loc < 0 )
         return false;
		
    glVertexAttribPointer ( loc,                  // index
                            numComponents,        // number of values per vertex
                            type,                 // type (GL_FLOAT)
                            normalized ? GL_TRUE : GL_FALSE,
                            stride,               // stride (offset to next vertex data)
                            (const GLvoid*) ptr );
		
    glEnableVertexAttribArray ( loc );
	
    return true;
}

bool    setUniformFloat ( GLuint program, const char * name, float value )
{
    int loc = glGetUniformLocation ( program, name );

    if ( loc < 0 )
        return false;

    glUniform1f ( loc, value );

    return true;
}

void init ()
{
    glClearColor ( 0.5, 0.5, 0.5, 1.0 );
    glEnable     ( GL_DEPTH_TEST );
    glDepthFunc  ( GL_LEQUAL );
}

void display ()
{
    glClear ( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    glUseProgram      ( program );
    glBindVertexArray ( vao );

    glDrawArrays      ( GL_TRIANGLES, 0, NUM_VERTICES );
	
    glBindVertexArray ( 0 );
    glUseProgram      ( 0 );

    glutSwapBuffers ();
}

void reshape ( int w, int h )
{
    glViewport ( 0, 0, (GLsizei)w, (GLsizei)h );
}

void key ( unsigned char key, int x, int y )
{
    if ( key == 27 || key == 'q' || key == 'Q' )	//	quit requested
        exit ( 0 );
}

void mouseWheel ( int wheel, int dir, int mouseX, int mouseY )
{
    if ( dir > 0 )
        scale += 0.05;
    else
        scale -= 0.05;
		
    glUseProgram      ( program );
    setUniformFloat   ( program, "scale", scale );
    glUseProgram      ( 0 );
    glutPostRedisplay ();
}

int main ( int argc, char * argv [] )
{
								// initialize glut
    glutInit            ( &argc, argv );
    glutInitDisplayMode ( GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH );
    glutInitWindowSize  ( 500, 500 );
								// prepare context for 3.3
    glutInitContextVersion ( 3, 3 );
    glutInitContextFlags   ( GLUT_FORWARD_COMPATIBLE | GLUT_DEBUG );
    glutInitContextProfile ( GLUT_CORE_PROFILE );
								// create window
    glutCreateWindow ( "GL 3.3 demo" );

    glewExperimental = GL_TRUE;

    glewInit ();

    if ( !GLEW_VERSION_3_3 )
    {
        printf ( "OpenGL 3.3 not supported.\n" );
		
        return 1;
    }
								// register handlers
    glutDisplayFunc    ( display );
    glutReshapeFunc    ( reshape );
    glutKeyboardFunc   ( key     );
    glutMouseWheelFunc ( mouseWheel );
	
    if ( !GL_ARB_vertex_array_object )
        printf ( "No VAO support\n" );

    program = createProgram ();
	
    glUseProgram ( program );

    glGenVertexArrays ( 1, &vao );
    glBindVertexArray ( vao );

    glGenBuffers ( 1, &vbo );
    glBindBuffer ( GL_ARRAY_BUFFER, vbo );
    glBufferData ( GL_ARRAY_BUFFER, NUM_VERTICES * VERTEX_SIZE, vertices, GL_STATIC_DRAW );	
	
    setAttrPtr      ( program, "position", 3, VERTEX_SIZE, (void *) 0 );
    setUniformFloat ( program, "scale", scale );

    glBindBuffer      ( GL_ARRAY_BUFFER, 0 );
    glBindVertexArray ( 0 );
    glUseProgram      ( 0 );

    glutMainLoop ();

    return 0;
}

Чтобы избежать большого количества рутинных операций, подобно тому как это делалось во многих предыдущих статьях, можно завернуть все основные необходимые сущности - вектора, матрицы, шейдеры - в объекты языка C++ и за счет этого заметно упростить работу (как это делалось в предыдущих статьях). Дополнительным плюсом таких врапперов является то, что на них можно переложить основной матричный функционал, перешедший в категорию deprecated. В этой статье такие врапперы рассматриваться не будут, но последующие статьи уже будут основаны на таких врапперах.

Ссылки

Пример работы с OpenGL 3

Creating a Cross Platform OpenGL 3.2 Context in SDL (C / SDL)

OpenGL 3.0 Context Creation (GLX)

OpenGL 3.1 The First Triangle (C++/Win)

OpenGL 3.3 and GLSL 3.3 with GLEW and GLUS

OpenGL 3 Framework.

По этой ссылке можно скачать весь исходный код к этой статье. Также доступны для скачивания откомпилированные версии для M$ Windows и Linux.

Обратите внимание, что для сборки под Linux может понадобиться скачать исходники последних версий библиотек freeglut и glew и собрать и установить их из исходников.