From 22883def8db9ef1f3ffb9b404318e7dd25fdbb51 Mon Sep 17 00:00:00 2001 From: Syoyo Fujita Date: Mon, 25 Jul 2016 22:46:30 +0900 Subject: [PATCH] Support PBR extension for MTL which is proposed in http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr --- loader_example.cc | 366 ++++++++++++++++++++++------------------- models/pbr-mat-ext.mtl | 19 +++ models/pbr-mat-ext.obj | 10 ++ tests/tester.cc | 30 +++- tiny_obj_loader.h | 115 ++++++++++++- 5 files changed, 370 insertions(+), 170 deletions(-) create mode 100644 models/pbr-mat-ext.mtl create mode 100644 models/pbr-mat-ext.obj diff --git a/loader_example.cc b/loader_example.cc index 340b071..076ab3d 100644 --- a/loader_example.cc +++ b/loader_example.cc @@ -4,19 +4,19 @@ #define TINYOBJLOADER_IMPLEMENTATION #include "tiny_obj_loader.h" +#include #include #include -#include +#include #include #include -#include #ifdef _WIN32 #ifdef __cplusplus extern "C" { #endif -#include #include +#include #ifdef __cplusplus } #endif @@ -30,7 +30,7 @@ extern "C" { #endif class timerutil { -public: + public: #ifdef _WIN32 typedef DWORD time_t; @@ -58,7 +58,8 @@ public: static_cast((tv[1].tv_usec - tv[0].tv_usec) / 1000); } time_t usec() { - return this->sec() * 1000000 + static_cast(tv[1].tv_usec - tv[0].tv_usec); + return this->sec() * 1000000 + + static_cast(tv[1].tv_usec - tv[0].tv_usec); } time_t current() { struct timeval t; @@ -66,7 +67,7 @@ public: return static_cast(t.tv_sec * 1000 + t.tv_usec); } -#else // C timer +#else // C timer // using namespace std; typedef clock_t time_t; @@ -81,7 +82,7 @@ public: #endif #endif -private: + private: #ifdef _WIN32 DWORD t_[2]; #else @@ -94,96 +95,103 @@ private: #endif }; -static void PrintInfo(const tinyobj::attrib_t &attrib, const std::vector& shapes, const std::vector& materials) -{ +static void PrintInfo(const tinyobj::attrib_t& attrib, + const std::vector& shapes, + const std::vector& materials) { 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 texcoords : " << (attrib.texcoords.size() / 2) + << std::endl; std::cout << "# of shapes : " << shapes.size() << std::endl; std::cout << "# of materials : " << materials.size() << std::endl; for (size_t v = 0; v < attrib.vertices.size() / 3; v++) { printf(" v[%ld] = (%f, %f, %f)\n", static_cast(v), - static_cast(attrib.vertices[3*v+0]), - static_cast(attrib.vertices[3*v+1]), - static_cast(attrib.vertices[3*v+2])); + static_cast(attrib.vertices[3 * v + 0]), + static_cast(attrib.vertices[3 * v + 1]), + static_cast(attrib.vertices[3 * v + 2])); } for (size_t v = 0; v < attrib.normals.size() / 3; v++) { printf(" n[%ld] = (%f, %f, %f)\n", static_cast(v), - static_cast(attrib.normals[3*v+0]), - static_cast(attrib.normals[3*v+1]), - static_cast(attrib.normals[3*v+2])); + static_cast(attrib.normals[3 * v + 0]), + static_cast(attrib.normals[3 * v + 1]), + static_cast(attrib.normals[3 * v + 2])); } for (size_t v = 0; v < attrib.texcoords.size() / 2; v++) { printf(" uv[%ld] = (%f, %f)\n", static_cast(v), - static_cast(attrib.texcoords[2*v+0]), - static_cast(attrib.texcoords[2*v+1])); + static_cast(attrib.texcoords[2 * v + 0]), + static_cast(attrib.texcoords[2 * v + 1])); } // For each shape for (size_t i = 0; i < shapes.size(); i++) { - printf("shape[%ld].name = %s\n", static_cast(i), shapes[i].name.c_str()); - printf("Size of shape[%ld].indices: %lu\n", static_cast(i), static_cast(shapes[i].mesh.indices.size())); + printf("shape[%ld].name = %s\n", static_cast(i), + shapes[i].name.c_str()); + printf("Size of shape[%ld].indices: %lu\n", static_cast(i), + static_cast(shapes[i].mesh.indices.size())); - size_t index_offset = 0; + size_t index_offset = 0; - 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.material_ids.size()); - printf("shape[%ld].num_faces: %lu\n", static_cast(i), static_cast(shapes[i].mesh.num_face_vertices.size())); + printf("shape[%ld].num_faces: %lu\n", static_cast(i), + static_cast(shapes[i].mesh.num_face_vertices.size())); - // For each face + // 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]; + size_t fnum = shapes[i].mesh.num_face_vertices[f]; - printf(" face[%ld].fnum = %ld\n", static_cast(f), static_cast(fnum)); + printf(" face[%ld].fnum = %ld\n", static_cast(f), + static_cast(fnum)); - // For each vertex in the face - for (size_t v = 0; v < fnum; v++) { - tinyobj::index_t idx = shapes[i].mesh.indices[index_offset + v]; - printf(" face[%ld].v[%ld].idx = %d/%d/%d\n", static_cast(f), static_cast(v), idx.vertex_index, idx.normal_index, idx.texcoord_index); - } + // For each vertex in the face + for (size_t v = 0; v < fnum; v++) { + tinyobj::index_t idx = shapes[i].mesh.indices[index_offset + v]; + printf(" face[%ld].v[%ld].idx = %d/%d/%d\n", static_cast(f), + static_cast(v), idx.vertex_index, idx.normal_index, + idx.texcoord_index); + } - printf(" face[%ld].material_id = %d\n", static_cast(f), shapes[i].mesh.material_ids[f]); + printf(" face[%ld].material_id = %d\n", static_cast(f), + shapes[i].mesh.material_ids[f]); - index_offset += fnum; + index_offset += fnum; } - printf("shape[%ld].num_tags: %lu\n", static_cast(i), static_cast(shapes[i].mesh.tags.size())); + printf("shape[%ld].num_tags: %lu\n", static_cast(i), + static_cast(shapes[i].mesh.tags.size())); for (size_t t = 0; t < shapes[i].mesh.tags.size(); t++) { - printf(" tag[%ld] = %s ", static_cast(t), shapes[i].mesh.tags[t].name.c_str()); + printf(" tag[%ld] = %s ", static_cast(t), + shapes[i].mesh.tags[t].name.c_str()); printf(" ints: ["); - for (size_t j = 0; j < shapes[i].mesh.tags[t].intValues.size(); ++j) - { - printf("%ld", static_cast(shapes[i].mesh.tags[t].intValues[j])); - if (j < (shapes[i].mesh.tags[t].intValues.size()-1)) - { - printf(", "); - } + for (size_t j = 0; j < shapes[i].mesh.tags[t].intValues.size(); ++j) { + printf("%ld", static_cast(shapes[i].mesh.tags[t].intValues[j])); + if (j < (shapes[i].mesh.tags[t].intValues.size() - 1)) { + printf(", "); + } } printf("]"); printf(" floats: ["); - for (size_t j = 0; j < shapes[i].mesh.tags[t].floatValues.size(); ++j) - { - printf("%f", static_cast(shapes[i].mesh.tags[t].floatValues[j])); - if (j < (shapes[i].mesh.tags[t].floatValues.size()-1)) - { - printf(", "); - } + for (size_t j = 0; j < shapes[i].mesh.tags[t].floatValues.size(); ++j) { + printf("%f", static_cast( + shapes[i].mesh.tags[t].floatValues[j])); + if (j < (shapes[i].mesh.tags[t].floatValues.size() - 1)) { + printf(", "); + } } printf("]"); printf(" strings: ["); - for (size_t j = 0; j < shapes[i].mesh.tags[t].stringValues.size(); ++j) - { - printf("%s", shapes[i].mesh.tags[t].stringValues[j].c_str()); - if (j < (shapes[i].mesh.tags[t].stringValues.size()-1)) - { - printf(", "); - } + for (size_t j = 0; j < shapes[i].mesh.tags[t].stringValues.size(); ++j) { + printf("%s", shapes[i].mesh.tags[t].stringValues[j].c_str()); + if (j < (shapes[i].mesh.tags[t].stringValues.size() - 1)) { + printf(", "); + } } printf("]"); printf("\n"); @@ -191,25 +199,59 @@ static void PrintInfo(const tinyobj::attrib_t &attrib, const std::vector(i), materials[i].name.c_str()); - printf(" material.Ka = (%f, %f ,%f)\n", static_cast(materials[i].ambient[0]), static_cast(materials[i].ambient[1]), static_cast(materials[i].ambient[2])); - printf(" material.Kd = (%f, %f ,%f)\n", static_cast(materials[i].diffuse[0]), static_cast(materials[i].diffuse[1]), static_cast(materials[i].diffuse[2])); - printf(" material.Ks = (%f, %f ,%f)\n", static_cast(materials[i].specular[0]), static_cast(materials[i].specular[1]), static_cast(materials[i].specular[2])); - printf(" material.Tr = (%f, %f ,%f)\n", static_cast(materials[i].transmittance[0]), static_cast(materials[i].transmittance[1]), static_cast(materials[i].transmittance[2])); - printf(" material.Ke = (%f, %f ,%f)\n", static_cast(materials[i].emission[0]), static_cast(materials[i].emission[1]), static_cast(materials[i].emission[2])); - printf(" material.Ns = %f\n", static_cast(materials[i].shininess)); + printf("material[%ld].name = %s\n", static_cast(i), + materials[i].name.c_str()); + printf(" material.Ka = (%f, %f ,%f)\n", + static_cast(materials[i].ambient[0]), + static_cast(materials[i].ambient[1]), + static_cast(materials[i].ambient[2])); + printf(" material.Kd = (%f, %f ,%f)\n", + static_cast(materials[i].diffuse[0]), + static_cast(materials[i].diffuse[1]), + static_cast(materials[i].diffuse[2])); + printf(" material.Ks = (%f, %f ,%f)\n", + static_cast(materials[i].specular[0]), + static_cast(materials[i].specular[1]), + static_cast(materials[i].specular[2])); + printf(" material.Tr = (%f, %f ,%f)\n", + static_cast(materials[i].transmittance[0]), + static_cast(materials[i].transmittance[1]), + static_cast(materials[i].transmittance[2])); + printf(" material.Ke = (%f, %f ,%f)\n", + static_cast(materials[i].emission[0]), + static_cast(materials[i].emission[1]), + static_cast(materials[i].emission[2])); + printf(" material.Ns = %f\n", + static_cast(materials[i].shininess)); printf(" material.Ni = %f\n", static_cast(materials[i].ior)); - printf(" material.dissolve = %f\n", static_cast(materials[i].dissolve)); - printf(" material.illum = %d\n", materials[i].illum); + printf(" material.dissolve = %f\n", + static_cast(materials[i].dissolve)); + printf(" material.illum = %d\n", materials[i].illum); printf(" material.map_Ka = %s\n", materials[i].ambient_texname.c_str()); printf(" material.map_Kd = %s\n", materials[i].diffuse_texname.c_str()); printf(" material.map_Ks = %s\n", materials[i].specular_texname.c_str()); - printf(" material.map_Ns = %s\n", materials[i].specular_highlight_texname.c_str()); + printf(" material.map_Ns = %s\n", + materials[i].specular_highlight_texname.c_str()); printf(" material.map_bump = %s\n", materials[i].bump_texname.c_str()); printf(" material.map_d = %s\n", materials[i].alpha_texname.c_str()); printf(" material.disp = %s\n", materials[i].displacement_texname.c_str()); - std::map::const_iterator it(materials[i].unknown_parameter.begin()); - std::map::const_iterator itEnd(materials[i].unknown_parameter.end()); + printf(" <>\n"); + printf(" material.Pr = %f\n", materials[i].roughness); + printf(" material.Pm = %f\n", materials[i].metallic); + printf(" material.Ps = %f\n", materials[i].sheen); + printf(" material.Pc = %f\n", materials[i].clearcoat_thickness); + printf(" material.Pcr = %f\n", materials[i].clearcoat_thickness); + printf(" material.aniso = %f\n", materials[i].anisotropy); + printf(" material.anisor = %f\n", materials[i].anisotropy_rotation); + printf(" material.map_Ke = %s\n", materials[i].emissive_texname.c_str()); + printf(" material.map_Pr = %s\n", materials[i].roughness_texname.c_str()); + printf(" material.map_Pm = %s\n", materials[i].metallic_texname.c_str()); + printf(" material.map_Ps = %s\n", materials[i].sheen_texname.c_str()); + printf(" material.norm = %s\n", materials[i].normal_texname.c_str()); + std::map::const_iterator it( + materials[i].unknown_parameter.begin()); + std::map::const_iterator itEnd( + materials[i].unknown_parameter.end()); for (; it != itEnd; it++) { printf(" material.%s = %s\n", it->first.c_str(), it->second.c_str()); @@ -218,12 +260,8 @@ static void PrintInfo(const tinyobj::attrib_t &attrib, const std::vector* materials, - std::map* matMap, - std::string* err) - { - (void)matId; - (void)err; - LoadMtl(matMap, materials, &m_matSStream); - return true; - } + using namespace tinyobj; + class MaterialStringStreamReader : public MaterialReader { + public: + MaterialStringStreamReader(const std::string& matSStream) + : m_matSStream(matSStream) {} + virtual ~MaterialStringStreamReader() {} + virtual bool operator()(const std::string& matId, + std::vector* materials, + std::map* matMap, + std::string* err) { + (void)matId; + (void)err; + LoadMtl(matMap, materials, &m_matSStream); + return true; + } - private: - std::stringstream m_matSStream; - }; + private: + std::stringstream m_matSStream; + }; MaterialStringStreamReader matSSReader(matStream); tinyobj::attrib_t attrib; std::vector shapes; std::vector materials; std::string err; - bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, &objStream, &matSSReader); - + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, &objStream, + &matSSReader); + if (!err.empty()) { std::cerr << err << std::endl; } @@ -356,15 +389,11 @@ std::string matStream( } PrintInfo(attrib, shapes, materials); - + return true; } -int -main( - int argc, - char **argv) -{ +int main(int argc, char** argv) { if (argc > 1) { const char* basepath = "models/"; if (argc > 2) { @@ -372,10 +401,11 @@ main( } assert(true == TestLoadObj(argv[1], basepath)); } else { - //assert(true == TestLoadObj("cornell_box.obj")); - //assert(true == TestLoadObj("cube.obj")); + // assert(true == TestLoadObj("cornell_box.obj")); + // assert(true == TestLoadObj("cube.obj")); assert(true == TestStreamLoadObj()); - assert(true == TestLoadObj("models/catmark_torus_creases0.obj", "models/", false)); + assert(true == + TestLoadObj("models/catmark_torus_creases0.obj", "models/", false)); } return 0; diff --git a/models/pbr-mat-ext.mtl b/models/pbr-mat-ext.mtl new file mode 100644 index 0000000..bed905d --- /dev/null +++ b/models/pbr-mat-ext.mtl @@ -0,0 +1,19 @@ +# .MTL with PBR extension. +newmtl pbr +Ka 0 0 0 +Kd 1 1 1 +Ks 0 0 0 +Ke 0.1 0.1 0.1 +Pr 0.2 +Pm 0.3 +Ps 0.4 +Pc 0.5 +Pcr 0.6 +aniso 0.7 +anisor 0.8 +map_Pr roughness.tex +map_Pm metallic.tex +map_Ps sheen.tex +map_Ke emissive.tex +norm normalmap.tex + diff --git a/models/pbr-mat-ext.obj b/models/pbr-mat-ext.obj new file mode 100644 index 0000000..bb3e371 --- /dev/null +++ b/models/pbr-mat-ext.obj @@ -0,0 +1,10 @@ +mtllib pbr-mat-ext.mtl + +o floor +usemtl pbr +v 552.8 0.0 0.0 +v 0.0 0.0 0.0 +v 0.0 0.0 559.2 +v 549.6 0.0 559.2 + +f 1 2 3 4 diff --git a/tests/tester.cc b/tests/tester.cc index e8e3b42..dbfb939 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -293,7 +293,7 @@ std::string matStream( return true; } -const char* gMtlBasePath = "../models"; +const char* gMtlBasePath = "../models/"; TEST_CASE("cornell_box", "[Loader]") { @@ -319,6 +319,34 @@ TEST_CASE("catmark_torus_creases0", "[Loader]") { REQUIRE(8 == shapes[0].mesh.tags.size()); } +TEST_CASE("pbr", "[Loader]") { + + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/pbr-mat-ext.obj", gMtlBasePath, /*triangulate*/false); + + if (!err.empty()) { + std::cerr << err << std::endl; + } + REQUIRE(true == ret); + REQUIRE(1 == materials.size()); + REQUIRE(0.2 == Approx(materials[0].roughness)); + REQUIRE(0.3 == Approx(materials[0].metallic)); + REQUIRE(0.4 == Approx(materials[0].sheen)); + REQUIRE(0.5 == Approx(materials[0].clearcoat_thickness)); + REQUIRE(0.6 == Approx(materials[0].clearcoat_roughness)); + REQUIRE(0.7 == Approx(materials[0].anisotropy)); + REQUIRE(0.8 == Approx(materials[0].anisotropy_rotation)); + REQUIRE(0 == materials[0].roughness_texname.compare("roughness.tex")); + REQUIRE(0 == materials[0].metallic_texname.compare("metallic.tex")); + REQUIRE(0 == materials[0].sheen_texname.compare("sheen.tex")); + REQUIRE(0 == materials[0].emissive_texname.compare("emissive.tex")); + REQUIRE(0 == materials[0].normal_texname.compare("normalmap.tex")); +} + TEST_CASE("stream_load", "[Stream]") { REQUIRE(true == TestStreamLoadObj()); } diff --git a/tiny_obj_loader.h b/tiny_obj_loader.h index 3534b91..777ca70 100644 --- a/tiny_obj_loader.h +++ b/tiny_obj_loader.h @@ -91,6 +91,22 @@ typedef struct { std::string bump_texname; // map_bump, bump std::string displacement_texname; // disp std::string alpha_texname; // map_d + + // PBR extension + // http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr + float roughness; // [0, 1] default 0 + float metallic; // [0, 1] default 0 + float sheen; // [0, 1] default 0 + float clearcoat_thickness; // [0, 1] default 0 + float clearcoat_roughness; // [0, 1] default 0 + float anisotropy; // aniso. [0, 1] default 0 + float anisotropy_rotation; // anisor. [0, 1] default 0 + std::string roughness_texname; // map_Pr + std::string metallic_texname; // map_Pm + std::string sheen_texname; // map_Ps + std::string emissive_texname; // map_Ke + std::string normal_texname; // norm. For normal mapping. + std::map unknown_parameter; } material_t; @@ -156,7 +172,6 @@ typedef struct callback_t_ { mtllib_cb(NULL), group_cb(NULL), object_cb(NULL) {} - } callback_t; class MaterialReader { @@ -563,6 +578,20 @@ static void InitMaterial(material_t *material) { material->dissolve = 1.f; material->shininess = 1.f; material->ior = 1.f; + + material->roughness = 0.f; + material->metallic = 0.f; + material->sheen = 0.f; + material->clearcoat_thickness = 0.f; + material->clearcoat_roughness = 0.f; + material->anisotropy_rotation = 0.f; + material->anisotropy = 0.f; + material->roughness_texname = ""; + material->metallic_texname = ""; + material->sheen_texname = ""; + material->emissive_texname = ""; + material->normal_texname = ""; + material->unknown_parameter.clear(); } @@ -779,6 +808,55 @@ void LoadMtl(std::map *material_map, continue; } + // PBR: roughness + if (token[0] == 'P' && token[1] == 'r' && IS_SPACE(token[2])) { + token += 2; + material.roughness = parseFloat(&token); + continue; + } + + // PBR: metallic + if (token[0] == 'P' && token[1] == 'm' && IS_SPACE(token[2])) { + token += 2; + material.metallic = parseFloat(&token); + continue; + } + + // PBR: sheen + if (token[0] == 'P' && token[1] == 's' && IS_SPACE(token[2])) { + token += 2; + material.sheen = parseFloat(&token); + continue; + } + + // PBR: clearcoat thickness + if (token[0] == 'P' && token[1] == 'c' && IS_SPACE(token[2])) { + token += 2; + material.clearcoat_thickness = parseFloat(&token); + continue; + } + + // PBR: clearcoat roughness + if ((0 == strncmp(token, "Pcr", 3)) && IS_SPACE(token[3])) { + token += 4; + material.clearcoat_roughness = parseFloat(&token); + continue; + } + + // PBR: anisotropy + if ((0 == strncmp(token, "aniso", 5)) && IS_SPACE(token[5])) { + token += 6; + material.anisotropy = parseFloat(&token); + continue; + } + + // PBR: anisotropy rotation + if ((0 == strncmp(token, "anisor", 6)) && IS_SPACE(token[6])) { + token += 7; + material.anisotropy_rotation = parseFloat(&token); + continue; + } + // ambient texture if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) { token += 7; @@ -835,6 +913,41 @@ void LoadMtl(std::map *material_map, continue; } + // PBR: roughness texture + if ((0 == strncmp(token, "map_Pr", 6)) && IS_SPACE(token[6])) { + token += 7; + material.roughness_texname = token; + continue; + } + + // PBR: metallic texture + if ((0 == strncmp(token, "map_Pm", 6)) && IS_SPACE(token[6])) { + token += 7; + material.metallic_texname = token; + continue; + } + + // PBR: sheen texture + if ((0 == strncmp(token, "map_Ps", 6)) && IS_SPACE(token[6])) { + token += 7; + material.sheen_texname = token; + continue; + } + + // PBR: emissive texture + if ((0 == strncmp(token, "map_Ke", 6)) && IS_SPACE(token[6])) { + token += 7; + material.emissive_texname = token; + continue; + } + + // PBR: normal map texture + if ((0 == strncmp(token, "norm", 4)) && IS_SPACE(token[4])) { + token += 5; + material.normal_texname = token; + continue; + } + // unknown parameter const char *_space = strchr(token, ' '); if (!_space) {