Array ( )
Вход:




Главная | OpenGL | GLSL | AI | Сеть | Примеры | Библиотека

OpenGL: Загрузка моделей формата .obj






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

Код урока взят из урока "загрузка текстур".
Минус формата .obj - он не хранит информации о материалах, так что мне пришлось придумать другой способ.
Я бы не стал использовать этот формат для демки или игры, но полезно уметь с ним обращатся, так как
он очень прост и популярен. Вообще, надо было написать этот урок раньше загрузки 3sd...

В этом уроке мы будем использовать STL vector. Что это такое я подробно описал в уроке "загрузка .3ds".

Вот небольшое описание формата OBJ. Каждая программа, экспортирующая и импортирующая этот формат,
делает это по-своему. Некоторые сохраняют нормали, некоторые - имена обьектов, и так далее.

Данные вы читаете на основе первого символа в строке.

"v" - эта линия содержит вершину (x y z)

// пример: v -1 -1 0

Прочитав все такие линии, вы получите геометрию обьекта.

"vt" - текстурные координаты обьекта (U V)

// пример: vt .99998 .99936

"f" - эта линия содержит индексы вершин для массива полигонов.
Если есть UV координаты, она содержит также и их индексы.

// пример: (Только вершины): f 1 2 3

// пример: (Вершины и текстуры): f 1/1 2/2 3/3





Теперь напишем класс, загружающий .obj-файл.

Новый файл: obj.h:
#ifndef _OBJ_H
#define _OBJ_H

#include "main.h"

// Класс, загружающий файл формата OBJ
class CLoadObj {

public:

    // Вы будете вызывать только эту функцию. Просто передаёте структуру
    // модели для сохранения данных, и имя файла для загрузки.
    bool ImportObj(t3DModel *pModel, char *strFileName);

    // Главный загружающий цикл, вызывающийся из ImportObj()
    void ReadObjFile(t3DModel *pModel);

    // Вызывается в ReadObjFile() если линия начинается с 'v'
    void ReadVertexInfo();

    // Вызывается в ReadObjFile() если линия начинается с 'f'
    void ReadFaceInfo();

    // Вызывается после загрузки информации полигонов
    void FillInObjectInfo(t3DModel *pModel);

    // Вычисление нормалей. Это не обязятельно, но очень желательно.
    void ComputeNormals(t3DModel *pModel);

    // Так как .obj файлы не хранят имен текстур и информации о материалах, мы создадим
    // функцию, устанавливающую их вручную. materialID - индекс для массива pMaterial нашей модели.
    void SetObjectMaterial(t3DModel *pModel, int whichObject, int materialID);

    // Чтобы проще присваивать материал к .obj обьекту, создадим для этого функцию.
    // Передаём в неё модель, имя материала, имя файла текстуры и цвет RGB.
    // Если нам нужен только цвет, передаём NULL для strFile.
    void AddMaterial(t3DModel *pModel, char *strName, char *strFile,
                     int r = 255,      int g = 255,   int b = 255);

private:

    // Указатель на файл
    FILE *m_FilePointer;

    // STL vector, содержащий список вершин
    vector<CVector3>  m_pVertices;

    // STL vector, содержащий список полигонов
    vector<tFace> m_pFaces;

    // STL vector, содержащий список UV координат
    vector<CVector2>  m_pTextureCoords;

    // Говорит нам, имеет ли обьект текстурные координаты
    bool m_bObjectHasUV;

    // Говорит нам, что мы только что прочитали данные полигонов, чтобы мы могли читать несколько обьектов
    bool m_bJustReadAFace;
};


#endif


Файл obj.cpp:
[code]
#include "main.h"
#include "obj.h"


///////////////////////////////// IMPORT OBJ \\\\\\\\\\\\\\\\*
/////
///// Функция загружает файл .obj в указанную переменную из указанного имени файла
/////
///////////////////////////////// IMPORT OBJ \\\\\\\\\\\\\\\\*

bool CLoadObj::ImportObj(t3DModel *pModel, char *strFileName)
{
char strMessage[255] = {0}; // Будет использоваться для сообщения об ошибке

// Убедимся, что переданы не пустые модель и имя файла
if(!pModel || !strFileName) return false;

// Открываем указанный файл для чтения
m_FilePointer = fopen(strFileName, "r");

// Убедимся, что файл правильно открыт
if(!m_FilePointer) {
// Сформируем сообщение об ошибке
sprintf(strMessage, "Unable to find or open the file: %s", strFileName);
MessageBox(NULL, strMessage, "Error", MB_OK);
return false;
}

// Теперь, имея открытый файл, считываем информацию
ReadObjFile(pModel);

// Прочитав всю информацию, вычисляем вершинные нормали
ComputeNormals(pModel);

// Закрываем файл
fclose(m_FilePointer);

// И возвращаем true
return true;
}


///////////////////////////////// READ OBJ FILE \\\\\\\\\\\\\\\\*
/////
///// Эта функция - главный цикл чтения файла .obj
/////
///////////////////////////////// READ OBJ FILE \\\\\\\\\\\\\\\\*

