Не удалось добавить нормали к сетке .obj

Я создал форму в Blender (я сделал триангуляцию граней и добавил нормали), которую я экспортировал в формат .obj для использования в проекте openframeworks. Я также написал небольшой класс для анализа этого OBJ-файла. Форма нарисована идеально, но я не могу правильно применить нормали.

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

введите описание изображения здесь

Вот мой сценарий загрузчика модели:

modelLoader.h

#ifndef _WAVEFRONTLOADER
#define _WAVEFRONTLOADER

#include "ofMain.h"

class waveFrontLoader {

public:

ofMesh mesh;

waveFrontLoader();
~waveFrontLoader();

void loadFile(char *fileName);
ofMesh generateMesh();

private:

typedef struct
{
    ofIndexType v1,v2,v3;
    ofIndexType vn1,vn2,vn3;
}
Index;

std::vector<ofVec3f> vertices;
std::vector<ofVec3f> normals;
std::vector<Index> indices;

void parseLine(char *line);
void parseVertex(char *line);
void parseNormal(char *line);
void parseFace(char *line);
};

#endif

modelLoader.cpp

#include "waveFrontLoader.h"

waveFrontLoader::waveFrontLoader()
{

}

void waveFrontLoader::loadFile(char *fileName)
{
ifstream file;
char line[255];

//open file in openframeworks data folder
file.open(ofToDataPath(fileName).c_str());

if (file.is_open())
{
    while (file.getline(line,255))
    {
        parseLine(line);
    }
}
}

void waveFrontLoader::parseLine(char *line)
{
//If empty, don't do anything with it
if(!strlen(line))
{
    return;
}

//get line type identifier from char string
char *lineType = strtok(strdup(line), " ");

//parse line depending on type
if (!strcmp(lineType, "v")) // Vertices
{
    parseVertex(line);
}
else if (!strcmp(lineType, "vn")) // Normals
{
    parseNormal(line);
}
else if (!strcmp(lineType, "f")) // Indices (Faces)
{
    parseFace(line);
}
}

void waveFrontLoader::parseVertex(char *line)
{
float x;
float y;
float z;

vertices.push_back(ofVec3f(x,y,z));

//get coordinates from vertex line and assign
sscanf(line, "v %f %f %f", &vertices.back().x, &vertices.back().y, &vertices.back().z);
}

void waveFrontLoader::parseNormal(char *line)
{
float x;
float y;
float z;

normals.push_back(ofVec3f(x,y,z));

//get coordinates from normal line and assign
sscanf(line, "vn %f %f %f", &normals.back().x, &normals.back().y, &normals.back().z);
}

void waveFrontLoader::parseFace(char *line)
{
indices.push_back(Index());

//get vertex and normal indices
sscanf(line, "f %d//%d %d//%d %d//%d",
       &indices.back().v1,
       &indices.back().vn1,
       &indices.back().v2,
       &indices.back().vn2,
       &indices.back().v3,
       &indices.back().vn3);
}

ofMesh waveFrontLoader::generateMesh()
{
//add vertices to mesh
for (std::vector<ofVec3f>::iterator i = vertices.begin(); i != vertices.end(); ++i)
{
    mesh.addVertex(*i);
}

//add indices to mesh
for (std::vector<Index>::iterator i = indices.begin(); i != indices.end(); ++i)
{
    // -1 to count from 0
    mesh.addIndex((i->v1) - 1);
    mesh.addIndex((i->v2) - 1);
    mesh.addIndex((i->v3) - 1);

    mesh.addNormal(normals[(i->vn1) - 1]);
    mesh.addNormal(normals[(i->vn2) - 1]);
    mesh.addNormal(normals[(i->vn3) - 1]);

}

return mesh;
}

waveFrontLoader::~waveFrontLoader()
{

}

Я тоже пробовал добавлять такие нормали (имеет смысл, так как это одна нормаль на лицо):

mesh.addNormal(normals[(i->vn1) - 1]);

Я также пробовал добавлять нормали только один раз на два нарисованных треугольника и пробовал добавлять индексы и нормали перед индексами. Ни один из них тоже не работал.

testApp.h

#pragma once

#include "ofMain.h"
#include "waveFrontLoader.h"

class testApp : public ofBaseApp{

public:
    void setup();
    void update();
    void draw();
    void exit();

    waveFrontLoader *objectLoader;
    ofMesh mesh;
    ofEasyCam camera;
    ofLight light;
};

testApp.cpp

#include "testApp.h"

