#include "Model.h" #include #include "Camera.h" extern Camera camera; // Словарь для хранения количества моделей, использующих общие VAO std::map vaos_count; // Конструктор без параметров Movable::Movable() : position(0), rotation(0), scale(1) {} // Конструктор копирования Movable::Movable(const Movable& copy) : position(copy.position), rotation(copy.rotation), scale(copy.scale) {} // Конструктор без параметров Model::Model() : verteces_count(0), first_index(0), indices_count(0), vertex_vbo(0), index_vbo(0), normals_vbo(0), texCoords_vbo(0), tangent_vbo(0), bitangent_vbo(0), normalmapped(false), parallaxmapped(false), displacementmapped(false) { // Создаем новый буфер vao = new VAO(); // Запоминаем, что для нового VAO количество моделей, использующих его, = 1 vaos_count[vao->get_handler()] = 1; // Приведение указателя к целому 8байт id.value = (GLuint64) this; id.etc = 0; } // Конструктор копирования Model::Model(const Model& copy) : vao(copy.vao), verteces_count(copy.verteces_count), first_index(copy.first_index), indices_count(copy.indices_count), vertex_vbo(copy.vertex_vbo), index_vbo(copy.index_vbo), normals_vbo(copy.normals_vbo), texCoords_vbo(copy.texCoords_vbo), tangent_vbo(copy.tangent_vbo), bitangent_vbo(copy.bitangent_vbo), texture_albedo(copy.texture_albedo), texture_roughness(copy.texture_roughness), texture_metallic(copy.texture_metallic), texture_specular(copy.texture_specular), texture_emitted(copy.texture_emitted), texture_heights(copy.texture_heights), texture_normals(copy.texture_normals), material(copy.material), normalmapped(copy.normalmapped), parallaxmapped(copy.parallaxmapped), displacementmapped(copy.displacementmapped) { // Если создается копия модели, то запоминаем поля и инкрементируем словарь с количеством vaos_count[vao->get_handler()]++; // Приведение указателя к целому 8байт id.value = (GLuint64) this; id.etc = copy.id.etc; } Model::~Model() { // При уничтожении модели проверяем: если это последний пользователь меша, то осовобождаем из под него память if (--vaos_count[vao->get_handler()] < 1) { if (vertex_vbo) delete vertex_vbo; if (index_vbo) delete index_vbo; if (texCoords_vbo) delete texCoords_vbo; if (normals_vbo) delete normals_vbo; if (tangent_vbo) delete tangent_vbo; if (bitangent_vbo) delete bitangent_vbo; delete vao; } } // Вызов отрисовки без uniform-даных void Model::render() { // Подключаем VAO vao->use(); // Если есть индексы - рисуем с их использованием if (indices_count) glDrawElements(GL_TRIANGLES, indices_count, GL_UNSIGNED_INT, (void*)(first_index*sizeof(GLuint))); // Если есть вершины - рисуем на основании массива вершин else if (verteces_count) glDrawArrays(GL_TRIANGLES, 0, verteces_count); } // Вызов отрисовки void Model::render(ShaderProgram &shaderProgram, UBO &material_buffer, const glm::mat4& global_tranform) { // Загрузка идентификатора объекта glUniform3uiv(shaderProgram.getUniformLoc("ID"), 1, (GLuint*) &id); // Расчитаем матрицу трансформации glm::mat4 model = global_tranform * this->getTransformMatrix(); GLuint model_uniform = shaderProgram.getUniformLoc("model"); glUniformMatrix4fv(model_uniform, 1, GL_FALSE, &model[0][0]); // Подключаем текстуры texture_albedo.use(); texture_roughness.use(); texture_metallic.use(); texture_specular.use(); texture_emitted.use(); texture_heights.use(); texture_normals.use(); // Загрузим параметры рельефного текстурирования glUniform1i(shaderProgram.getUniformLoc("normalmapped"), normalmapped); glUniform1i(shaderProgram.getUniformLoc("parallaxmapped"), parallaxmapped); glUniform1i(shaderProgram.getUniformLoc("displacementmapped"), displacementmapped); // Загружаем данные о материале material_buffer.load(&material, sizeof(material)); render(); } // Функция для конфигурации атрибута вершинного буфера void vertex_attrib_config() { // Включаем необходимый атрибут у выбранного VAO glEnableVertexAttribArray(0); // Устанавливаем связь между VAO и привязанным BO glVertexAttribPointer( 0 // индекс атрибута, должен совпадать с Layout шейдера , 3 // количество компонент одного элемента , GL_FLOAT // тип , GL_FALSE // нормализованность значений , 0 // шаг , (void *)0 // отступ с начала массива ); } // Загрузка вершин в буфер void Model::load_verteces(glm::vec3* verteces, GLuint count) { // Подключаем VAO vao->use(); // Если до этого такого буфера не было - создаем его, иначе используем if (!vertex_vbo) vertex_vbo = new BO(VERTEX, vertex_attrib_config); else vertex_vbo->use(); // Загрузка вершин в память буфера vertex_vbo->load(verteces, sizeof(glm::vec3)*count); // Запоминаем количество вершин для отрисовки verteces_count = count; } // Загрузка индексов в буфер void Model::load_indices(GLuint* indices, GLuint count) { // Подключаем VAO vao->use(); // Если до этого такого буфера не было - создаем его, иначе используем if (!index_vbo) index_vbo = new BO(ELEMENT); else index_vbo->use(); // Загрузка вершин в память буфера index_vbo->load(indices, sizeof(GLuint)*count); // Запоминаем количество вершин для отрисовки indices_count = count; } // Функция для конфигурации атрибута вершинного буфера void texCoords_attrib_config() { // Включаем необходимый атрибут у выбранного VAO glEnableVertexAttribArray(1); // Устанавливаем связь между VAO и привязанным BO glVertexAttribPointer( 1 // индекс атрибута, должен совпадать с Layout шейдера , 2 // количество компонент одного элемента , GL_FLOAT // тип , GL_FALSE // нормализованность значений , 0 // шаг , (void *)0 // отступ с начала массива ); } // Загрузка текстурных координат в буфер void Model::load_texCoords(glm::vec2* texCoords, GLuint count) { // Подключаем VAO vao->use(); // Если до этого такого буфера не было - создаем его, иначе используем if (!texCoords_vbo) texCoords_vbo = new BO(VERTEX, texCoords_attrib_config); else texCoords_vbo->use(); // Загрузка вершин в память буфера texCoords_vbo->load(texCoords, sizeof(glm::vec2)*count); } // Функция для конфигурации атрибута вершинного буфера void normals_attrib_config() { // Включаем необходимый атрибут у выбранного VAO glEnableVertexAttribArray(2); // Устанавливаем связь между VAO и привязанным BO glVertexAttribPointer( 2 // индекс атрибута, должен совпадать с Layout шейдера , 3 // количество компонент одного элемента , GL_FLOAT // тип , GL_FALSE // нормализованность значений , 0 // шаг , (void *)0 // отступ с начала массива ); } // Загрузка нормалей в буфер void Model::load_normals(glm::vec3* normals, GLuint count) { // Подключаем VAO vao->use(); // Если до этого такого буфера не было - создаем его, иначе используем if (!normals_vbo) normals_vbo = new BO(VERTEX, normals_attrib_config); else normals_vbo->use(); // Загрузка вершин в память буфера normals_vbo->load(normals, sizeof(glm::vec3)*count); } #include // Матрица трансформации модели glm::mat4 Movable::getTransformMatrix() { glm::mat4 transformMatrix = glm::mat4(1.0f); // Перемещение модели transformMatrix = glm::translate(transformMatrix, position); // Поворот модели transformMatrix = glm::rotate(transformMatrix, glm::radians(rotation.x), glm::vec3(1.0, 0.0, 0.0)); transformMatrix = glm::rotate(transformMatrix, glm::radians(rotation.y), glm::vec3(0.0, 1.0, 0.0)); transformMatrix = glm::rotate(transformMatrix, glm::radians(rotation.z), glm::vec3(0.0, 0.0, 1.0)); // Масштабирование transformMatrix = glm::scale(transformMatrix, scale); return transformMatrix; } // Привязка текстуры к модели void Model::set_texture(Texture& texture) { GLuint type = texture.getType(); switch(type) { case TEX_ALBEDO: texture_albedo = texture; material.base_color.r = -1; break; case TEX_ROUGHNESS: texture_roughness = texture; material.roughness = -1; break; case TEX_METALLIC: texture_metallic = texture; material.metallic = -1; break; case TEX_SPECULAR: texture_specular = texture; material.specular = -1; break; case TEX_EMITTED: texture_emitted = texture; material.emitted.r = -1; break; case TEX_HEIGHTS: texture_heights = texture; break; case TEX_NORMAL: texture_normals = texture; break; }; } // Ограничение диапазона из буфера индексов void Model::set_index_range(GLuint beg, GLuint count) { first_index = beg; indices_count = count; } // Функция для конфигурации атрибута вершинного буфера void tangent_attrib_config() { // Включаем необходимый атрибут у выбранного VAO glEnableVertexAttribArray(3); // Устанавливаем связь между VAO и привязанным BO glVertexAttribPointer( 3 // индекс атрибута, должен совпадать с Layout шейдера , 3 // количество компонент одного элемента , GL_FLOAT // тип , GL_FALSE // нормализованность значений , 0 // шаг , (void *)0 // отступ с начала массива ); } // Функция для конфигурации атрибута вершинного буфера void bitangent_attrib_config() { // Включаем необходимый атрибут у выбранного VAO glEnableVertexAttribArray(4); // Устанавливаем связь между VAO и привязанным BO glVertexAttribPointer( 4 // индекс атрибута, должен совпадать с Layout шейдера , 3 // количество компонент одного элемента , GL_FLOAT // тип , GL_FALSE // нормализованность значений , 0 // шаг , (void *)0 // отступ с начала массива ); } // Загрузка касательных векторов в буфер void Model::load_tangent(glm::vec3* tangent, GLuint count) { // Подключаем VAO vao->use(); // Если до этого такого буфера не было - создаем его, иначе используем if (!tangent_vbo) tangent_vbo = new BO(VERTEX, tangent_attrib_config); else tangent_vbo->use(); // Загрузка вершин в память буфера tangent_vbo->load(tangent, sizeof(glm::vec3)*count); } // Загрузка бикасательных векторов в буфер void Model::load_bitangent(glm::vec3* bitangent, GLuint count) { // Подключаем VAO vao->use(); // Если до этого такого буфера не было - создаем его, иначе используем if (!bitangent_vbo) bitangent_vbo = new BO(VERTEX, bitangent_attrib_config); else bitangent_vbo->use(); // Загрузка вершин в память буфера bitangent_vbo->load(bitangent, sizeof(glm::vec3)*count); } #define TINYOBJLOADER_IMPLEMENTATION #include "tiny_obj_loader.h" #include inline void hash_combine(std::size_t& seed) { } template inline void hash_combine(std::size_t& seed, const T& v, Rest... rest) { std::hash hasher; seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2); hash_combine(seed, rest...); } // Расчет касательных и бикасательных векторов void calc_tb(const GLuint* indices, const int indices_count, const glm::vec3* verteces, const glm::vec2* texCords, glm::vec3* tangent, glm::vec3* bitangent) { glm::vec2 dTex1, dTex2; // Разница по текстурным координатам glm::vec3 dPos1, dPos2; // Разница по координатам вершин float f; // Разность произведений glm::vec3 tmp; // Для вычислений вектора for (int i = 0; i < indices_count; i+=3) { // Разности векторов dTex1 = texCords[indices[i+1]] - texCords[indices[i]]; dTex2 = texCords[indices[i+2]] - texCords[indices[i]]; dPos1 = verteces[indices[i+1]] - verteces[indices[i]]; dPos2 = verteces[indices[i+2]] - verteces[indices[i]]; f = dTex1.x * dTex2.y - dTex2.x * dTex1.y; // Покомпонентное вычисление касательного вектора tmp.x = (dTex2.y * dPos1.x - dTex1.y * dPos2.x) / f; tmp.y = (dTex2.y * dPos1.y - dTex1.y * dPos2.y) / f; tmp.z = (dTex2.y * dPos1.z - dTex1.y * dPos2.z) / f; // Добавим вектор в контейнер tangent[indices[i ]] += tmp; // Для каждого индекса полигона tangent[indices[i+1]] += tmp; // значение вектора tangent[indices[i+2]] += tmp; // одинаковое // Покомпонентное вычисление бикасательного вектора tmp.x = (-dTex2.x * dPos1.x + dTex1.x * dPos2.x) / f; tmp.y = (-dTex2.x * dPos1.y + dTex1.x * dPos2.y) / f; tmp.z = (-dTex2.x * dPos1.z + dTex1.x * dPos2.z) / f; // Добавим вектор в контейнер bitangent[indices[i ]] += tmp; // Для каждого индекса полигона bitangent[indices[i+1]] += tmp; // значение вектора bitangent[indices[i+2]] += tmp; // одинаковое } } GrouptedModel loadOBJtoGroupted(const char* filename, const char* mtl_directory, const char* texture_directory) { GrouptedModel result; Model model; tinyobj::attrib_t attrib; std::vector shapes; std::vector 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 indices; // индексы модели std::vector verteces; // вершины std::vector normals; // нормали std::vector texCords; // текстурные координаты std::vector tangent, bitangent; // касательный и бикасательный веткоры size_t hash; // Для уникальных вершин std::map uniqueVerteces; // словарь для уникальных вершин: ключ - хеш, значение - индекс вершины int last_material_index = 0; // индекс последнего материала (для группировки моделей) int count = 0, offset; // для индексов начала и конца в индексном буфере std::vector materials_range; // хранилище индексов std::vector 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.parts.push_back(model); // Создание копии с общим VAO auto s = --result.parts.end(); s->set_index_range(materials_range[i], 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 GrouptedModel::render(ShaderProgram &shaderProgram, UBO &material_buffer) { glm::mat4 transform = this->getTransformMatrix(); for (auto& model : parts) model.render(shaderProgram, material_buffer, transform); } // Генерирует сферу заданного радиуса с определенным количеством сегментов void genShpere(Model& model, float radius, int sectorsCount) { std::vector vertices; std::vector normals; std::vector indices; float x, y, z, xy; // Позиция вершины float nx, ny, nz, lengthInv = 1.0f / radius; // Нормаль вершины float PI = 3.14159265; float sectorStep = PI / sectorsCount; // Шаг сектора float longAngle, latAngle; // Углы for(int i = 0; i <= sectorsCount; ++i) { latAngle = PI / 2 - i * sectorStep; // Начиная с pi/2 до -pi/2 xy = radius * cos(latAngle); // r * cos(lat) z = radius * sin(latAngle); // r * sin(lat) // добавляем (sectorCount+1) вершин на сегмент // Последняя и первая вершины имеют одинаковые нормали и координаты for(int j = 0; j <= sectorsCount; ++j) { longAngle = j * 2 * sectorStep; // Начиная с 0 до 2*pi // Положение вершины (x, y, z) x = xy * cos(longAngle); // r * cos(lat) * cos(long) y = xy * sin(longAngle); // r * cos(lat) * sin(long) vertices.push_back({x, y, z}); // Нормали (nx, ny, nz) nx = x * lengthInv; ny = y * lengthInv; nz = z * lengthInv; normals.push_back({nx, ny, nz}); } } int k1, k2; for(int i = 0; i < sectorsCount; ++i) { k1 = i * (sectorsCount + 1); // начало текущего сегмента k2 = k1 + sectorsCount + 1; // начало следующего сегмента for(int j = 0; j < sectorsCount; ++j, ++k1, ++k2) { // 2 треугольника на один сегмент // k1, k2, k1+1 if(i != 0) { indices.push_back(k1); indices.push_back(k2); indices.push_back(k1 + 1); } // k1+1, k2, k2+1 if(i != (sectorsCount-1)) { indices.push_back(k1 + 1); indices.push_back(k2); indices.push_back(k2 + 1); } } } // Загрузка в модель model.load_verteces(&vertices[0], vertices.size()); model.load_normals(&normals[0], normals.size()); model.load_indices(&indices[0], indices.size()); } // Изменение флага записи идентификатора для всех моделей void GrouptedModel::set_group_id(GLuint64 id, GLuint etc) { for (auto& part : parts) { part.id.value = id; if (etc) part.id.etc = etc; } } // Сдвинуть объект на dvec void Movable::dPosition(const glm::vec3& dvec) { position += dvec; } // Повернуть объект на dvec void Movable::dRotation(const glm::vec3& dvec) { rotation += dvec; } // Увеличить объект на dvec void Movable::dScale(const glm::vec3& dvec) { scale += dvec; } // Получить позицию объекта const glm::vec3 Movable::getPos() { return position; }