root@home‎ > ‎

Знакомство с 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() );

}

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

 

PS.

Постскриптум. Оставайтесь с нами, в ближайшее время планируется статья по написанию собственного экспортера скелетной анимации из 3DSMax. Затем мы с Вами переведем все это дело на шейдеры (анимация на CPU - моветон). Ну и ждите трассировку лучей на шейдерах! И все это с использованием OpenGL 3.0!

PS2.

Если вы счастливый обладатель видеокарты от AMD, то пока не пофиксят багу в драйвере придется отказаться от forward-compatible контекста и в коде заменить

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

на

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

С ув. =[ 0r@ngE ]= ( iOrange )

ċ
TestOpenGL3.zip
(86k)
Sergey iOrange,
Jan 10, 2010, 10:20 AM
Comments