Урок 8. Загрузка мешей в OpenGL4

20.08.2012

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

Форматы мешей

Загрузка внешних 3d моделей, — мешей — необходимая часть любого 3d приложения. В основном, каждое приложение, в частности, много игр поддерживают свои собственные форматы мешей. В основном, уникальность формата меша определяется уникальностью движка игры. Как правило, у каждого движка игры существуют свои собственный формат мешей. В нашем текущем уроке мы разберем формат .obj.

Данные, экспортируемые при экспорте меша из 3dредактора

При экспорте меша как правило существуют определенные данные, обязательные для экспорта:

  • координаты вершин
  • индексы полигонов
  • нормали
  • материалы

Вы уже догадываетесь, что первое что экспортируется, то есть обязательные данные для экспорта – это индексные и вершинные буферы, то есть координаты и индексы вершин. Нормали используются для правильного освещения меша и могут быть также быть экспортированы из 3d редактора. Материалы используются тогда, когда меш состоит из нескольких частей, например с различными текстурами или фактурами. Например, одна из частей меша может быть металлической, в таком случае ей можно назначить отдельный материал и в последствии создать специальный шейдер для его отображения.

Правильный экспорт мешей

Существует пара простых правил для хорошего экспорта меша из редактора.

  • Всему мешу целиком назначайте в редакторе один материал и одну текстуру. Используйте для всего меша не больше одной текстуры и не больше одного материала.
  • Если функция загрузки мешей по каким-то причинам в вашей программе не подхватывает автоматическую загрузку текстуры, имя которой прописано в материале, или при экспорте меша вообще не экспортируется материал, загружайте ваши меши функцией наподобие loadmesh(“mesh.obj”,”texture.jpg”); то есть, указывайте в функции загрузки меша отдельно имя текстуры. Или же загружайте отдельно, сначала меш, а затем текстуру к нему.
  • Если после загрузки меша меш отображается очень темным, то попробуйте осуществить либо в редакторе либо в функции загрузке меша операцию flipnormals, то есть обращение нормалей.
  • Если после загрузки меша в вашей программе этот меш отображается в виде маленькой точки или не виден совсем, значит подберите его масштаб при экспорте из редактора – либо увеличьте меш, чтобы он стал больше чем точка, либо уменьшите его, чтобы он поместился на экран и стал виден.

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

Загрузка меша из формата .obj

В нашем примере мы будем рассматривать загрузку меша из формата .obj. Загрузив меш самостоятельно при помощи собственного загрузчика из формата .obj мы получим полный контроль над процессом загрузки и рендера меша.

Ознакомимся с форматом obj и способом, существующим для его загрузки. Формат objявляется по сути дампом вершинного и индексного буфера модели в редакторе. Формат obj текстовый, то есть вы можете любой файл .obj открыть текстовым редактором. Вот типичный пример файла формата obj:

#
mtllib ./box2.mtl
g
# object (null) to come ...
#-1.968504 0.000000 1.9685041.968504 0.000000 1.968504-1.968504 0.000000 -1.9685041.968504 0.000000 -1.968504-1.968504 3.937008 1.9685041.968504 3.937008 1.968504-1.968504 3.937008 -1.9685041.968504 3.937008 -1.968504
# 8 vertices
 
vt  0.000000 0.000000 0.000000
vt  1.000000 0.000000 0.000000
vt  0.000000 1.000000 0.000000
vt  1.000000 1.000000 0.000000
vt  0.000000 0.000000 0.000000
vt  1.000000 0.000000 0.000000
vt  0.000000 1.000000 0.000000
vt  1.000000 1.000000 0.000000
vt  0.000000 0.000000 0.000000
vt  1.000000 0.000000 0.000000
vt  0.000000 1.000000 0.000000
vt  1.000000 1.000000 0.000000
# 12 texture vertices
 
vn  0.000000 -2.000000 -0.000000
vn  0.000000 -1.000000 -0.000000
vn  0.000000 -1.000000 -0.000000
vn  0.000000 -2.000000 -0.000000
vn  0.000000 2.000000 -0.000000
vn  0.000000 1.000000 -0.000000
vn  0.000000 1.000000 -0.000000
vn  0.000000 2.000000 -0.000000
# 8 vertex normals
 
g (null)
f 1/10/1 3/12/3 4/11/4
f 4/11/4 2/9/2 1/10/1
f 5/9/5 6/10/6 8/12/8
f 8/12/8 7/11/7 5/9/5
f 1/5/1 2/6/2 6/8/6
f 6/8/6 5/7/5 1/5/1
f 2/1/2 4/2/4 8/4/8
f 8/4/8 6/3/6 2/1/2
f 4/5/4 3/6/3 7/8/7
f 7/8/7 8/7/8 4/5/4
f 3/1/3 1/2/1 5/4/5
f 5/4/5 7/3/7 3/1/3
# 12 faces
 
g

Разберем формат файла obj. Каждая строка начинается с идентификацинного символа. Комментарии # не учитываются. Символы бывают следующие: v, vt, vn, f. Эти символы нам понадобятся больше всего. Символ v обозначает вершину. После этого символа в строке идут координаты этой вершины. Символ vt обозначает текстурные координаты. Так как текстурные координаты имеют только u и v координаты, то третья цифра для них будет всегда равна нулю. Символ vn обозначает нормаль.

При загрузке в программу каждой строке, начинающейся с идентификационного символа  v, vt или vn присваивается индекс, так, как если бы сам файл содержал индексы каждой строки, таким образом:

#
mtllib ./box2.mtl
g
# object (null) to come ...
#-1.968504 0.000000 1.968504     [1]1.968504 0.000000 1.968504[2]-1.968504 0.000000 -1.968504    [3]1.968504 0.000000 -1.968504     [4]-1.968504 3.937008 1.968504     [5]1.968504 3.937008 1.968504[6]-1.968504 3.937008 -1.968504    [7]1.968504 3.937008 -1.968504     [8]
# 8 vertices
 
vt  0.000000 0.000000 0.000000     [1]
vt  1.000000 0.000000 0.000000     [2]
vt  0.000000 1.000000 0.000000     [3]
vt  1.000000 1.000000 0.000000     [4]
vt  0.000000 0.000000 0.000000     [5]
vt  1.000000 0.000000 0.000000     [6]
vt  0.000000 1.000000 0.000000     [7]
vt  1.000000 1.000000 0.000000     [8]
vt  0.000000 0.000000 0.000000     [9]
vt  1.000000 0.000000 0.000000     [10]
vt  0.000000 1.000000 0.000000     [11]
vt  1.000000 1.000000 0.000000     [12]
# 12 texture vertices
 
vn  0.000000 -2.000000 -0.000000   [1]
vn  0.000000 -1.000000 -0.000000   [2]
vn  0.000000 -1.000000 -0.000000   [3]
vn  0.000000 -2.000000 -0.000000   [4]
vn  0.000000 2.000000 -0.000000    [5]
vn  0.000000 1.000000 -0.000000    [6]
vn  0.000000 1.000000 -0.000000    [7]
vn  0.000000 2.000000 -0.000000    [8]
# 8 vertex normals
 
g (null)
f 1/10/1 3/12/3 4/11/4
f 4/11/4 2/9/2 1/10/1
f 5/9/5 6/10/6 8/12/8
f 8/12/8 7/11/7 5/9/5
f 1/5/1 2/6/2 6/8/6
f 6/8/6 5/7/5 1/5/1
f 2/1/2 4/2/4 8/4/8
f 8/4/8 6/3/6 2/1/2
f 4/5/4 3/6/3 7/8/7
f 7/8/7 8/7/8 4/5/4
f 3/1/3 1/2/1 5/4/5
f 5/4/5 7/3/7 3/1/3
# 12 faces
 
g

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

Каждая тройка чисел в строке f формата obj обозначает следующие: номер вершины v, номер координаты текстуры vt, и номер нормали vn и так три раза. Также обратите внимание, что вершин(8) в нашем примере меньше чем текстурных координат к ним (12).  Конечно же для OpenGLравенство количества вершин и текстурных координат должно соблюдаться. Таким образом, формат obj содержит только условно дамп индексного буфера. На самом деле, требуется определенный алгоритм, чтобы преобразовать данные из obj в настоящий вершинный и индексный буфер.

Этот алгоритм следующий: Возьмем первые три цифры из первой строки f. Это индексы 1/10/1. Соответственно, возьмем первую строку v, десятую строку vt и первую строку vn.

