Skip to content

Vedingrot/OpenGL_with_fixed_pipeline_guide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 

Repository files navigation

Введение

Сразу оговорюсь, что я использовал функционал старых версий OpenGL. И многие функции являются "deprecated", но для наших целей этого вполне достаточно, потому что нам необходимо рисовать примитивы такие как линии и точки. От использования современного OpenGL с шейдерами (прим. программы выполняемые прямо на GPU), я отказался в первую очередь потому что надо писать тесты аффинных преобразований, а это мягко говоря, затруднительно сделать на языке шейдеров GLSL да и лень сейчас его учить. Будем крутить трансформации на процессоре, благо они сейчас довольно шустрые.

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

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

В классе QOpenGLWidget, который является контекстом рисования, есть три функции initializeGL(), resizeGL() и paintGL(). Из названия понятно, когда эти функции вызываются. О paintGL(), можно думать как о цикле отрисовки.

Изначальные настройки OpenGL

Функции, которые должны выполняться при первой инициализации контекста OpenGL т.е. внутри initializeGL().

glEnable(GL_DEPTH_TEST) - включаем буфер глубины для отображения Z-координаты. Для Qt иногда необходимо прописывать initializeOpenGLFunctions(). Знаю, что на Маке функции работают и без этого, но на убунте не всегда. Лучше воткнуть на всякий случай.

Изменение размера контекста OpenGL

При изменение размера контекста отрисовки необходимо вызывать функцию glViewPort(0, 0, width, height), где ширина и высота задается в пикселях. Естественно, все внутри функции, которая вызывается у вас при изменение размера окна resizeGL(width, height). Если у вас окно фиксированного размера, вы можете опустить эту функцию.

Отрисовка

Перед тем, как начать разбираться с отрисовкой нужно поговорить о том, как мы будем передавать данные в OpenGL. По ТЗ нам нужно отрисовывать модель в каркасном виде и дополнительно подсвечивать вершины. Любая 3Д модель состоит из полигонов, которые могут быть любого размера и формы. Представим, как-нибудь простой объект, который хранит не сильно много вершин, например куб. Каждый полигон имеет свои координаты вершин и эти координаты могут повторяться для соседних полигонов.

cube_picture

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

Распарсить .obj файлы и понять какие линии соединяются полигоны, думаю не сложно.

Если у нас полигон записан как f 5 1 4, то мы должны добавить в массив индексов [ 5, 1, 1, 4, 4, 5 ], т.е. соединить все ребра полигона и так для каждого следующего полигона в файле добавляем его в массив индексов.

Из каждого элемента массива необходимо вычесть единицу, потому что индексация идет с нуля.
[ 5, 1, 1, 4, 4, 5, ... ] -> [ 4, 0, 0, 3, 3, 4, ... ]

В конечном итоге после парсинга .obj, у нас должны получиться массив индексов и массив вершин, который хранит координаты вершин в виде [x1, y1, z1, x2, y2, z2, x3…]. Сразу скажу, что массив вершин в ходе аффинных преобразований будет меняться и стоит сразу подумать как его передавать в OpenGL, и как вы его будете менять в ходе выполнения программы.

Для начала после создания массива вершин необходимо передать его в функцию, glVertexPointer(3, GL_DOUBLE, 0, vertex_array), где 3 - это количество координат на вершину, GL_DOUBLE - тип данных в массиве, 0 - расстояние между вершинами в массиве, в нашем случае означает, что вершины упакованы в массив без пробелов, и vertex_array - указатель на массив вершин.

Для того, чтобы отрисовка массива вершин проходила успешно нам нужно обязательно включить состояние OpenGL, glEnableClientState(GL_VERTEX_ARRAY), и выключить его после всех отрисовок, glDisableClientState(GL_VERTEX_ARRAY).

Для отрисовки используются функции glDrawArrays() или glDrawElements(). На второй мы остановимся поподробнее.

Мы будем рисовать линии, что мы скажем функции первым аргументом. glDrawElements(GL_LINES, edges_counter, GL_UNSIGNED_INT, indexes_array), edges_counter - по-сути размер массива индексов, третий аргумент тип данных который хранится у нас в массиве, и indexes_array - указатель на массив индексов.

Попробуйте изначально отрисовать кубик от -0.5 до 0.5, чтобы увидеть, что у вас все рисуется. glDrawArrays() будет использоваться в бонусной части, для отрисовки вершин. Функций упомянутых выше достаточно, для того, чтобы рисовать объекты с белыми ребрами на черном фоне, но этого будет многим недостаточно так что перейдем к бонусным частям.

Матрицы преобразований