void CLoadObj::ReadObjFile(t3DModel *pModel)
{
char strLine[255] = {0};
char ch = 0;

while(!feof(m_FilePointer))
{
float x = 0.0f, y = 0.0f, z = 0.0f;

// Читаем первый символ текущей строки файла
ch = fgetc(m_FilePointer);

switch(ch)
{
case 'v': // Проверяем, не 'v' ли это (может быть вершина/нормаль/текст. коорд.)

// Если мы только что читали информацию о полигоне, а сейчас читаем вершину,
// значит мы перешли к следующему обьекту, и нужно сохранить данные предыдущего.
if(m_bJustReadAFace) {
// Сохраняем данные последнего обьекта в структуру модели
FillInObjectInfo(pModel);
}

// Расшифровываем всю текущую линию
ReadVertexInfo();
break;

case 'f': // Если первый символ -'f', эта строка описывает полигон

// Нужно считать информацию о полигоне. Строки полигонов могут
// содержать или индексы вершин, или ещё и текстурные координаты,
// если они есть у обьекта.
ReadFaceInfo();
break;

case 'n':

// Если прочитан символ новой строки - это пустая строка, ничего не делаем.
break;

default:
// Если что-то неизвестное, просто читаем эту строку в "мусор", чтобы перейти
// к следующей - нам она не нужна.
fgets(strLine, 100, m_FilePointer);
break;
}
}

// Теперь сохраняем последний прочитанный обьект
FillInObjectInfo(pModel);
}


///////////////////////////////// READ VERTEX INFO \\\\\\\\\\\\\\\\*
/////
///// Эта функция читает информацию о вершинах ("v" вершина : "vt" UVCoord)
/////
///////////////////////////////// READ VERTEX INFO \\\\\\\\\\\\\\\\*

void CLoadObj::ReadVertexInfo()
{
CVector3 vNewVertex = {0};
CVector2 vNewTexCoord = {0};
char strLine[255] = {0};
char ch = 0;

// Читаем второй сисвол строки, чтобы увидеть, что содержит строка: вершины/нормали/UV
ch = fgetc(m_FilePointer);

if(ch == ' ') // Если только пробел - далее идёт только иняормация о вершинах ("v")
{
// Читаем вершину. Формат: "v x y z"
fscanf(m_FilePointer, "%f %f %f", &vNewVertex.x, &vNewVertex.y, &vNewVertex.z);

// Читаем остальную линию, чтобы перейти к следующей
fgets(strLine, 100, m_FilePointer);

// Добавляем новую вершину в список
m_pVertices.push_back(vNewVertex);
}
else if(ch == 't') // Если второй символ - 't', строка содержит UV координаты ("vt")
{
// Читаем текстурные координаты. Формат: "vt u v"
fscanf(m_FilePointer, "%f %f", &vNewTexCoord.x, &vNewTexCoord.y);

// Читаем оставшуюся линию, чтобы перейти к следующей
fgets(strLine, 100, m_FilePointer);

// Вносим новые текстурные координаты в список
m_pTextureCoords.push_back(vNewTexCoord);

// Активируем флаг, сообщающий, что обьект имеет текстурные координаты.
// Теперь мы знаем, что строки полигонов будут содержать индексы не только
// вершин, но и текст. координат ("f 1/1 2/2 3/3")
m_bObjectHasUV = true;
}
else // Иначе это, видимо, нормаль, и нам она не нужна ("vn")
{
// Мы рассчитываем собственные нормали, так что пропустим строку
fgets(strLine, 100, m_FilePointer);
}
}


///////////////////////////////// READ FACE INFO \\\\\\\\\\\\\\\\*
/////
///// Читает информацию полигона ("f")
/////
///////////////////////////////// READ FACE INFO \\\\\\\\\\\\\\\\*

void CLoadObj::ReadFaceInfo()
{
tFace newFace = {0};
char strLine[255] = {0};

// Функция читает информацию о полигонах обьекта.
// Эта информация - 3д точки, составляющие полигон, и UV координаты,
// если на обьект наложена текстура.
// Если обьект имеет текстурные координаты, формат строки будет
// такой: "f v1/uv1 v2/uv2 v3/uv3"
// Иначе такой: "f v1 v2 v3"
// Внимание! Всегда убеждайтесь, что вычитаете 1 из индексов, так как
// массивы в c++ начинаются с 0, а индексы в .obj начинаются с 1.

// Проверяем, имеет ли обьект текстурные координаты
if(m_bObjectHasUV )
{
// Читаем индексы вершин и текстурных координат.
fscanf(m_FilePointer, "%d/%d %d/%d %d/%d", &newFace.vertIndex[0], &newFace.coordIndex[0],
&newFace.vertIndex[1], &newFace.coordIndex[1],
&newFace.vertIndex[2], &newFace.coordIndex[2]);
}
else // если обьект НЕ содержит текстурных координат
{
// Читаем только индексы вершин
fscanf(m_FilePointer, "%d %d %d", &newFace.vertIndex[0],
&newFace.vertIndex[1],
&newFace.vertIndex[2]);
}

// Читаем линию до конца, чтобы перейти к следующей
fgets(strLine, 100, m_FilePointer);

// Добавляем новый полигон в список
m_pFaces.push_back(newFace);

// Устанавливаем этот флаг в TRUE, чтобы знать, что только что читали полигон. Если
// после этого читается вершина - значит, мы перешли к следующему обьекту и нужно
// сохранить этот.
m_bJustReadAFace = true;
}









Комментарии:

Войдите, чтобы оставить комментарий:












Яндекс.Метрика
 Яндекс цитирования.