//--------------------------------------------------------------
void testApp::setup()
{
glEnable(GL_DEPTH_TEST);
glEnable(GL_LIGHTING);

ofBackground(10, 10, 10);

camera.setDistance(10);
light.setPosition(10,30,-25);

objectLoader = new waveFrontLoader();
objectLoader->loadFile("test.obj");
mesh = objectLoader->generateMesh();
}

//--------------------------------------------------------------
void testApp::update()
{

}

//--------------------------------------------------------------
void testApp::draw()
{
camera.begin();
light.enable();
mesh.draw();
light.disable();
camera.end();
}

void testApp::exit()
{
delete objectLoader;
}

person anthony    schedule 20.08.2014    source источник
comment
Опубликуйте свой связанный с OpenGL код, который рисует модель.   -  person kbirk    schedule 20.08.2014
comment
Добавлен основной класс рисования   -  person anthony    schedule 20.08.2014
comment
Можете выложить код из ofMesh :: draw? Я хочу посмотреть, в каком формате вы отправляете данные о вершинах.   -  person kbirk    schedule 20.08.2014
comment
ofMesh - это встроенный класс openframeworks. Источник доступен здесь: github.com/openframeworks/ openFrameworks / blob / master / libs /   -  person anthony    schedule 20.08.2014


Ответы (2)


Я думаю, это может быть ошибка индексации из-за несоответствия индексов вашей позиции и обычных массивов. Обычно при создании буферов вершин с помощью OpenGL все массивы атрибутов вершин (позиция, нормаль, текскоорд и т. Д.) Должны иметь одинаковую длину. Поэтому, когда треугольник определяется как индексы [0, 1, 2], он будет использовать позиции [v0, v1, v2] и нормали [n0, n1, n2].

Давайте на примере куба рассмотрим, что происходит в вашем коде.

Файл .obj куба будет содержать:

- 8 positions, lets call them v0 to v7
- 6 normals, lets call them n0 to n5
- 12 faces/triangles of format v//n v//n v//n, called f0 to f11

В коде generateMesh () вы должны отправить массив вершин:

[ v0, v1, v2, v3, v4, v5, v6, v7 ] // length of 8

индексный массив:

[ f0.a, f0.b, f0.c, .... f11.a, f11.b, f11.c ] // length of 36

и нормальный массив

[ n0, n0, n0, n0, n0, n0, n1, n1, n1, ... n5, n5, n5 ] // length of 36.

В этом примере значения индекса треугольника будут находиться в диапазоне от [0 до 7] для позиций и от [0 до 5] для нормалей. Это работает для ваших отправленных вершин, но ваши отправленные нормали находятся в диапазоне от [0 до 31].

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

ofMesh waveFrontLoader::generateMesh()
{
    int indexCount = 0;
    for (std::vector<Index>::iterator i = indices.begin(); i != indices.end(); ++i)
    {
        // add face of positions, -1 to count from 0
        mesh.addVertex(vertices[(i->v1) - 1]);
        mesh.addVertex(vertices[(i->v2) - 1]);
        mesh.addVertex(vertices[(i->v3) - 1]);

        // add face of normals, -1 to count from 0
        mesh.addNormal(normals[(i->vn1) - 1]);
        mesh.addNormal(normals[(i->vn2) - 1]);
        mesh.addNormal(normals[(i->vn3) - 1]);

        // in this code we are defining our vertex arrays 
        // according to the indices, so they will always
        // be [0 to n]
        mesh.addIndex( indexCount++ );
        mesh.addIndex( indexCount++ );
        mesh.addIndex( indexCount++ );   
    }
}

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

Более сложным подходом было бы использование std :: map или std :: set, чтобы проверить, существует ли уже комбинация position + normal + etc, и использовать эти существующие индексы вместо добавления избыточных данных в массив. В примере с кубом это приведет к тому, что первые две грани будут индексами [0, 1, 2, 1, 2, 3] с использованием 4 вершин, а не индексами [0, 1, 2, 3, 4, 5] с использованием 6 вершин,

person kbirk    schedule 20.08.2014
comment
Ваш метод сработал идеально! Большое спасибо за разъяснение. Я не понимал, что индексный и нормальный векторы должны быть одинакового размера. Я буду работать над оптимизацией скрипта, чтобы избежать дублирования. - person anthony; 21.08.2014

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

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

OpenGL - трудности с индексными буферами

Почему мой анализатор OBJ отображает такие сетки?

person Reto Koradi    schedule 20.08.2014