пятница, 24 апреля 2009 г.

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

Знаете за что я люблю программирование? Казалось бы, работа программиста, это же "обезьяний труд", сидишь с утра до вечера, пишешь код проекта, который большей своей частью состоит из угрюмого кода, такого как проверка на ошибки, защита от дурака, работа с памятью. Все это должно угнетать. Однако, когда вот в такой вот рутине появляется возможность изучить что-то новое, прикоснуться к ранее неизвестному и реализовать это. Это по настоящему приятно, это доставляет моральное удовольствие и иногда это даже лучше чем шоколад :)

И к чему я это пишу? Да просто хочу вам сказать, что вышел уже OpenGL 3.1 и пора бы его немного пощупать. К сожалению достойных туториалов по Интернету мало, не говоря уже о туторах на русском, поэтому свой опыт знакомства я решил описать именно в виде небольшого пособия, которое, возможно, кому-нибудь да поможет.

Чем отличается OpenGL 3.0 от более младших версий? Все просто - вся поддержка FFP (fixed function pipeline) переведена в ряд морально устаревших вещей, а точнее помечена deprecated. Это конечно не все, еще часть функций из расширений перекочевала в ядро, но самое важное изменение именно отказ от FFP. Сразу замечу что в OpenGL 3.1 уже убрали все что было помечено deprecated. Осталась одна маленькая лазейка для использования FFP это GL_ARB_compatibility (это расширение доступно только в OpenGL 3.1). Вобщем более детально можно почитать в спецификации, которую можно взять с сайта opengl.org

Чем же грозит отказ от FFP? В первую очередь убран так называемый immediate mode*, это различные функции вроде glVertex, glNormal и т.п. Пользоваться glBegin/glEnd больше нельзя. Убрали glVertexPointer, glNormalPointer и т.п.. Убрали операции с матрицами, т.е. различные glLoadIndentity, glMultMatrix, glLoadMatrix, glOrtho, glFrustum и т.п. Убрали дисплейные списки (display list). И много чего еще. Все эти изменения коснулись также и GLSL, из которого убрали связанные с FFP юниформы (uniform), такие как gl_ModelViewProjectionMatrix, gl_NormalMatrix, gl_Vertex, gl_FogCoord и т.п. Теперь вся ответственность за вычисление подобных вещей возложена на программиста, а это, между прочим, не так уж и плохо ;)

Пожалуй теории на этом хватит, перейдем к некоторым практическим аспектами реализации. Во-первых, нормальной поддержки OpenGL 3.1 в стабильных драйверах ATI и nVidia пока нет, скажу даже больше, пока еще страдает и поддержка OpenGL 3.0. Поэтому будем создавать forward контекст пока что с поддержкой OpenGL 3.0 и будем использовать GLSL 1.3.

Есть интересная особенность при создании forward контекста, для его создания нужно иметь функцию wglCreateContextAttribsARB, которую надо получать через wglGetProcAddress. Ничего не смущает? Правильно, использовать wglGetProcAddress можно только после создания контекста OpenGL. Замкнутый круг :) На самом деле это не проблема, мы просто создаем временный OpenGL контекст, как мы это делаем обычно, получаем адрес функции, создаем уже forward контекст и удаляем временный. Звучит просто? Так оно и есть, вот пример кода:
PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = NULL;
int attribs[] =
{
WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
WGL_CONTEXT_MINOR_VERSION_ARB, 0,
WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
0
};

/* create temp render context */
HGLRC temp_render_context = wglCreateContext(window->context);
if (!temp_render_context || !wglMakeCurrent(window->context, temp_render_context))
{
LOG_ERROR("Creating temp render context fail (%d)\n", GetLastError());
destroy_opengl_window(window);
return false;
}

wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB");

if (NULL == wglCreateContextAttribsARB)
{
LOG_ERROR("%s fail\n", "wglCreateContextAttribsARB");
wglDeleteContext(temp_render_context);
destroy_opengl_window(window);
return false;
}

/* create forward render context */
window->render_context = wglCreateContextAttribsARB(window->context, 0, attribs);
if (!window->render_context || !wglMakeCurrent(window->context, window->render_context))
{
LOG_ERROR("Creating render context fail (%d)\n", GetLastError());
wglDeleteContext(temp_render_context);
destroy_opengl_window(window);
return false;
}

/* delete temp render context */
wglDeleteContext(temp_render_context);
Если сейчас что-то непонятно - не расстраивайтесь, сначала просмотрите всю статью целиком, посмотрите на исходные тексты к этой статье, ссылка на которые приведена в самом конце.

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

Ну что же, мы создали forward контекст... И что дальше? Нету ни glMatrixMode ни gluPerspective, как установить матрицу перспективного преобразования? В forward контексте - только руками. Например функция, которая сформирует проекционную матрицу аналогичную той, что формирует gluPerspective:
static inline void matrix4_projection(matrix4 M, float fov, float aspect, float znear, float zfar)
{
float f = 1.0f / tanf(fov/2.0f);
memset(M, 0, sizeof(float)*16);
M[0][0] = f/aspect;
M[1][1] = f;
M[2][2] = (zfar+znear)/(znear-zfar);
M[2][3] = 2*zfar*znear/(znear-zfar);
M[3][2] = -1.0f;
}
Здесь matrix4 это typedef float matrix4[4][4], все очень просто. Ну а дальше вы берете эту матрицу и отправляете ее в шейдер, вместе с видовой матрицей, делается это вызовом функции glUniformMatrix4fv. Более конкретно это показано в исходниках.

Для вывода самого куба необходимо передать в шейдер данные о его вершинах: позицию в пространстве, нормаль и текстурные координаты для каждой вершины. Для этого в шейдере заводится несколько атрибутов, которые обозначаются с помощью квалификатора in (но вообще говоря если никакого квалификатора не указано, то подразумевается что это in). До GLSL 1.3, на то, что это атрибут указывал квалификатор attribute. Работа с атрибутами внутри программы не поменялась, после загрузки и подготовки шейдеров надо получить позицию (location) атрибута, выбрать активную шейдерную программу и передать в атрибут необходимые значения. Также выглядит и работа с юниформами (uniform). Вот код реализующий передачу данных о вершинах куба в шейдер:
position_index   = get_attrib_index(&shader, "position");
normal_index = get_attrib_index(&shader, "normal");
texcoord_index = get_attrib_index(&shader, "texcoord");

glEnableVertexAttribArray(position_index);
glEnableVertexAttribArray(normal_index);
glEnableVertexAttribArray(texcoord_index);

use_shader_program(&shader);

glVertexAttribPointer(position_index, 3, GL_FLOAT, 0, 0, cube_vertices);
glVertexAttribPointer(normal_index, 3, GL_FLOAT, 0, 0, cube_normals);
glVertexAttribPointer(texcoord_index, 2, GL_FLOAT, 0, 0, cube_texcoords);
За более подробной информацией по функциям get_attrib_index и use_shader_program отсылаю вас к исходникам тествого проекта, коротко говоря это просто обертки над glGetAttribLocation и glUseProgram, соответственно.

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

Ссылка на архив с материалами к данной статье (включает уже собранный проект, текстуру и шейдеры и исходники, а также Makefile для MinGW): gl99.7z (220 KB).

* Мне уже указали на то, что immediate mode можно вполне использовать и без FFP, но это не меняет того факта, что этот функционал был объявлен deprecated в OpenGL 3.0

4 комментария:

  1. Все четко и понятно. Спасибо за статью.

    ОтветитьУдалить
  2. Уж не знаю насколько актуально, но исходники на рапиде недоступны.

    ОтветитьУдалить
  3. Да, караул. Без исходников плохо.

    ОтветитьУдалить
  4. Исходники и расширенный материал можно найти здесь https://code.google.com/p/gl33lessons/ :)

    ОтветитьУдалить