-1.968504 0.000000 1.968504     [1]
vt  1.000000 0.000000 0.000000     [10]
vn  0.000000 -2.000000 -0.000000   [1]

Теперь у нас образовался набор данных, как раз и соответсвующих одной вершине для вершинного буфера, а именно: координата, текстурная координата и нормаль. Теперь образуем из этих данных структуру типа VERTEX:

struct VERTEX {
      glm::vec3 pos;
      glm::vec3 normal;
      glm::vec2 texcoord;
}
VERTEX vertices[1024];

У нас существует временный массив для вершинного буфера vertices. Добавим данные вершины 1/10/1 в этот массив. Номер этой вершины массива vertices будет равен 0, так как она первая.

vertices[0]= ... v, vt, vn of 1/10/1 vertex ...

Затем возьмем следующим элемент из строки f. Это будет 3/12/3.

vertices[1]= ... v, vt, vn of 3/12/3 vertex ...

Таким же образом возьмем следующим элемент из строкиf. Это будет 4/11/4.

vertices[2]= ... v, vt, vn of 4/11/4 vertex ...

Одновременно с увеличением индекса новых элементов вершинного буфера – это числа 0, 1, 2 записываем эти индексы в индексный буфер.

indices[count++]=0;
indices[count++]=1;
indices[count++]=2;

Таким образом, при каждой новой тройке чисел из строки f мы должны собрать новый элемент типа VERTEX и добавить этот элемент в вершинный буфер, если там нет точно такого же элемента. Если во временном вершинном буфере уже содержится элемент с точно такими же координатами, текстурными координатами и нормалью мы не должны его добавлять заново а просто мы должны определить номер этого элемента. Таким образом, индексный буфер будет содержать данные наподобие тех, что на этом листинге:

indices[count++]=0[new];
indices[count++]=1[new];
indices[count++]=2[new];
indices[count++]=3[new];
indices[count++]=2[copy];
indices[count++]=4[new];

Код загрузки данных из формата obj

Алгоритм, описанный выше реализован программно. Вы можете его найти в исходном тексте meshobj.cpp, данный код содержится в методе MeshFromObj::LoadMeshFromObj. Обратите внимание, что перед тем как осуществить проход формата obj создается три временных буфера для хранения массивов v, vt и vn соответственно.

Данный код существует в виде метода объекта MeshFromObj и является частью модуля meshobj.h. Для того, чтобы использовать объект MeshFromObj и при помощи него загружать данные из внешних мешей необходимо подключить к вашему OpenGL приложению модуль meshobj.h.

Загрузка и отображение меша при помощи модуля meshobj

Модуль meshobj.h имеет функцию загрузки и рендера меша, загружаемого из файла obj. Сначала подключите этот модуль к вашему OpenGL приложению:

#include "meshobj.h"

Вся функциональность модуля осуществляется через базовый объект типа MeshFromObj. Создав несколько экземпляров этого объекта вы можете загрузить несколько различных мешей. Сначала объявите переменную для хранения экземпляра объекта:

MeshFromObj*      g_MeshFromObj=NULL;

Затем создайте экземпляр объекта и загрузите меш:

g_MeshFromObj=new MeshFromObj(”mesh.obj);

Рендер меша вы можете осуществить припомощи метода MeshFromObj::Draw

g_MeshFromObj.Draw();

Метод MeshFromObj::Draw самостоятельно устанавливает вершинные и индексные буферы OpenGL. Таким образом, для рендера меша нужно просто вызвать Draw. Чтобы поместить меш в нужное место сцены, конечно, установите также матрицы трансформаций перед вызовом метода.

Обзор приложения

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

Заключение

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

Заполнен: OpenGL4
Присвоен тэг:

avatar

Об Авторе ()

Планета назначения, JO - 8703 - IV, обозначается светлой точкой на объемном экране корабля. К тому времени, как Робоид нанялся на юпитерианский круговой рейс, ему следовало бы стать главным инженером, но после Суховодного пионерского его вышибли, внеся в черный список, и высадили в Луна-Сити за то, что вместо слежения за приборами он провел время, программируя синтезатор пищи корабля строжайше запрещенной корабельными инструкциями последовательностью синтеза пива.

Комментирование закрыто.

Наверх