无法将法线添加到.obj网格中

时间:2014-08-20 17:57:48

标签: c++ opengl blender openframeworks .obj

我已经在Blender中创建了一个形状(我确保三角形并添加法线)我已经以.obj格式导出以在openframeworks项目中使用。我还写了一个小类来解析这个.obj文件。形状画得很完美,但我似乎无法正常应用法线。

导出仅包含顶点,法线和面。正如.obj格式所指示的那样:我将顶点添加到网格中并使用提供的索引来绘制面。法线和法线的索引以类似的方式提供。如下图所示,我没有正确应用它们。

enter image description here

这是我的模型加载器脚本:

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;
}

2 个答案:

答案 0 :(得分:1)

我认为这可能是索引错误,因为您的位置索引和普通数组不对应。通常,在使用OpenGL创建顶点缓冲区时,所有顶点属性(position,normal,texcoord等)数组的长度必须相同。因此,当三角形定义为索引[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组合是否已经存在,并使用那些现有索引而不是将冗余数据添加到数组中。在立方体示例中,这将导致前两个面使用4个顶点而不是使用6个顶点的索引[0,1,2,3,4,5]作为索引[0,1,2,1,2,3],

答案 1 :(得分:0)

通过阅读文件的方式,您根本不需要任何索引。您正在复制加载的每个面(三角形)的所有顶点和法线。因此不需要索引,您可以按顺序使用顶点进行绘制。如果您直接使用OpenGL,则可以使用glDrawArrays(GL_TRIANGLES, ...)代替glDrawElements(GL_TRIANGLES, ...)。对于您正在使用的框架,可能存在相同的选项。

要有效渲染模型,您需要实际共享顶点,并使用索引缓冲区。您需要为位置和法线的每个唯一组合创建一个OpenGL顶点。以下是我之前关于该主题的两个回答,更详细地说明了这是如何工作的:

OpenGL - Index buffers difficulties

Why is my OBJ parser rendering meshes like this?