20/src/Scene.cpp

568 lines
30 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "Scene.h"
// Конструктор пустой сцены
Scene::Scene()
{
}
// Конструктор копирования
Scene::Scene(const Scene &copy): root(copy.root),
nodes(copy.nodes), models(copy.models), cameras(copy.cameras)
{
rebuld_tree(copy);
}
// Оператор присваивания
Scene& Scene::operator=(const Scene& other)
{
root = other.root;
nodes = other.nodes;
models = other.models;
cameras = other.cameras;
rebuld_tree(other);
return *this;
}
// Рендер сцены
void Scene::render(ShaderProgram &shaderProgram, UBO &material_buffer)
{
for (auto & model : models)
model.render(shaderProgram, material_buffer);
}
// Перестройка узлов выбранного списка
template <class T>
void Scene::rebuild_Nodes_list(T& nodes, const Scene& from)
{
for (auto it = nodes.begin(); it != nodes.end(); it++)
{
// Берем родителя, который указывает на оригинальный объект
Node* parent = it->getParent();
// Если родитель - оригинальный корневой узел, то меняем на собственный корневой узел
if (parent == &from.root)
{
it->setParent(&root);
continue;
}
// Если можно привести к модели, то ищем родителя среди моделей
if (dynamic_cast<Model*>(parent))
move_parent(*it, from.models, this->models);
else
// Иначе проверяем на принадлежность к камерам
if (dynamic_cast<Camera*>(parent))
move_parent(*it, from.cameras, this->cameras);
// Иначе это пустой узел
else
move_parent(*it, from.nodes, this->nodes);
// Не нашли родителя - значит он не часть этой сцены
// и изменений по нему не требуется
}
}
// Сдвигает родителя узла между двумя списками при условии его принадлежности к оригинальному
template <class T>
void Scene::move_parent(Node& for_node, const std::list<T>& from_nodes, std::list<T>& this_nodes)
{
// Возьмем адрес родителя
Node* parent = for_node.getParent();
// Цикл по элементам списков для перемещения родителя
// Списки в процессе копирования идеинтичные, вторая проверка не требуется
for (auto it_from = from_nodes.begin(), it_this = this_nodes.begin(); it_from != from_nodes.end(); ++it_from, ++it_this)
// Если адрес объекта, на который указывает итератор, совпадает с родителем - меняем родителя по второму итератору (it_this)
if (&(*it_from) == parent)
for_node.setParent(&(*it_this));
}
// Перестройка дерева после копирования или присваивания
void Scene::rebuld_tree(const Scene& from)
{
// Восстановим родителей в пустых узлах для копии
rebuild_Nodes_list(nodes, from);
rebuild_Nodes_list(models, from);
rebuild_Nodes_list(cameras, from);
}
#define TINYOBJLOADER_IMPLEMENTATION
#include "tiny_obj_loader.h"
#include <functional>
inline void hash_combine(std::size_t& seed) { }
template <typename T, typename... Rest>
inline void hash_combine(std::size_t& seed, const T& v, Rest... rest) {
std::hash<T> hasher;
seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2);
hash_combine(seed, rest...);
}
Scene loadOBJtoScene(const char* filename, const char* mtl_directory, const char* texture_directory)
{
Scene result;
Model model;
// Все модели образованные на основании этой модели будут иметь общего родителя
model.setParent(&result.root);
tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials;
std::string err;
// Значение гамма-коррекции
extern float inv_gamma;
// Если в процессе загрузки возникли ошибки - выдадим исключение
if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename, mtl_directory))
throw std::runtime_error(err);
std::vector<GLuint> indices; // индексы модели
std::vector<glm::vec3> verteces; // вершины
std::vector<glm::vec3> normals; // нормали
std::vector<glm::vec2> texCords; // текстурные координаты
std::vector<glm::vec3> tangent, bitangent; // касательный и бикасательный веткоры
size_t hash; // Для уникальных вершин
std::map <int, int> uniqueVerteces; // словарь для уникальных вершин: ключ - хеш, значение - индекс вершины
int last_material_index = 0; // индекс последнего материала (для группировки моделей)
int count = 0, offset; // для индексов начала и конца в индексном буфере
std::vector<int> materials_range; // хранилище индексов
std::vector<int> materials_ids; // индексы материалов
materials_range.push_back(count); // Закидываем начало отрезка в индексном буфере
// Цикл по считанным моделям
for (const auto& shape : shapes)
{
offset = count; // Переменная для
last_material_index = shape.mesh.material_ids[(count - offset)/3]; // Запоминаем индекс материала
// Цикл по индексам модели
for (const auto& index : shape.mesh.indices)
{
hash = 0;
hash_combine( hash
, attrib.vertices[3 * index.vertex_index + 0], attrib.vertices[3 * index.vertex_index + 1], attrib.vertices[3 * index.vertex_index + 2]
, attrib.normals[3 * index.normal_index + 0], attrib.normals[3 * index.normal_index + 1], attrib.normals[3 * index.normal_index + 2]
, attrib.texcoords[2 * index.texcoord_index + 0], attrib.texcoords[2 * index.texcoord_index + 1]);
if (!uniqueVerteces.count(hash))
{
uniqueVerteces[hash] = verteces.size();
// группируем вершины в массив на основании индексов
verteces.push_back({ attrib.vertices[3 * index.vertex_index + 0]
, attrib.vertices[3 * index.vertex_index + 1]
, attrib.vertices[3 * index.vertex_index + 2]
});
// группируем нормали в массив на основании индексов
normals.push_back({ attrib.normals[3 * index.normal_index + 0]
, attrib.normals[3 * index.normal_index + 1]
, attrib.normals[3 * index.normal_index + 2]
});
// группируем текстурные координаты в массив на основании индексов
texCords.push_back({ attrib.texcoords[2 * index.texcoord_index + 0]
, 1-attrib.texcoords[2 * index.texcoord_index + 1]
});
}
// Сохраняем индекс в массив
indices.push_back(uniqueVerteces[hash]);
// Если индекс последнего материала изменился, то необходимо сохранить его
if (last_material_index != shape.mesh.material_ids[(count - offset)/3])
{
materials_range.push_back(count); // как конец отрезка
materials_ids.push_back(last_material_index); // как используемый материал
last_material_index = shape.mesh.material_ids[(count - offset)/3];
}
count++;
} // for (const auto& index : shape.mesh.indices)
// Если последний материал не загружен - загружаем его
if (materials_range[materials_range.size()-1] != count-1)
{
materials_range.push_back(count); // последний конец отрезка
materials_ids.push_back(last_material_index); // последний используемый материал
}
} // for (const auto& shape : shapes)
// Изменим размер массивов
tangent.resize(verteces.size());
bitangent.resize(verteces.size());
// Расчет касательных и бикасательных векторов
calc_tb(indices.data(), indices.size(), verteces.data(), texCords.data(), tangent.data(), bitangent.data());
// Загрузка в буферы
model.load_verteces (&verteces[0], verteces.size());
model.load_normals (&normals[0], normals.size());
model.load_texCoords(&texCords[0], texCords.size());
model.load_tangent(&tangent[0], tangent.size());
model.load_bitangent(&bitangent[0], bitangent.size());
// Загрузка индексного буфера
model.load_indices (&indices[0], indices.size());
// Создаем копии модели, которые будут рендериться в заданном диапазоне
// И присваиваем текстуры копиям на основании материала
for (int i = 0; i < materials_range.size()-1; i++)
{
result.models.push_back(model); // Создание копии с общим VAO
auto s = --result.models.end();
s->set_index_range(materials_range[i]*sizeof(GLuint), materials_range[i+1]-materials_range[i]);
// Материал
s->material.base_color = pow(glm::vec3(materials[materials_ids[i]].diffuse[0], materials[materials_ids[i]].diffuse[1], materials[materials_ids[i]].diffuse[2]), glm::vec3(1/inv_gamma));
s->material.roughness = 1 - sqrt(materials[materials_ids[i]].shininess/1000); // шероховатость поверхности
s->material.metallic = (materials[materials_ids[i]].ambient[0] + materials[materials_ids[i]].ambient[1] + materials[materials_ids[i]].ambient[2]) / 3.0f;
s->material.specular = (materials[materials_ids[i]].specular[0] + materials[materials_ids[i]].specular[1] + materials[materials_ids[i]].specular[2]) / 3.0f;
s->material.emitted = pow(glm::vec3(materials[materials_ids[i]].emission[0], materials[materials_ids[i]].emission[1], materials[materials_ids[i]].emission[2]), glm::vec3(1/inv_gamma));
// Текстуры
if (!materials[materials_ids[i]].diffuse_texname.empty())
{
Texture diffuse(TEX_ALBEDO, texture_directory + materials[materials_ids[i]].diffuse_texname);
s->set_texture(diffuse);
}
if (!materials[materials_ids[i]].ambient_texname.empty())
{
Texture ambient(TEX_METALLIC, texture_directory + materials[materials_ids[i]].ambient_texname);
s->set_texture(ambient);
}
if (!materials[materials_ids[i]].specular_texname.empty())
{
Texture specular(TEX_SPECULAR, texture_directory + materials[materials_ids[i]].specular_texname);
s->set_texture(specular);
}
if (!materials[materials_ids[i]].normal_texname.empty())
{
Texture normal(TEX_NORMAL, texture_directory + materials[materials_ids[i]].normal_texname);
s->set_texture(normal);
}
if (!materials[materials_ids[i]].bump_texname.empty())
{
Texture heights(TEX_HEIGHTS, texture_directory + materials[materials_ids[i]].bump_texname);
s->set_texture(heights);
}
}
return result;
}
// Изменение флага записи идентификатора для всех моделей
void Scene::set_group_id(GLuint64 id, GLuint etc)
{
for (auto& model : models)
{
model.id.value = id;
if (etc)
model.id.etc = etc;
}
}
#define TINYGLTF_IMPLEMENTATION
#define TINYGLTF_NO_STB_IMAGE_WRITE
#define TINYGLTF_NOEXCEPTION
#define JSON_NOEXCEPTION
#include "tiny_gltf.h"
void collectGLTFnodes(int node_id, std::vector<int> &nodes, tinygltf::Model &in_model)
{
nodes.push_back(node_id);
for (auto& child : in_model.nodes[node_id].children)
collectGLTFnodes(child, nodes, in_model);
}
Scene loadGLTFtoScene(std::string filename)
{
Scene result;
tinygltf::TinyGLTF loader; // Объект загрузчика
tinygltf::Model in_model; // Модель в формате загрузчика
std::string err; // Строка под ошибки
std::string warn; // Строка под предупреждения
bool success = loader.LoadASCIIFromFile(&in_model, &err, &warn, filename); // Загрузка из файла
// Если есть ошибки или предупреждения - выдадим исключение
if (!err.empty() || !warn.empty())
throw std::runtime_error(err + '\n' + warn);
// Если все успешно считано - продолжаем загрузку
if (success)
{
// Загрузим данные в вершинные и индексные буферы
std::vector<BO> BOs;
for (auto &bufferView : in_model.bufferViews)
{
auto &buffer = in_model.buffers[bufferView.buffer];
BOs.push_back(BO((BUFFER_TYPE)bufferView.target, buffer.data.data() + bufferView.byteOffset, bufferView.byteLength));
}
// Адрес директории для относительных путей изображений
std::string dir = filename.substr(0, filename.find_last_of("/\\") + 1);
// Загрузим используемые текстуры
std::vector<Texture> textures;
for (auto &image : in_model.images)
{
// Если длинна файла больше 0, то текстура в отдельном файле
if (image.uri.size() > 0)
{
Texture tmp(TEX_AVAILABLE_COUNT, (dir + image.uri).c_str());
textures.push_back(tmp);
}
else // иначе она является частью буфера
{
GLuint format = GL_RGBA;
GLenum type = GL_UNSIGNED_BYTE;
// Формат пикселя
if (image.component == 1)
format = GL_RED;
else if (image.component == 2)
format = GL_RG;
else if (image.component == 3)
format = GL_RGB;
// Тип данных
if (image.bits == 16)
type = GL_UNSIGNED_SHORT;
else if (image.bits == 32)
type = GL_UNSIGNED_INT;
Texture tmp(image.width, image.height, image.image.data(), 0, format, format, type);
textures.push_back(tmp);
}
}
// Указатели на узлы для построения иерархии родитель-потомок
std::vector<Node *> pNodes(in_model.nodes.size(), NULL);
// Индексы родителей (-1 - корневой узел сцены)
std::vector<int> parents_id(in_model.nodes.size(), -1);
// Цикл по сценам
for (auto &scene : in_model.scenes)
{
// Так как у нас есть информация о потомках корневого узла сцены - пройдем рекурсивно и соберем все узлы из этой сцены:
std::vector<int> scene_nodes;
// Цикл по узлам рассматриваемой сцены с рекурсивным проходом потомков
for (auto &node_id : scene.nodes)
collectGLTFnodes(node_id, scene_nodes, in_model);
// Цикл по всем узлам рассматриваемой сцены
for (auto &node_id : scene_nodes)
{
auto &node = in_model.nodes[node_id];
Node *tmpParent = &result.root; // Указатель на родителя, используется если узел сложный (несколько мешей или камера-меш)
// Запишем текущий узел как родительский для потомков
for (auto& child : node.children)
parents_id[child] = node_id;
// Проверим наличие сложной сетки
bool complex_mesh = false;
// Если у узла есть полигональная сетка
if (node.mesh > -1)
if (in_model.meshes[node.mesh].primitives.size() > 1)
complex_mesh = true;
// Если узел составной: имеет и камеру, и полигональную сетку
// или узел пустой
// или имеет сложную полигональную сетку (примитивов больше одного)
if (node.camera > -1 && node.mesh > -1
|| node.camera == -1 && node.mesh == -1
|| complex_mesh)
{
// Создадим вспомогательный родительский узел для трансформаций
result.nodes.push_back(Node(&result.root));
pNodes[node_id] = tmpParent = &result.nodes.back(); // Сохраним в массив узлов и как родителя
// В противном случае дополнительный узел не требуется
}
// Обработаем полигональную сетку
if (node.mesh > -1)
{
auto &mesh = in_model.meshes[node.mesh];
// Для каждого примитива связанного с полигональной сеткой
for (auto &primitive : mesh.primitives)
{
Model model(tmpParent); // Тут используется либо корневой узел сцены, либо вспомогательный узел
// Цикл по атрибутам примитива
for (auto &attribute : primitive.attributes)
{
// Средство доступа
auto &accessor = in_model.accessors[attribute.second];
// Границы буфера
auto &bufferView = in_model.bufferViews[accessor.bufferView];
// Индекс привязки на шейдере
int attribute_index;
if (attribute.first.compare("POSITION") == 0)
attribute_index = 0;
else if (attribute.first.compare("TEXCOORD_0") == 0)
attribute_index = 1;
else if (attribute.first.compare("NORMAL") == 0)
attribute_index = 2;
else
continue;
// Подключаем вершинный буфер
model.setBO(attribute_index, BOs[accessor.bufferView]);
BOs[accessor.bufferView].use();
glEnableVertexAttribArray(attribute_index);
// Определим спецификацию атрибута
glVertexAttribPointer( attribute_index // индекс атрибута, должен совпадать с Layout шейдера
, tinygltf::GetNumComponentsInType(accessor.type) // количество компонент одного элемента
, accessor.componentType // тип
, accessor.normalized ? GL_TRUE : GL_FALSE // нормализованность значений
, accessor.ByteStride(bufferView) // шаг
, ((char *)NULL + accessor.byteOffset) // отступ с начала массива
);
}
// Если есть индексы
if (primitive.indices > -1)
{
// Средство доступа для индексов
auto &accessor = in_model.accessors[primitive.indices];
// Границы индексного буфера
auto &bufferView = in_model.bufferViews[accessor.bufferView];
model.setIndicesBO(BOs[accessor.bufferView]);
model.set_index_range(accessor.byteOffset, accessor.count, accessor.componentType);
}
// Если есть материал
if (primitive.material > -1)
{
// Параметры материалов
auto &material = in_model.materials[primitive.material];
model.material.base_color = {material.pbrMetallicRoughness.baseColorFactor[0], material.pbrMetallicRoughness.baseColorFactor[1], material.pbrMetallicRoughness.baseColorFactor[2]};
model.material.metallic = material.pbrMetallicRoughness.metallicFactor;
model.material.roughness = material.pbrMetallicRoughness.roughnessFactor;
model.material.emitted = {material.emissiveFactor[0], material.emissiveFactor[1], material.emissiveFactor[2]};
if (material.pbrMetallicRoughness.baseColorTexture.index > -1)
{
textures[material.pbrMetallicRoughness.baseColorTexture.index].setType(TEX_ALBEDO);
model.set_texture(textures[material.pbrMetallicRoughness.baseColorTexture.index]);
}
if (material.pbrMetallicRoughness.metallicRoughnessTexture.index > -1)
{
textures[material.pbrMetallicRoughness.metallicRoughnessTexture.index].setType(TEX_METALLIC);
model.set_texture(textures[material.pbrMetallicRoughness.metallicRoughnessTexture.index]);
model.material.roughness = -2;
}
if (material.emissiveTexture.index > -1)
{
textures[material.emissiveTexture.index].setType(TEX_EMITTED);
model.set_texture(textures[material.emissiveTexture.index]);
}
auto specular_ext = material.extensions.find("KHR_materials_specular");
if (specular_ext != material.extensions.end())
{
if (specular_ext->second.Has("specularColorFactor"))
{
auto &specular_color = specular_ext->second.Get("specularColorFactor");
model.material.specular = (specular_color.Get(0).GetNumberAsDouble() + specular_color.Get(1).GetNumberAsDouble() + specular_color.Get(2).GetNumberAsDouble()) / 3;
}
if (specular_ext->second.Has("specularColorTexture"))
{
auto &specular_texture = specular_ext->second.Get("specularColorTexture");
int index = specular_texture.Get("index").GetNumberAsInt();
if (index > -1)
{
textures[index].setType(TEX_SPECULAR);
model.set_texture(textures[index]);
}
}
}
}
result.models.push_back(model); // Добавляем к сцене
// Если ещё не сохранили
if (!pNodes[node_id])
pNodes[node_id] = &result.models.back(); // Сохраним адрес созданного узла
}
}
// Обработаем камеру
if (in_model.nodes[node_id].camera > -1)
{
auto &in_camera = in_model.cameras[in_model.nodes[node_id].camera];
// Если камера использует проекцию перспективы
if (in_camera.type == "perspective")
{
Camera camera(in_camera.perspective.aspectRatio, glm::vec3(0.0f), CAMERA_DEFAULT_ROTATION, in_camera.perspective.yfov, in_camera.perspective.znear, in_camera.perspective.zfar);
result.cameras.push_back(camera);
}
// Иначе ортографическую проекцию
else
{
Camera camera(in_camera.orthographic.xmag, in_camera.orthographic.ymag, glm::vec3(0.0f), CAMERA_DEFAULT_ROTATION, in_camera.orthographic.znear, in_camera.orthographic.zfar);
result.cameras.push_back(camera);
}
// Если у узла есть полигональная сетка - сделаем камеру потомком модели, адрес которой записан в вектор
if (in_model.nodes[node_id].mesh > -1)
result.cameras.back().setParent(pNodes[node_id]);
// Иначе узел является камерой сам по себе
else
{
result.cameras.back().setParent(&result.root);
pNodes[node_id] = &result.cameras.back(); // Сохраним адрес созданного узла
}
}
}
}
// Зададим трансформацию и родителей для узлов
// Цикл по всем индексам узлов
for (int node_id = 0; node_id < in_model.nodes.size(); node_id++)
{
// Проверка на нулевой указатель
if (pNodes[node_id])
{
// Если есть матрица трансформации - разберем её на составляющие
if (in_model.nodes[node_id].matrix.size() == 16)
{
glm::mat4 transform = glm::make_mat4(in_model.nodes[node_id].matrix.data());
pNodes[node_id]->e_position() = glm::vec3(transform[3][0], transform[3][1], transform[3][2]);
pNodes[node_id]->e_scale() = {glm::length(glm::vec3(transform[0][0], transform[1][0], transform[2][0])), glm::length(glm::vec3(transform[0][1], transform[1][1], transform[2][1])), glm::length(glm::vec3(transform[0][2], transform[1][2], transform[2][2]))};
for (int i = 0; i < 3; i++)
transform[i] = glm::normalize(transform[i]);
pNodes[node_id]->e_rotation() = glm::quat_cast(transform);
}
else
{
// Если есть параметры трансформации
if (in_model.nodes[node_id].translation.size() == 3)
pNodes[node_id]->e_position() = glm::vec3(in_model.nodes[node_id].translation[0], in_model.nodes[node_id].translation[1], in_model.nodes[node_id].translation[2]);
if (in_model.nodes[node_id].rotation.size() == 4)
pNodes[node_id]->e_rotation() = glm::quat(in_model.nodes[node_id].rotation[3], glm::vec3(in_model.nodes[node_id].rotation[0], in_model.nodes[node_id].rotation[1], in_model.nodes[node_id].rotation[2]));
if (in_model.nodes[node_id].scale.size() == 3)
pNodes[node_id]->e_scale() = glm::vec3(in_model.nodes[node_id].scale[0], in_model.nodes[node_id].scale[1], in_model.nodes[node_id].scale[2]);
}
// Если индекс родителя > -1, то родитель создан и это не корневой узел сцены
if (parents_id[node_id] > -1)
pNodes[node_id]->setParent(pNodes[parents_id[node_id]]);
}
}
}
return result;
}