我已经在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;
}
答案 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顶点。以下是我之前关于该主题的两个回答,更详细地说明了这是如何工作的: