Знакомство с OpenGL 3.0

 

Здравствуйте дорогие читатели!

Можно долго спорить о том что лучше – OpenGL или Direct3D, но мы этого делать не будем (ведь не будем, да?). Просто примем существование обоих GAPI как факт и будем с этим жить :) .

Но я отвлекся. Итак сегодняшняя статья будет про OpenGL. Также статья будет небольшой и по большей части вводной. Дело в том что я хотел бы начать цикл статей по OpenGL 3.x. Как, наверное, большинству из Вас известно, появление OpenGL 3 стало переломным моментом во всем OpenGL-комьюнити. Данный релиз представлял собой обратно-несовместимое усовершенствование стандарта OpenGL. Можно долго спорить хорошо это или плохо, но одно ясно точно – OpenGL стал ближе к современным GPU. Из OpenGL 3.x выброшен «устаревший» функционал (immediate-mode,FFP-features) что в свою очередь упростит драйвер (раньше драйверу приходилось поддерживать все фичи с момента появления OpenGL 1.1). В общем тема давно избита и незнакомых с новшествами отошлем к спецификациям (http://www.opengl.org/registry/) и огромному треду на (http://www.gamedev.ru/code/forum/?id=84124)  а сами продолжим :) .

Итак, чтоже мы сегодня намерены выучить? Во-первых научимся создавать контекст «чистого» OpenGL 3.0 (без deprecated-функционала). Во вторых научимся жить без FFP (Fixed-Function Pipeline), проделывая всю необходимую работу сами используя GLSL 1.30. Сразу хочу оговорить что кому-то мой стиль кодирования может не понравиться – что тут поделаешь, но каждый может писать по своему. Также код пока что не особо претендует на портируемость, ибо мне временно нет на чем тестировать (винт с моей KUbuntu 9.04 сдох :( ).

Опишем в двух словах как создается OpenGL 3.x контекст:

для этого нам нужно создать фейковый контекст OpenGL как обычно (wglCreateContext), затем получить адрес функции wglCreateContextAttribsARB, с помощью нее создать новый контекст OpenGL 3.x, фейковый контекст после этого можно удалить за ненадобностью.

  • Полный код Вы можете скачать в конце статьи.
HGLRC  fakeGLRC = NULL;
HDC    hDC = NULL;
uint32 pixelFormat = 0;
 
PIXELFORMATDESCRIPTOR pfd =
{   sizeof( PIXELFORMATDESCRIPTOR ), 1,
    PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
    PFD_TYPE_RGBA, color, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, depth, 0, 0,
    PFD_MAIN_PLANE, 0, 0, 0, 0
};
 
if ( !( hDC = GetDC( hWnd ) ) )
    _RET_WITH_ERROR( "Can't create a dummy gl device context" )
if ( !( pixelFormat = ::ChoosePixelFormat( hDC, &pfd ) ) )
    _RET_WITH_ERROR( "Can't find a suitable dummy pixelformat" )
if ( !::SetPixelFormat( hDC, pixelFormat, &pfd ) )
    _RET_WITH_ERROR( "Can't set the pixelformat" )
if ( !( fakeGLRC = wglCreateContext( hDC ) ) )
    _RET_WITH_ERROR( "Can't create a dummy gl rendering context" )
if ( !wglMakeCurrent( hDC, fakeGLRC ) )
    _RET_WITH_ERROR( "Can't activate dummy gl rendering context" )
if ( !HaveOpenGL3Support() )
    _RET_WITH_ERROR( "Your videocard not support OpenGL 3.0" )
 
PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)ProcedureAddress( "wglCreateContextAttribsARB" );
if ( !wglCreateContextAttribsARB )
    _RET_WITH_ERROR( "Your videocard support OpenGL 3.0,\nbut wglCreateContextAttribsARB == NULL!" )
 
Log_Normal( "OpenGL 3.0 support detected.\n\tCreating context" );
 
int attribs[] =
{   WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
    WGL_CONTEXT_MINOR_VERSION_ARB, 0,
    WGL_CONTEXT_FLAGS_ARB, forwardCompatible ? WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB : 0,
    0, 0
};
if ( NULL == ( glRc = wglCreateContextAttribsARB( hDC, NULL, attribs ) ) )
{
    ReleaseDC( hWnd, hDC );
    _RET_WITH_ERROR( "Creating OpenGL 3.0 context failed" )
}
 
Log_Normal( "Destroying fake OpenGL context..." );
 
wglMakeCurrent( NULL, NULL );
wglDeleteContext( fakeGLRC );
 
Log_Normal( "Setup OpenGL 3.0 context..." );
if ( !wglMakeCurrent( hDC, glRc ) )
{
    Log_Error( "Couldn't make OprnGL 3.0 context current" );
    ReleaseDC( hWnd, hDC );
    wglDeleteContext( glRc );
    return false;
}
 
return true;

Итак OpenGL 3.0 контекст создан! Поздравляю! Проверить это можно вызвав функцию glGetString(GL_VERSION);
Двигаем дальше. Так как мы пока что пишем под Windows, то ждать opengl3.dll нам не приходиться, значит получать адреса всех нужных нам функций придется по старинке. Радует одно – если видеокарта/драйвер поддерживает OpenGL 3.x и у Вас создался контекст – значит присутствует весь core-функционал. Но пока с новым OpenGL не все так сладко, поэтому я привык проверять все нужные мне расширения. Нам нужны адреса функций для работы с VBO (vertex buffer object), Vertex Attributes и конечно же GLSL.

Log_Normal( "Initializing VBO extensions..." );
if ( !glext::InitVBO() )
{
    Log_Error( "Couldn't initialize VBO" );
    OpenGL_3::Shutdown();
}
Log_Normal( "Initializing VBO extensions OK" );
glext::InitVertexAttribs();
Log_Normal( "Initializing GLSL extensions..." );
if ( !glext::InitGLSLNeededExts() )
{
    Log_Error( "Couldn't initialize GLSL" );
    OpenGL_3::Shutdown();
}
Log_Normal( "Initializing GLSL extensions OK" );
std::cout << "OpenGL GLSL Version :  " << OpenGL_3::GetGLSLVersion() << std::endl;

Как я уже говорил ранее, FFP в OpenGL 3.x отмирает, а значит что теперь вершинный и фрагментный процессинг отдан нам под полный контроль. Значит нам нужно написать как минимум два шейдера – вершинный и фрагментный. Для старту не будем сильно извращаться и напишем элементарный функционал:
Vertex Shader:

#version 130
precision highp float;
uniform mat4x4 modelViewProjection;
in vec4 vertPosition;
in vec4 vertColor;
out vec4 color;
void main() {
    color = vertColor;
    gl_Position = modelViewProjection * vertPosition;
}

Fragment Shader:

#version 130
precision highp float;
in vec4 color;
out vec4 fragColor;
void main() {
    fragColor = color;
}

Как видим эти шейдеры ничего особенного не делают – вершинный шейдер передает цвет вершины во фрагментный шейдер а также трансформирует вершину MVP матрицей. Фрагментный же шейдер просто красит пиксел в интерполированный цвет пришедший из вершинного шейдера. Остановимся подробнее на новых для нас вещах:
как Вы уже наверное заметили, первой строкой каждого шейдера идет указание использовать версию GLSL 1.30, затем идет указание какой точности будут производиться вычисления. Входные параметры шейдеров такие:

в вершинном шейдере – in заменяет бывшие attribute, тоесть указывают на вершинные атрибуты которые пользователь передает с помощью VBO, out – бывшие varying – выходные значения вершинного шейдера, которые передаются во фрагментный шейдер.

во фрагментном шейдере – in заменяет бывшие varying – входные значения пришедшие из вершинного шейдера, out – выходной результат фрагментного шейдера (теперь пользователь должен конкретно указывать какой out-параметр фрагментного шейдера куда будет записываться, по умолчанию это бэкбуффер).

Ок, с этим мы разобрались. Как видим многие predefined переменные шейдеров были вычеркнуты (как например gl_ModelViewProjectionMatrix), поэтому матрицы теперь нужно «готовить» самим и передавать в шейдер (напоминает D3D, неправда?).
Итак шейдеры загружены, скомпилированы и слинкованы в программу. Теперь надо узнать куда же нам подавать вершинные атрибуты, сделать это можно с помощью функции glGetAttribLocation(program, attribName);

_vertPosition = glext::glGetAttribLocationARB( program, "vertPosition" );
_vertColor = glext::glGetAttribLocationARB( program, "vertColor" );

Теперь осталось дело за малым – создать вершинный буфер, залить в него данные и начать рисовать!

void InitVertexData( void )
{
    DrawVertex_s vertexes[] =
    {
        { vec3(  50, 500, 0 ),    255, 0, 0, 255 },
        { vec3( 750, 500, 0 ),    0, 255, 0, 255 },
        { vec3( 400,  50, 0 ),    0, 0, 255, 255 }
    };
 
    glext::glGenBuffersARB( 1, &_vbo );
    glext::glBindBufferARB( GL_ARRAY_BUFFER, _vbo );
        glext::glBufferDataARB( GL_ARRAY_BUFFER, sizeof(vertexes), vertexes, GL_STATIC_DRAW );
    glext::glBindBufferARB( GL_ARRAY_BUFFER, 0 );
}

Теперь когда шейдеры и данные готовы, можем приступать к отрисовке. Для этого забиндим нужный нам вершинный буфер, укажем каким вершинным атрибутам какие данные брать и в каком формате, выставим матрицу и наконец отрисуем. Примерно так:

void Draw( void )
{
    glClear( GL_COLOR_BUFFER_BIT );
 
    glext::glBindBufferARB( GL_ARRAY_BUFFER, _vbo );
 
    glext::glEnableVertexAttribArrayARB( _vertColor );
    glext::glEnableVertexAttribArrayARB( _vertPosition );
 
    glext::glVertexAttribPointerARB( _vertColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(DrawVertex_s), vbo_offsetof(DrawVertex_s, r) );
    glext::glVertexAttribPointerARB( _vertPosition, 3, GL_FLOAT, GL_FALSE, sizeof(DrawVertex_s), NULL );
 
    uint32 deltaTime = GetTickCount() - _prevTime;
    _prevTime = GetTickCount();
    _curAngle += float(deltaTime)*0.05f;
    if ( _curAngle > 360.0f )
        _curAngle -= 360.0f;
 
    mv = RotateMatrixZ( _curAngle );
    mvp = mv * proj;
    glext::glUniformMatrix4fvARB( _modelViewProjection, 1, 0, mvp );
 
    glDrawArrays( GL_TRIANGLES, 0, 3 );
 
    SwapBuffers( wglGetCurrentDC() );
}


Если вы видите то же что на скриншотах – поздравляю! Теперь вы официально являетесь пользователем OpenGL 3.0. Если же что-то не получилось, значит либо у Вас видеокарта/драйвер не поддерживает OpenGL 3.0, либо я по ошибке где-то использовал нестандартную фичу (nVidia сильно развращает в этом плане :) ). Вобщем, если не взлетело – прошу сообщить об этом посредством комментариев.
PS.
Если вы счастливый обладатель видеокарты от AMD, то пока не пофиксят багу в драйвере придется отказаться от forward-compatible контекста и в коде заменить

if ( !OpenGL_3::Initialize( g_hWnd, _glRC ) )

на

if ( !OpenGL_3::Initialize( g_hWnd, _glRC, 32, 24, false ) )

Исходный код к статье:
TestOpenGL3.zip (1970)

  3 Ответов в “Знакомство с OpenGL 3.0”

  1. Здравствуйте,

    У меня код урока рендерит черный экран. Попробовал, цвет фона можно поменять glClearColor( 1, 0, 0, 1 ), т.е. контекст рабочий. Все статусы возвращены как ОК. Аналогичный урок http://code.google.com/p/gl33lessons/wiki/Lesson02 у меня на компе работает. Проблема с шейдерами? Как вычислить ошибку?

    Спасибо, Игорь

    • Здравствуйте!

      Пришлите мне пожалуйста лог выводимый в консоль. Постараемся решить проблему.

  2. Приветствую!
    И присоединяюсь к первому посту :)

Оставить комментарий на iOrange Отменить ответ

(required)

(required)

Вы можете использовать HTML теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

© 2011 3D-Orange.com.ua
e-mail me

3D-Orange.com.ua is proudly powered by WordPress.
Suffusion theme by Sayontan Sinha