Положение системы координат описывается с помощью матрицы преобразования и нам надо научиться эту матрицу изменять. Для этого у нас есть удобные процедуры:

  • glScaled(scaleFactorX, scaleFactorY, scaleFactorZ).
  • glTranslated(xMov, yMov, zMov);
  • glRotated(angle, x, y, z);

Здесь нужен именно такой, порядок, потому что мы изменяем систему координат, а не положение фигуры.

Во многих функциях в конце стоит буква 'd', это почти всегда означает,
что функция принимает агрументы типа double.

Перед тем, как начать делать какие-либо преобразования необходимо перейти в режим представления модели, для этого используется функция glMatrixMode(GL_MODELVIEW).

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

Проекции

Красивый видосик по проекциям в OpenGL.

Начнем разбираться с бонусной частью проекта с проекций. По-умолчанию OpenGL отрисовывает всё в параллельной проекции.

Я не просто так говорил вам пробовать отрисовывать куб размера от -0.5 до 0.5. Видимый объем OpenGL, по-умолчанию это куб от -1 до 1.

opengl_volume

Ось Z направлена из монитора нам в глаз.

Перед тем, как менять видимый объем или тип проекции необходимо перевести матричный режим в glMatrixMode(GL_PROJECTION) загрузить единичную матрицу glLoadIdentity(). И только после этого задать видимый объем glOrtho(left, right, bottom, top, near, far). Естественно, что все не должно схопываться в плоскость, например left != right и т. п.

Границы проекции можно задать, вычислив максимальное и минимальное значение по каждой оси и домножив эти границы на какой-либо коэфициент, чтобы задать изначальное маштабирование фигуры.

Проекции мы можем менять как в initializeGL(), так и в paintGL(), самое главное не забывать поменять матричный режим и загрузить единичную матрицу.

Центральная проекция

С центральной проекцией история немного отличается. Нам нужно задать её так, чтобы была перспектива. glFrustum(left, right, bottom, top, near, far), Понятное дело, что нельзя допускать схлопывание в плоскость, но ещё необходимо, чтобы near и far, были положительные.

Конечно можно использовать gluPerspective(), но этого надо ставить дополнительную библиотеку.

//  winWidth и winHeight мы задавали также через минимальные и максимальные значения
float fov = 60.0 * M_PI / 180;  // 60 угол в градусах
float heapHeight = winHeight / (2 * tan(fov / 2));
glFrustum(-winWidth, winWidth, -winHeight, winHeight, heapHeight, far);
// far можно задать любым, лишь бы все умещалось.

Так как near у нас положительный нам необходимо сдвинуть фигуру на некоторое расстояние по Z, чтобы все влезало в объем отрисовки glTranslated(0, 0, -heapHeight * 3).

Цвет фона

glClearColor(r, g, b, alpha);
glClear(GL_COLOR_BUFFER_BIT);

glClearColor() отвечает за состояние цвета фона, но после этого необходимо очищать буфер глубины через glClear(GL_COLOR_BUFFER_BIT). Цвета задаются значением типа float в диапазоне от 0 до 1. Для точек и линий мы будем указывать значение цвета в таком же формате.

Настройки линии

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

Цвет линии

glColor3d(red, green, blue)

Эта функция будет менять также цвет вершин, но передавать туда мы будем уже другие цвета.

Толщина линии

glLineWidth(width)

width имеет тип float.

Тип линии

glLineStripple(factor, pattern) - Определяет шаблон (паттерн) пунктира. Шаблон пунктира задается вторым аргументом в виде шестнадцатибитного числа, где каждый бит числа определяет вид шаблона. Равномерный шаблон получается если задаешь его в виде 0x3333 (т.е. 0011 0011 0011 0011). Аргумент factor - это просто множитель, определяющий сколько раз подряд будет использоваться каждый бит в шаблоне. По-умолчанию он равен 1.

Включаем режим отрисовки линий пунктиром функцией glEnable(GL_LINE_STIPPLE).

Настройки точек

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

В glDrawArrays() первым аргументом мы передадим тип примитивов, в нашем случае GL_POINTS, вторым идет индекс первого элемента массива, который мы отрисовываем просто укажем 0, третьим аргументом количество вершин(!), именно вершин, а не координат вершин.

Цвет точки

Абсолютно также задается, как и цвет линии

Размер точки

glPointSize(size)

size имеет тип float.

Тип точки

Тип точки самое интересное. glEnable(GL_POINT_SMOOTH) перед отрисовкой и glDisable(GL_POINT_SMOOTH) после отрисовки.

Круто, только есть такая проблема, что эта функция работает не на всех видеокарточках.

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

Если кто-то знает другие способы скруглить точки, пишите об этом.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published