Формат 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
#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;
}
Комментарии:
Войдите, чтобы оставить комментарий: