Знакомство с 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 (1956)
Здравствуйте,
У меня код урока рендерит черный экран. Попробовал, цвет фона можно поменять glClearColor( 1, 0, 0, 1 ), т.е. контекст рабочий. Все статусы возвращены как ОК. Аналогичный урок http://code.google.com/p/gl33lessons/wiki/Lesson02 у меня на компе работает. Проблема с шейдерами? Как вычислить ошибку?
Спасибо, Игорь
Здравствуйте!
Пришлите мне пожалуйста лог выводимый в консоль. Постараемся решить проблему.
Приветствую!
И присоединяюсь к первому посту