圖形學基礎 | 實現OBJ文件的載入

在做一個軟光柵化渲染器.
增加對複雜模型的支持.
選擇對OBJ文件進行支持.

OBJ文件格式介紹

文件格式的介紹可以參考前一篇文章
圖形學基礎 | 詳解3D中的obj文件格式

OBJ文件載入

這裏找了一個開源的OBJ文件解析的庫 tinyobjloader.
只有一個頭文件 tiny_obj_loader.h
主要的使用方法可以參考Github上的 loader_example.cc Demo.

1. tiny_obj_loader.h 的使用

include這個頭文件需要先定義一個宏

#define TINYOBJLOADER_IMPLEMENTATION
#include "tiny_obj_loader.h"

2. tiny_obj_loader.h 中數據結構的介紹

2.1 attrib_t

// Vertex attributes
typedef struct {
  std::vector<real_t> vertices;   // 'v'
  std::vector<real_t> normals;    // 'vn'
  std::vector<real_t> texcoords;  // 'vt'
  std::vector<real_t> colors;     // extension: vertex colors
} attrib_t;

attrib_t 主要存放的就是 OBJ文件中所有的頂點數據信息. 比如:

  • vertices : 頂點位置信息
  • normals : 法線信息
  • texcoords : 紋理座標信息
  • colors : 顏色信息

2.2 shape_t

typedef struct {
  std::string name;
  mesh_t mesh;
  path_t path;
} shape_t;

shape_t 表示的是比如這個物體的一個部分.
在 OBJ文件中, 如 o xxx 表示一個部分的開始.主要存儲的信息有:

  • name : 這部分的名稱 xxx
  • mesh : 構成這個部分的頂點信息. 這裏通過使用索引的方式來記錄 . 因爲所有的數據信息都放在了 attrib_t 中.
  • path : pairs of indices for lines 按註釋的意思是 線段的索引. 可能有問題. 因爲沒有用到這個.

在這裏重點在於 mesh_t 這個數據結構:

// Index struct to support different indices for vtx/normal/texcoord.
// -1 means not used.
typedef struct {
  int vertex_index;
  int normal_index;
  int texcoord_index;
} index_t;

typedef struct {
  std::vector<index_t> indices;
  std::vector<unsigned char> num_face_vertices;  // The number of vertices per
                                                 // face. 3 = polygon, 4 = quad,
                                                 // ... Up to 255.
  std::vector<int> material_ids;                 // per-face material ID
  std::vector<unsigned int> smoothing_group_ids;  // per-face smoothing group
                                                  // ID(0 = off. positive value
                                                  // = group id)
  std::vector<tag_t> tags;                        // SubD tag
} mesh_t;

索引信息重要放在 std::vector<index_t> indices; 中.
index_t 中是哪些數據信息的索引下標呢:

  • vertices : 頂點位置信息
  • normals : 法線信息
  • texcoords : 紋理座標信息

3. 通過 tiny_obj_loader.h 讀取並存儲數據

  • 主要參考了 loader_example.ccPrintInfo(attrib, shapes, materials) 這個函數
bool Object::make_mesh_and_material_by_obj(const char* filename, const char* basepath,bool triangulate){

	std::cout << "Loading " << filename << std::endl;

	tinyobj::attrib_t attrib; // 所有的數據放在這裏
	std::vector<tinyobj::shape_t> shapes; 
	// 一個shape,表示一個部分,
	// 其中主要存的是索引座標 mesh_t類,
	// 放在indices中
	/*
	// -1 means not used.
	typedef struct {
	  int vertex_index;
	  int normal_index;
	  int texcoord_index;
	} index_t;
	*/
	std::vector<tinyobj::material_t> materials;

	std::string warn;
	std::string err;

	bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, filename,
		basepath, triangulate);

	// 接下里就是從上面的屬性中取值了
	if (!warn.empty()) {
		std::cout << "WARN: " << warn << std::endl;
	}

	if (!err.empty()) {
		std::cerr << "ERR: " << err << std::endl;
	}

	if (!ret) {
		printf("Failed to load/parse .obj.\n");
		return false;
	}

	// ========================== 將讀入的模型數據存入自己定義的數據結構中 ========================
	
	std::cout << "# of vertices  : " << (attrib.vertices.size() / 3) << std::endl;
	std::cout << "# of normals   : " << (attrib.normals.size() / 3) << std::endl;
	std::cout << "# of texcoords : " << (attrib.texcoords.size() / 2)
		<< std::endl;

	std::cout << "# of shapes    : " << shapes.size() << std::endl;
	std::cout << "# of materials : " << materials.size() << std::endl;

	///1. 獲取各種材質和紋理
	{
		for (int i = 0; i < materials.size(); i++) {
			Material* m = new Material();
			tinyobj::material_t tm = materials[i];
			string name = tm.name;
			if (name.size()) {
				m->name = name;
			}
			m->ambient.r = tm.ambient[0];
			m->ambient.g = tm.ambient[1];
			m->ambient.b = tm.ambient[2];

			m->diffuse.r = tm.diffuse[0];
			m->diffuse.g = tm.diffuse[1];
			m->diffuse.b = tm.diffuse[2];

			m->specular.r = tm.specular[0];
			m->specular.g = tm.specular[1];
			m->specular.b = tm.specular[2];

			m->transmittance.r = tm.transmittance[0];
			m->transmittance.g = tm.transmittance[1];
			m->transmittance.b = tm.transmittance[2];

			m->emission.r = tm.emission[0];
			m->emission.g = tm.emission[1];
			m->emission.b = tm.emission[2];

			m->shininess = tm.shininess;
			m->ior = tm.ior;
			m->dissolve = tm.dissolve;
			m->illum = tm.illum;
			m->pad0 = tm.pad0;

			m->ambient_tex_id = -1;
			m->diffuse_tex_id = -1;
			m->specular_tex_id = -1;
			m->specular_highlight_tex_id = -1;
			m->bump_tex_id = -1;
			m->displacement_tex_id = -1;
			m->alpha_tex_id = -1;

			m->ambient_texname = "";
			m->diffuse_texname = "";
			m->specular_texname = "";
			m->specular_highlight_texname = "";
			m->bump_texname = "";
			m->displacement_texname = "";
			m->alpha_texname = "";

			if (tm.ambient_texname.size()) {

			}
			if (tm.diffuse_texname.size()) {

			}
			if (tm.specular_texname.size()) {

			}
			if (tm.specular_highlight_texname.size()) {

			}
			if (tm.bump_texname.size()) {

			}
			if (tm.displacement_texname.size()) {
			}
			if (tm.alpha_texname.size()) {

			}

			this->materials.push_back(m);
		}


	}

	/// 2.頂點數據
	{
		// For each shape 遍歷每一個部分
		for (size_t i = 0; i < shapes.size(); i++) {
			// 這部分的名稱
			printf("shape[%ld].name = %s\n", static_cast<long>(i),
				shapes[i].name.c_str());
			// 網格的點數
			printf("Size of shape[%ld].mesh.indices: %lu\n", static_cast<long>(i),
				static_cast<unsigned long>(shapes[i].mesh.indices.size()));
			//printf("Size of shape[%ld].path.indices: %lu\n", static_cast<long>(i),static_cast<unsigned long>(shapes[i].path.indices.size()));

			//assert(shapes[i].mesh.num_face_vertices.size() == shapes[i].mesh.material_ids.size());
			//assert(shapes[i].mesh.num_face_vertices.size() == shapes[i].mesh.smoothing_group_ids.size());

			printf("shape[%ld].num_faces: %lu\n", static_cast<long>(i),
				static_cast<unsigned long>(shapes[i].mesh.num_face_vertices.size()));

			Model* model = new Model(); // 每一部分的模型數據
			// 頂點數量  = face的數量x3 
			model->mesh_num = shapes[i].mesh.num_face_vertices.size() * 3; 
			// 開闢空間
			Vertex *mesh_data = new Vertex[model->mesh_num];
			size_t index_offset = 0;

			// For each face
			for (size_t f = 0; f < shapes[i].mesh.num_face_vertices.size(); f++) {
				size_t fnum = shapes[i].mesh.num_face_vertices[f];

				// 獲得所索引下標
				tinyobj::index_t idx;
				int vertex_index[3];
				int normal_index[3];
				int texcoord_index[3];
				for (size_t v = 0; v < fnum; v++) {
					idx = shapes[i].mesh.indices[index_offset + v];
					vertex_index[v] = idx.vertex_index;
					texcoord_index[v] = idx.texcoord_index;
					normal_index[v] = idx.normal_index;
				}
				for (size_t v = 0; v < fnum; v++) {
					// v
					mesh_data[index_offset + v].pos.x = attrib.vertices[(vertex_index[v]) * 3 + 0];
					mesh_data[index_offset + v].pos.y = attrib.vertices[(vertex_index[v]) * 3 + 1];
					mesh_data[index_offset + v].pos.z = attrib.vertices[(vertex_index[v]) * 3 + 2];
					mesh_data[index_offset + v].pos.w = 1.0f;

					// vt
					mesh_data[index_offset + v].tc.u = attrib.texcoords[texcoord_index[v] * 2 + 0];
					mesh_data[index_offset + v].tc.v = attrib.texcoords[texcoord_index[v] * 2 + 1];

					// vn
					mesh_data[index_offset + v].normal.x = attrib.normals[normal_index[v] * 3 + 0];
					mesh_data[index_offset + v].normal.y = attrib.normals[normal_index[v] * 3 + 1];
					mesh_data[index_offset + v].normal.z = attrib.normals[normal_index[v] * 3 + 2];
					mesh_data[index_offset + v].normal.w = 1.0f;

					// color
					mesh_data[index_offset + v].color.r = 1.0f;
					mesh_data[index_offset + v].color.g = 1.0f;
					mesh_data[index_offset + v].color.b = 1.0f;
					mesh_data[index_offset + v].color.a = 1.0f;
				}
			
				// 偏移
				index_offset += fnum;
			}
			model->mesh = mesh_data;
			models.push_back(model);
		}
	}

	std::cout << "# Loading Complete #"<< std::endl;
	//PrintInfo(attrib, shapes, materials);
	return true;
}

實現結果

  • meshlab 觀看效果.
    在這裏插入圖片描述

  • 導入光柵化渲染器的線框模型
    在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章