Add gltf asset parser & mesh generator

This commit is contained in:
Nikita Lisitsa 2023-01-07 02:26:26 +03:00
parent e19b515404
commit 13590ff9e5
4 changed files with 324 additions and 0 deletions

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

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

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

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