Add gltf asset parser & mesh generator
This commit is contained in:
parent
e19b515404
commit
13590ff9e5
4 changed files with 324 additions and 0 deletions
40
libs/gfx/include/psemek/gfx/gltf_mesh.hpp
Normal file
40
libs/gfx/include/psemek/gfx/gltf_mesh.hpp
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/gfx/buffer.hpp>
|
||||
#include <psemek/gfx/array.hpp>
|
||||
#include <psemek/gfx/gltf_parser.hpp>
|
||||
#include <psemek/util/span.hpp>
|
||||
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace psemek::gfx
|
||||
{
|
||||
|
||||
struct gltf_mesh
|
||||
{
|
||||
struct mesh
|
||||
{
|
||||
struct primitive
|
||||
{
|
||||
gfx::array vao;
|
||||
std::optional<std::size_t> material;
|
||||
std::size_t index_count;
|
||||
std::size_t index_offset;
|
||||
GLenum index_type;
|
||||
|
||||
void draw();
|
||||
};
|
||||
|
||||
std::vector<primitive> primitives;
|
||||
};
|
||||
|
||||
std::vector<gfx::buffer> buffers;
|
||||
|
||||
std::unordered_map<std::string, mesh> meshes;
|
||||
|
||||
gltf_mesh() = default;
|
||||
gltf_mesh(gltf_asset const & asset, std::vector<util::span<char const>> const & buffers);
|
||||
};
|
||||
|
||||
}
|
||||
72
libs/gfx/include/psemek/gfx/gltf_parser.hpp
Normal file
72
libs/gfx/include/psemek/gfx/gltf_parser.hpp
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#pragma once
|
||||
|
||||
#include <psemek/io/stream.hpp>
|
||||
#include <psemek/gfx/color.hpp>
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <optional>
|
||||
|
||||
namespace psemek::gfx
|
||||
{
|
||||
|
||||
struct gltf_asset
|
||||
{
|
||||
struct node
|
||||
{
|
||||
std::string name;
|
||||
std::size_t mesh;
|
||||
};
|
||||
|
||||
struct mesh
|
||||
{
|
||||
struct primitive
|
||||
{
|
||||
std::optional<std::size_t> position;
|
||||
std::optional<std::size_t> normal;
|
||||
std::optional<std::size_t> texcoord;
|
||||
std::size_t indices;
|
||||
std::optional<std::size_t> material;
|
||||
};
|
||||
|
||||
std::vector<primitive> primitives;
|
||||
};
|
||||
|
||||
struct material
|
||||
{
|
||||
color_4f albedo;
|
||||
};
|
||||
|
||||
struct accessor
|
||||
{
|
||||
std::size_t buffer_view;
|
||||
std::size_t component_type;
|
||||
std::size_t count;
|
||||
std::size_t type;
|
||||
bool normalized;
|
||||
};
|
||||
|
||||
struct buffer_view
|
||||
{
|
||||
std::size_t buffer;
|
||||
std::size_t offset;
|
||||
std::size_t length;
|
||||
};
|
||||
|
||||
struct buffer
|
||||
{
|
||||
std::size_t length;
|
||||
std::string uri;
|
||||
};
|
||||
|
||||
std::vector<node> nodes;
|
||||
std::vector<mesh> meshes;
|
||||
std::vector<material> materials;
|
||||
std::vector<accessor> accessors;
|
||||
std::vector<buffer_view> buffer_views;
|
||||
std::vector<buffer> buffers;
|
||||
};
|
||||
|
||||
gltf_asset parse_gltf(io::istream && stream);
|
||||
|
||||
}
|
||||
67
libs/gfx/source/gltf_mesh.cpp
Normal file
67
libs/gfx/source/gltf_mesh.cpp
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
#include <psemek/gfx/gltf_mesh.hpp>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace psemek::gfx
|
||||
{
|
||||
|
||||
void gltf_mesh::mesh::primitive::draw()
|
||||
{
|
||||
vao.bind();
|
||||
gl::DrawElements(gl::TRIANGLES, index_count, index_type, reinterpret_cast<void const *>(index_offset));
|
||||
}
|
||||
|
||||
gltf_mesh::gltf_mesh(gltf_asset const & asset, std::vector<util::span<char const>> const & buffers)
|
||||
{
|
||||
if (buffers.size() != asset.buffers.size())
|
||||
throw std::runtime_error("wrong number of glTF buffers");
|
||||
|
||||
for (auto buffer : buffers)
|
||||
this->buffers.emplace_back().load(buffer.data(), buffer.size(), gl::STATIC_DRAW);
|
||||
|
||||
for (auto const & node : asset.nodes)
|
||||
{
|
||||
auto & target_mesh = meshes[node.name];
|
||||
|
||||
auto const & mesh = asset.meshes[node.mesh];
|
||||
|
||||
for (auto const & primitive : mesh.primitives)
|
||||
{
|
||||
auto & target_primitive = target_mesh.primitives.emplace_back();
|
||||
target_primitive.material = primitive.material;
|
||||
|
||||
target_primitive.vao.bind();
|
||||
|
||||
{
|
||||
auto const & indices_accessor = asset.accessors[primitive.indices];
|
||||
auto const & indices_view = asset.buffer_views[indices_accessor.buffer_view];
|
||||
|
||||
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, this->buffers[indices_view.buffer].id());
|
||||
target_primitive.index_offset = indices_view.offset;
|
||||
target_primitive.index_type = indices_accessor.component_type;
|
||||
target_primitive.index_count = indices_accessor.count;
|
||||
}
|
||||
|
||||
std::pair<GLuint, std::optional<std::size_t>> attributes[3] =
|
||||
{
|
||||
{0, primitive.position},
|
||||
{1, primitive.normal},
|
||||
{2, primitive.texcoord},
|
||||
};
|
||||
|
||||
for (auto const & attribute : attributes)
|
||||
{
|
||||
if (!attribute.second) continue;
|
||||
|
||||
auto const & accessor = asset.accessors[*attribute.second];
|
||||
auto const & view = asset.buffer_views[accessor.buffer_view];
|
||||
|
||||
gl::BindBuffer(gl::ARRAY_BUFFER, this->buffers[view.buffer].id());
|
||||
gl::EnableVertexAttribArray(attribute.first);
|
||||
gl::VertexAttribPointer(attribute.first, accessor.type, accessor.component_type, accessor.normalized, 0, reinterpret_cast<void const *>(view.offset));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
145
libs/gfx/source/gltf_parser.cpp
Normal file
145
libs/gfx/source/gltf_parser.cpp
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
#include <psemek/gfx/gltf_parser.hpp>
|
||||
#include <psemek/util/to_string.hpp>
|
||||
|
||||
#include <boost/preprocessor/stringize.hpp>
|
||||
|
||||
#define RAPIDJSON_ASSERT(x) if (!(x)) throw ::std::runtime_error("Error parsing glTF: " BOOST_PP_STRINGIZE(x));
|
||||
#define RAPIDJSON_NOEXCEPT_ASSERT(x)
|
||||
|
||||
#include <rapidjson/document.h>
|
||||
#include <rapidjson/istreamwrapper.h>
|
||||
|
||||
namespace psemek::gfx
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::string_view to_string(rapidjson::ParseErrorCode const & code)
|
||||
{
|
||||
using namespace rapidjson;
|
||||
|
||||
switch (code)
|
||||
{
|
||||
case kParseErrorNone: return "no error";
|
||||
case kParseErrorDocumentEmpty: return "document is empty";
|
||||
case kParseErrorDocumentRootNotSingular: return "document root must not be followed by other values";
|
||||
case kParseErrorValueInvalid: return "invalid value";
|
||||
case kParseErrorObjectMissName: return "missing a name for object member";
|
||||
case kParseErrorObjectMissColon: return "missing a colon after a name of object member";
|
||||
case kParseErrorObjectMissCommaOrCurlyBracket: return "missing a comma or '}' after an object member";
|
||||
case kParseErrorArrayMissCommaOrSquareBracket: return "missing a comma or ']' after an array element";
|
||||
case kParseErrorStringUnicodeEscapeInvalidHex: return R"(Incorrect hex digit after \\u escape in string)";
|
||||
case kParseErrorStringUnicodeSurrogateInvalid: return "surrogate pair in string is invalid";
|
||||
case kParseErrorStringEscapeInvalid: return "invalid escape character in string";
|
||||
case kParseErrorStringMissQuotationMark: return "missing a closing quotation mark in string";
|
||||
case kParseErrorStringInvalidEncoding: return "invalid encoding in string";
|
||||
case kParseErrorNumberTooBig: return "number too big to be stored in double";
|
||||
case kParseErrorNumberMissFraction: return "missing fraction part in number";
|
||||
case kParseErrorNumberMissExponent: return "missing exponent in number";
|
||||
case kParseErrorTermination: return "parsing was terminated";
|
||||
case kParseErrorUnspecificSyntaxError: return "unspecific syntax error";
|
||||
default: return "(unknown error)";
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t parse_accessor_type(std::string const & type)
|
||||
{
|
||||
if (type == "SCALAR")
|
||||
return 1;
|
||||
if (type == "VEC2")
|
||||
return 2;
|
||||
if (type == "VEC3")
|
||||
return 3;
|
||||
if (type == "VEC4")
|
||||
return 4;
|
||||
throw std::runtime_error(util::to_string("Unknown accessor component type: ", type));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
gltf_asset parse_gltf(io::istream && stream)
|
||||
{
|
||||
auto description_str = io::read_full(std::move(stream));
|
||||
description_str.push_back(0);
|
||||
|
||||
rapidjson::Document document;
|
||||
document.ParseInsitu(description_str.data());
|
||||
|
||||
if (document.HasParseError())
|
||||
throw std::runtime_error(util::to_string("Error parsing glTF: ", to_string(document.GetParseError()), " at ", document.GetErrorOffset()));
|
||||
|
||||
gltf_asset result;
|
||||
|
||||
for (auto const & node : document["nodes"].GetArray())
|
||||
{
|
||||
auto & target = result.nodes.emplace_back();
|
||||
target.mesh = node["mesh"].GetUint64();
|
||||
target.name = node["name"].GetString();
|
||||
}
|
||||
|
||||
for (auto const & mesh : document["meshes"].GetArray())
|
||||
{
|
||||
auto & target = result.meshes.emplace_back();
|
||||
for (auto const & primitive : mesh["primitives"].GetArray())
|
||||
{
|
||||
auto & primitive_target = target.primitives.emplace_back();
|
||||
primitive_target.indices = primitive["indices"].GetUint64();
|
||||
if (primitive.HasMember("material"))
|
||||
primitive_target.material = primitive["material"].GetUint64();
|
||||
|
||||
auto const & attributes = primitive["attributes"];
|
||||
if (attributes.HasMember("POSITION"))
|
||||
primitive_target.position = attributes["POSITION"].GetUint64();
|
||||
if (attributes.HasMember("NORMAL"))
|
||||
primitive_target.normal = attributes["NORMAL"].GetUint64();
|
||||
if (attributes.HasMember("TEXCOORD_0"))
|
||||
primitive_target.texcoord = attributes["TEXCOORD_0"].GetUint64();
|
||||
}
|
||||
}
|
||||
|
||||
for (auto const & material : document["materials"].GetArray())
|
||||
{
|
||||
auto & target = result.materials.emplace_back();
|
||||
|
||||
auto const & color = material["pbrMetallicRoughness"]["baseColorFactor"].GetArray();
|
||||
for (std::size_t i = 0; i < 4; ++i)
|
||||
target.albedo[i] = color[i].GetFloat();
|
||||
}
|
||||
|
||||
for (auto const & accessor : document["accessors"].GetArray())
|
||||
{
|
||||
auto & target = result.accessors.emplace_back();
|
||||
|
||||
target.buffer_view = accessor["bufferView"].GetUint64();
|
||||
target.component_type = accessor["componentType"].GetUint64();
|
||||
target.count = accessor["count"].GetUint64();
|
||||
target.type = parse_accessor_type(accessor["type"].GetString());
|
||||
|
||||
if (accessor.HasMember("normalized"))
|
||||
target.normalized = accessor["normalized"].GetBool();
|
||||
else
|
||||
target.normalized = false;
|
||||
}
|
||||
|
||||
for (auto const & buffer_view : document["bufferViews"].GetArray())
|
||||
{
|
||||
auto & target = result.buffer_views.emplace_back();
|
||||
|
||||
target.buffer = buffer_view["buffer"].GetUint64();
|
||||
target.offset = buffer_view["byteOffset"].GetUint64();
|
||||
target.length = buffer_view["byteLength"].GetUint64();
|
||||
}
|
||||
|
||||
for (auto const & buffer : document["buffers"].GetArray())
|
||||
{
|
||||
auto & target = result.buffers.emplace_back();
|
||||
|
||||
target.length = buffer["byteLength"].GetUint64();
|
||||
target.uri = buffer["uri"].GetString();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue