From 969862ca56bc926e4bfb27a7b8ccada928553e16 Mon Sep 17 00:00:00 2001 From: "re.kovalev" Date: Mon, 14 Nov 2022 20:24:58 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9A=D0=BE=D0=BF=D0=B8=D1=8F=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=D0=BA=D1=82=D0=B0=20=D1=81=2006?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/c_cpp_properties.json | 20 +++ .vscode/settings.json | 48 ++++++ .vscode/tasks.json | 79 +++++++++ include/Buffers.h | 62 +++++++ include/Camera.h | 49 ++++++ include/Model.h | 59 +++++++ include/Shader.h | 30 ++++ include/Texture.h | 33 ++++ shaders/shader.frag | 13 ++ shaders/shader.vert | 21 +++ src/Buffers.cpp | 144 ++++++++++++++++ src/Camera.cpp | 141 +++++++++++++++ src/Model.cpp | 314 ++++++++++++++++++++++++++++++++++ src/Shader.cpp | 154 +++++++++++++++++ src/Texture.cpp | 119 +++++++++++++ src/main.cpp | 138 +++++++++++++++ 16 files changed, 1424 insertions(+) create mode 100644 .vscode/c_cpp_properties.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 include/Buffers.h create mode 100644 include/Camera.h create mode 100644 include/Model.h create mode 100644 include/Shader.h create mode 100644 include/Texture.h create mode 100644 shaders/shader.frag create mode 100644 shaders/shader.vert create mode 100644 src/Buffers.cpp create mode 100644 src/Camera.cpp create mode 100644 src/Model.cpp create mode 100644 src/Shader.cpp create mode 100644 src/Texture.cpp create mode 100644 src/main.cpp diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..04cf9c6 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,20 @@ +{ + "configurations": [ + { + "name": "some_name", + "includePath": [ + "${workspaceFolder}/include", + "${workspaceFolder}/../dependencies/GLFW/include", + "${workspaceFolder}/../dependencies/glad/include", + "${workspaceFolder}/../dependencies/glm", + "${workspaceFolder}/../dependencies/stb", + "${workspaceFolder}/../dependencies/tinyobjloader" + ], + "compilerPath": "C:/MinGW/bin/g++.exe", + "cStandard": "c11", + "cppStandard": "c++11", + "intelliSenseMode": "gcc-x86" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e7216c1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,48 @@ +{ + "files.associations": { + "fstream": "cpp", + "iosfwd": "cpp", + "map": "cpp", + "atomic": "cpp", + "new": "cpp", + "array": "cpp", + "*.tcc": "cpp", + "cctype": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "random": "cpp", + "string": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "initializer_list": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "ostream": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "cinttypes": "cpp", + "typeinfo": "cpp" + } +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..6afc88d --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,79 @@ +{ + "tasks": [ + { + "type": "cppbuild", + "label": "C/C++: g++.exe сборка активного файла", + "command": "C:/MinGW/bin/g++.exe", + "args": [ + "-fdiagnostics-color=always", + "${workspaceRoot}/src/*.cpp", + "${workspaceRoot}/../dependencies/glad/src/glad.c", + + "-I${workspaceRoot}/include", + + "--std=c++11", + + "-I${workspaceRoot}/../dependencies/GLFW/include", + "-L${workspaceRoot}/../dependencies/GLFW/lib-mingw", + "-I${workspaceFolder}/../dependencies/glad/include", + "-I${workspaceFolder}/../dependencies/glm", + "-I${workspaceFolder}/../dependencies/stb", + "-I${workspaceFolder}/../dependencies/tinyobjloader", + "-static", + "-lopengl32", + "-lglfw3dll", + "-o", + "${workspaceRoot}/${workspaceFolderBasename}.exe" + ], + "options": { + "cwd": "${fileDirname}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "detail": "Задача создана отладчиком." + }, + { + "type": "cppbuild", + "label": "C/C++ x64: g++.exe сборка активного файла", + "command": "C:/MinGW64/bin/g++.exe", + "args": [ + "-fdiagnostics-color=always", + "${workspaceRoot}/src/*.cpp", + "${workspaceRoot}/../dependencies/glad/src/glad.c", + + "-I${workspaceRoot}/include", + + "--std=c++11", + + "-I${workspaceRoot}/../dependencies/GLFW/include", + "-L${workspaceRoot}/../dependencies/GLFW/lib-mingw-w64", + "-I${workspaceFolder}/../dependencies/glad/include", + "-I${workspaceFolder}/../dependencies/glm", + "-I${workspaceFolder}/../dependencies/stb", + "-I${workspaceFolder}/../dependencies/tinyobjloader", + "-static", + "-lopengl32", + "-lglfw3dll", + "-o", + "${workspaceRoot}/${workspaceFolderBasename}.exe" + ], + "options": { + "cwd": "${fileDirname}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "detail": "Задача создана отладчиком." + } + ], + "version": "2.0.0" +} \ No newline at end of file diff --git a/include/Buffers.h b/include/Buffers.h new file mode 100644 index 0000000..903246c --- /dev/null +++ b/include/Buffers.h @@ -0,0 +1,62 @@ +#ifndef BUFFERS_H +#define BUFFERS_H + +#include + +#include + +// Объект массива вершин +class VAO +{ + public: + VAO(); // Создает VAO и активирует его + ~VAO(); // Уничтожает VAO + VAO(const VAO & copy); // Конструктор копирования + VAO& operator=(const VAO & other); // Оператор присваивания + + void use(); // Активация VAO + static void disable(); // Деактивация активного VAO + + private: + GLuint handler; // Дескриптор + static std::map handler_count; // Счетчик использований дескриптора +}; + +// Тип буфера +enum BUFFER_TYPE { VERTEX = GL_ARRAY_BUFFER + , ELEMENT = GL_ELEMENT_ARRAY_BUFFER + , UNIFORM = GL_UNIFORM_BUFFER + }; + +// Объект вершинного буфера +class BO +{ + public: + BO(BUFFER_TYPE type); // Создает пустой буфер заданного типа + BO(BUFFER_TYPE type, const void *data, int size); // Создает и загружает туда данные + ~BO(); // Уничтожает буфер + BO(const BO & copy); // Конструктор копирования + BO& operator=(const BO & other); // Оператор присваивания + + void load(const void *data, int size, GLuint mode = GL_STATIC_DRAW); // Загрузка данных в буфер + void use(); + + protected: + GLuint handler; // Дескриптор + BUFFER_TYPE type; // Тип буфера + private: + static std::map handler_count; // Счетчик использований дескриптора +}; + +// Объект uniform-буфера +class UBO : public BO +{ + public: + UBO(int size, int binding); // Создает пустой uniform-буфер заданного размера с автоматической привязкой + UBO(const void *data, int size, int binding); // Создает пустой uniform-буфер заданного размера с автоматической привязкой + + void rebind(int binding); // Перепривязка + void loadSub(const void *data, int size, int offset = 0); // Загрузка с отступом +}; + +#endif // BUFFERS_H diff --git a/include/Camera.h b/include/Camera.h new file mode 100644 index 0000000..3357cba --- /dev/null +++ b/include/Camera.h @@ -0,0 +1,49 @@ +#ifndef CAMERA_H +#define CAMERA_H + +#include + +// Ближняя граница области отсечения +#define CAMERA_NEAR 0.1f +// Дальняя граница области отсечения +#define CAMERA_FAR 100.0f +// Вектор, задающий верх для камеры +#define CAMERA_UP_VECTOR glm::vec3(0.0f, 1.0f, 0.0f) +// Стандартный угол обзора +#define CAMERA_FOVy 60.0f + +class Camera +{ + public: + Camera(float aspect, const glm::vec3 &position = glm::vec3(0.0f, 0.0f, 0.0f), const glm::vec2 &xyOffset = glm::vec2(90.0f, 0.0f), float fovy = CAMERA_FOVy); // Конструктор камеры с проекцией перспективы + Camera(float width, float height, const glm::vec3 &position = glm::vec3(0.0f, 0.0f, 0.0f), const glm::vec2 &xyOffset = glm::vec2(90.0f, 0.0f)); // Конструктор ортографической камеры + virtual ~Camera(); // Деструктор + const glm::mat4& getVP(); // Возвращает ссылку на константную матрицу произведения матриц вида и проекции + const glm::mat4& getProjection(); // Возвращает ссылку на константную матрицу проекции + const glm::mat4& getView(); // Возвращает ссылку на константную матрицу вида + void rotate(const glm::vec2 &xyOffset); // Поворачивает камеру на dx и dy пикселей + void move(const glm::vec3 &posOffset); // Сдвигает камеру на указанный вектор (dx,dy,dz) + void setPosition(const glm::vec3 &position); // Устанавливает местоположение + void setRotation(const glm::vec2 &xyOffset); // Устанавливает угол поворота камеры + void setPerspective(float fov, float aspect); // Устанавливает заданную матрицу перспективы + void setOrtho(float width, float height); // Устанавливает заданную ортографическую матрицу + void setSensitivity(float sensitivity); // Изменяет чувствительность мыши + protected: + Camera(const glm::vec3 &position, const glm::vec2 &xyOffset); // Защищенный (protected) констуктор камеры без перспективы + void recalcTarget(); // Пересчет цели, на которую смотрит камера + void recalcView(); // Пересчет матрицы вида + void recalcVP(); // Пересчет произведения матриц + + glm::vec3 position; // Местоположение камеры + glm::vec3 target; // Цель, на которую смотрит камера + glm::vec2 currentRotation; // Текущий поворот камеры + glm::mat4 projection; // Матрица проекции + glm::mat4 view; // Матрица вида + glm::mat4 vp; // Матрица произведения вида и проекции + bool requiredRecalcVP; // Необходимость пересчета матрицы вида и проекции камеры + bool requiredRecalcView; // Необходимость пересчета матрицы вида камеры + float sensitivity; // Чувствительность мыши +}; + + +#endif // CAMERA_H \ No newline at end of file diff --git a/include/Model.h b/include/Model.h new file mode 100644 index 0000000..bca141d --- /dev/null +++ b/include/Model.h @@ -0,0 +1,59 @@ +#ifndef MODEL_H +#define MODEL_H + +#include "Buffers.h" +#include "Texture.h" + +#include + +#include + +#define DEFAULT_MTL_DIR "./" +class GrouptedModel loadOBJtoGroupted(const char* filename, const char* mtl_directory = DEFAULT_MTL_DIR, const char* texture_directory = DEFAULT_MTL_DIR); + +// Класс определяющий положение, вращение и размер объекта +class Movable +{ + public: + Movable(); // Конструктор без параметров + Movable(const Movable& copy); // Конструктор копирования + + glm::vec3 position; // позиция модели + glm::vec3 rotation; // поворот модели + glm::vec3 scale; // масштабирование модели + glm::mat4 getTransformMatrix(); // Матрица трансформации модели +}; + +// Класс модели с примененным материалом +class Model : public Movable +{ + public: + Model(); // Конструктор без параметров + Model(const Model& copy); // Конструктор копирования + ~Model(); + void render(const GLuint &model_uniform, const glm::mat4& global_tranform = glm::mat4(1)); // Вызов отрисовки + void load_verteces(glm::vec3* verteces, GLuint count); // Загрузка вершин в буфер + void load_indices(GLuint* indices, GLuint count); // Загрузка индексов в буфер + void load_texCoords(glm::vec2* texCoords, GLuint count); // Загрузка текстурных координат в буфер + void load_normals(glm::vec3* normals, GLuint count); // Загрузка нормалей в буфер + void set_texture(Texture& texture); // Привязка текстуры к модели + void set_index_range(GLuint beg, GLuint count); // Ограничение диапазона из буфера индексов + + private: + VAO vao; + BO vertex_vbo, index_vbo; // вершинный и индексный буферы + BO normals_vbo, texCoords_vbo; // буферы с нормалями и текстурными координатами + GLuint verteces_count; // Количество вершин + GLuint first_index, indices_count; // Первый и количество индексов + Texture texture_diffuse; // Диффузная текстура +}; + +// Класс сгруппированной модели +class GrouptedModel: public Movable +{ + public: + void render(const GLuint &model_uniform); // Вызов отрисовки + std::vector parts; +}; + +#endif // MODEL_H diff --git a/include/Shader.h b/include/Shader.h new file mode 100644 index 0000000..0562dd7 --- /dev/null +++ b/include/Shader.h @@ -0,0 +1,30 @@ +#ifndef SHADER_H +#define SHADER_H + +#include + +#include +#include + +// Класс шейдерной программы +class ShaderProgram +{ + public: + ShaderProgram(); + ShaderProgram(const ShaderProgram ©); + ~ShaderProgram(); + ShaderProgram& operator=(const ShaderProgram& other); + + void use(); // Использование шейдеров + void load(GLuint type, const char* filename); // Функция для загрузки шейдеров + void link(); // Формирование программы из загруженных шейдеров + GLuint getUniformLoc(const char* name); // Возвращает местоположение uniform-переменной + void bindUniformBlock(const char* name, int binding); // Привязка uniform-блока + void bindTextures(const char* textures_base_shader_names[], int count); // Инициализация текстур на шейдере + private: + GLuint program; // Дескриптор + static std::map handler_count; // Получение количества использований по дескриптору шейдера (Shared pointer) + std::map uniformLocations; // Местоположения uniform-переменных +}; + +#endif // SHADER_H \ No newline at end of file diff --git a/include/Texture.h b/include/Texture.h new file mode 100644 index 0000000..207116f --- /dev/null +++ b/include/Texture.h @@ -0,0 +1,33 @@ +#ifndef TEXTURE_H +#define TEXTURE_H + +#include + +#include +#include + +enum TexType { + TEX_DIFFUSE, + TEX_AVAILABLE_COUNT +}; + +class Texture +{ + public: + Texture(GLuint type = TEX_AVAILABLE_COUNT, const std::string& filename = ""); // Загрузка текстуры с диска или использование "пустой" + Texture(const Texture& other); // Конструктор копирования + ~Texture(); + + Texture& operator=(const Texture& other); // Оператор присваивания + + void use(); // Привязка текстуры + static void disable(GLuint type); // Отвязка текстуры по типу + GLuint getType(); // Возвращает тип текстуры + private: + GLuint handler; // Дескриптор текстуры + GLuint type; // Тип текстуры, соответствует её слоту + static std::map filename_handler; // Получение дескриптора текстуры по её имени + static std::map handler_count; // Получение количества использований по дескриптору текстуры (Shared pointer) +}; + +#endif // TEXTURE_H diff --git a/shaders/shader.frag b/shaders/shader.frag new file mode 100644 index 0000000..801807d --- /dev/null +++ b/shaders/shader.frag @@ -0,0 +1,13 @@ +#version 330 core + +in vec2 texCoord; + +uniform sampler2D tex_diffuse; + +out vec4 color; + +void main() +{ + color = texture(tex_diffuse, texCoord); +} + \ No newline at end of file diff --git a/shaders/shader.vert b/shaders/shader.vert new file mode 100644 index 0000000..ffe81bb --- /dev/null +++ b/shaders/shader.vert @@ -0,0 +1,21 @@ +#version 420 core + +layout(location = 0) in vec3 pos; +layout(location = 1) in vec2 inTexCoord; +layout(location = 2) in vec3 normals; + +layout(std140, binding = 0) uniform Camera +{ + mat4 projection; + mat4 view; +}; + +uniform mat4 model; + +out vec2 texCoord; + +void main() +{ + gl_Position = projection * view * model * vec4(pos, 1.0); + texCoord = inTexCoord; +} diff --git a/src/Buffers.cpp b/src/Buffers.cpp new file mode 100644 index 0000000..2e9eebb --- /dev/null +++ b/src/Buffers.cpp @@ -0,0 +1,144 @@ +#include "Buffers.h" + +// Счетчики использований дескрипторов +std::map VAO::handler_count; +std::map BO::handler_count; + +// Создает VAO и активирует его +VAO::VAO() +{ + glGenVertexArrays(1, &handler); // Генерация одного объекта массива вершин + glBindVertexArray(handler); // Привязка для использования + handler_count[handler] = 1; // Инициализация счетчика для дескриптора +} + +// Уничтожает VAO +VAO::~VAO() +{ + // Если дескриптор никем не используется - освободим его + if (!--handler_count[handler]) + { + glDeleteVertexArrays(1, &handler); + handler_count.erase(handler); // Удаление из словаря + } +} + +// Конструктор копирования +VAO::VAO(const VAO & copy) : handler(copy.handler) +{ + handler_count[handler]++; +} + +// Оператор присваивания +VAO& VAO::operator=(const VAO & other) +{ + // Если это разные дескрипторы + if (handler != other.handler) + { // то следуюет удалить текущий перед заменой + this->~VAO(); + handler = other.handler; + handler_count[handler]++; + } + + return *this; +} + +// Активация VAO +void VAO::use() +{ + glBindVertexArray(handler); // Привязка VAO для использования +} + +// Деактивация активного VAO +void VAO::disable() +{ + glBindVertexArray(0); // Отключение VAO +} + +// Создает пустой буфер заданного типа +BO::BO(BUFFER_TYPE t) : type(t) +{ + glGenBuffers(1, &handler); // Генерация одного объекта буфера + handler_count[handler] = 1; + use(); // Привязка буфера +} + +// Создает и загружает туда данные +BO::BO(BUFFER_TYPE t, const void *data, int size) : BO(t) +{ + load(data, size); +} + +// Уничтожает буфер +BO::~BO() +{ + if (handler) // Если буфер был создан + { + // Если дескриптор никем не используется - освободим его + if (!--handler_count[handler]) + { + glDeleteBuffers(1, &handler); + handler_count.erase(handler); // Удаление из словаря + } + handler = 0; + } +} + +// Конструктор копирования +BO::BO(const BO & copy) : handler(copy.handler), type(copy.type) +{ + handler_count[handler]++; +} + +// Оператор присваивания +BO& BO::operator=(const BO & other) +{ + // Если это разные дескрипторы + if (handler != other.handler) + { // то следуюет удалить текущий перед заменой + this->~BO(); + handler = other.handler; + handler_count[handler]++; + } + // Изменим тип + type = other.type; + + return *this; +} + +// Загрузка вершин в буфер +void BO::load(const void *data, int size, GLuint mode) +{ + use(); // Привязка буфера + glBufferData(type, size, data, mode); +} + +void BO::use() +{ + glBindBuffer(type, handler); // Привязка элементного буфера +} + +// Создает пустой uniform-буфер заданного размера с автоматической привязкой +UBO::UBO(int size, int binding) : BO(UNIFORM, 0, size) +{ + rebind(binding); +} + +// Создает пустой uniform-буфер заданного размера с автоматической привязкой +UBO::UBO(const void *data, int size, int binding) : BO(UNIFORM, data, size) +{ + rebind(binding); +} + +// перепривязка +void UBO::rebind(int binding) +{ + glBindBufferBase(type, binding, handler); +} + +// Загрузка с отступом +void UBO::loadSub(const void *data, int size, int offset) +{ + use(); + glBufferSubData(type, offset, size, data); +} diff --git a/src/Camera.cpp b/src/Camera.cpp new file mode 100644 index 0000000..d1f6431 --- /dev/null +++ b/src/Camera.cpp @@ -0,0 +1,141 @@ +#include "Camera.h" + +#include +#include + +// Защищенный (protected) конструктор камеры без перспективы +Camera::Camera(const glm::vec3 &pos, const glm::vec2 &xyOffset) +: position(pos), currentRotation(xyOffset) +{ + sensitivity = 0.05; + recalcTarget(); +} + +// Конструктор камеры с проекцией перспективы +Camera::Camera(float aspect, const glm::vec3 &position, const glm::vec2 &xyOffset, float fovy) +: Camera(position, xyOffset) +{ + setPerspective(fovy, aspect); +} + +// Конструктор ортографической камеры +Camera::Camera(float width, float height, const glm::vec3 &position, const glm::vec2 &xyOffset) +: Camera(position, xyOffset) +{ + setOrtho(width, height); +} + +// Деструктор +Camera::~Camera() { } + +// Пересчет цели, на которую смотрит камера +void Camera::recalcTarget() +{ + if(currentRotation.y > 89.0f) + currentRotation.y = 89.0f; + if(currentRotation.y < -89.0f) + currentRotation.y = -89.0f; + + target.x = cos(glm::radians(currentRotation.x)) * cos(glm::radians(currentRotation.y)); + target.y = sin(glm::radians(currentRotation.y)); + target.z = sin(glm::radians(currentRotation.x)) * cos(glm::radians(currentRotation.y)); + + requiredRecalcView = true; + requiredRecalcVP = true; +} + +// Пересчет матрицы вида +void Camera::recalcView() +{ + view = glm::lookAt(position, position + target, CAMERA_UP_VECTOR); + requiredRecalcView = false; +} + +// Пересчет произведения матриц +void Camera::recalcVP() +{ + vp = projection * view; + requiredRecalcVP = false; +} + +// Возвращает ссылку на константную матрицу проекции +const glm::mat4& Camera::getProjection() +{ + return projection; +} + +// Возвращает ссылку на константную матрицу вида +const glm::mat4& Camera::getView() +{ + if (requiredRecalcView) + recalcView(); + return view; +} + +// Возвращает ссылку на константную матрицу вида +const glm::mat4& Camera::getVP() +{ + if (requiredRecalcVP) + { + if (requiredRecalcView) + recalcView(); + recalcVP(); + } + return vp; +} + +// Поворачивает камеру на dx и dy пикселей +void Camera::rotate(const glm::vec2 &xyOffset) +{ + currentRotation += xyOffset * sensitivity; + + recalcTarget(); + requiredRecalcView = true; + requiredRecalcVP = true; +} + +// Сдвигает камеру на указанный вектор (dx,dy,dz) +void Camera::move(const glm::vec3 &posOffset) +{ + position += posOffset; + + requiredRecalcView = true; + requiredRecalcVP = true; +} + +// Устанавливает местоположение +void Camera::setPosition(const glm::vec3 &pos) +{ + position = pos; + + requiredRecalcView = true; + requiredRecalcVP = true; +} + +// Устанавливает угол поворота камеры +void Camera::setRotation(const glm::vec2 &xyOffset) +{ + currentRotation = xyOffset; + recalcTarget(); +} + +// Устанавливает заданную матрицу перспективы +void Camera::setPerspective(float fovy, float aspect) +{ + projection = glm::perspective(glm::radians(fovy), aspect, CAMERA_NEAR, CAMERA_FAR); + requiredRecalcVP = true; +} + +// Устанавливает заданную ортографическую матрицу +void Camera::setOrtho(float width, float height) +{ + const float aspect = width / height; + projection = glm::ortho(-1.0f, 1.0f, -1.0f/aspect, 1.0f/aspect, CAMERA_NEAR, CAMERA_FAR); + requiredRecalcVP = true; +} + +// Изменяет чувствительность мыши +void Camera::setSensitivity(float sens) +{ + sensitivity = sens; +} diff --git a/src/Model.cpp b/src/Model.cpp new file mode 100644 index 0000000..0b3ebef --- /dev/null +++ b/src/Model.cpp @@ -0,0 +1,314 @@ +#include "Model.h" +#include "Camera.h" +extern Camera camera; + +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(VERTEX), index_vbo(ELEMENT), normals_vbo(VERTEX), texCoords_vbo(VERTEX) +{ + +} + +// Конструктор копирования +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), +texture_diffuse(copy.texture_diffuse) +{ + +} + +Model::~Model() +{ + +} + +// Вызов отрисовки +void Model::render(const GLuint &model_uniform, const glm::mat4& global_tranform) +{ + // Расчитаем матрицу трансформации + glm::mat4 model = global_tranform * this->getTransformMatrix(); + glUniformMatrix4fv(model_uniform, 1, GL_FALSE, &model[0][0]); + + // Подключаем текстуры + texture_diffuse.use(); + + // Подключаем 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 vertex_attrib_config() +{ + // Включаем необходимый атрибут у выбранного VAO + glEnableVertexAttribArray(0); + // Устанавливаем связь между VAO и привязанным VBO + glVertexAttribPointer( 0 // индекс атрибута, должен совпадать с Layout шейдера + , 3 // количество компонент одного элемента + , GL_FLOAT // тип + , GL_FALSE // нормализованность значений + , 0 // шаг + , (void *)0 // отступ с начала массива + ); +} + +// Загрузка вершин в буфер +void Model::load_verteces(glm::vec3* verteces, GLuint count) +{ + // Подключаем VAO и вершинный буфер + vao.use(); + vertex_vbo.use(); + + // Загрузка вершин в память буфера + vertex_vbo.load(verteces, sizeof(glm::vec3)*count); + vertex_attrib_config(); + // Запоминаем количество вершин для отрисовки + verteces_count = count; +} + +// Загрузка индексов в буфер +void Model::load_indices(GLuint* indices, GLuint count) +{ + // Подключаем VAO и индексный буфер + vao.use(); + index_vbo.use(); + + // Загрузка вершин в память буфера + index_vbo.load(indices, sizeof(GLuint)*count); + // Запоминаем количество вершин для отрисовки + indices_count = count; +} + +// Функция для конфигурации атрибута вершинного буфера +void texCoords_attrib_config() +{ + // Включаем необходимый атрибут у выбранного VAO + glEnableVertexAttribArray(1); + // Устанавливаем связь между VAO и привязанным VBO + glVertexAttribPointer( 1 // индекс атрибута, должен совпадать с Layout шейдера + , 2 // количество компонент одного элемента + , GL_FLOAT // тип + , GL_FALSE // нормализованность значений + , 0 // шаг + , (void *)0 // отступ с начала массива + ); +} + +// Загрузка текстурных координат в буфер +void Model::load_texCoords(glm::vec2* texCoords, GLuint count) +{ + // Подключаем VAO + vao.use(); + + texCoords_vbo.use(); + + // Загрузка вершин в память буфера + texCoords_vbo.load(texCoords, sizeof(glm::vec2)*count); + texCoords_attrib_config(); +} + +// Функция для конфигурации атрибута вершинного буфера +void normals_attrib_config() +{ + // Включаем необходимый атрибут у выбранного VAO + glEnableVertexAttribArray(2); + // Устанавливаем связь между VAO и привязанным VBO + glVertexAttribPointer( 2 // индекс атрибута, должен совпадать с Layout шейдера + , 3 // количество компонент одного элемента + , GL_FLOAT // тип + , GL_FALSE // нормализованность значений + , 0 // шаг + , (void *)0 // отступ с начала массива + ); +} + +// Загрузка нормалей в буфер +void Model::load_normals(glm::vec3* normals, GLuint count) +{ + // Подключаем VAO + vao.use(); + + normals_vbo.use(); + + // Загрузка вершин в память буфера + normals_vbo.load(normals, sizeof(glm::vec3)*count); + normals_attrib_config(); +} + +#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_DIFFUSE: + texture_diffuse = texture; + break; + }; +} + +// Ограничение диапазона из буфера индексов +void Model::set_index_range(GLuint beg, GLuint count) +{ + first_index = beg; + indices_count = 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...); +} + +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; + + // Если в процессе загрузки возникли ошибки - выдадим исключение + 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; // текстурные координаты + 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) + + + + // Загрузка в буферы + model.load_verteces (&verteces[0], verteces.size()); + model.load_normals (&normals[0], normals.size()); + model.load_texCoords(&texCords[0], texCords.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]); + + Texture diffuse(TEX_DIFFUSE, texture_directory + materials[materials_ids[i]].diffuse_texname); + s->set_texture(diffuse); + } + + return result; +} + +// Вызов отрисовки групповой модели +void GrouptedModel::render(const GLuint &model_uniform) +{ + glm::mat4 transform = this->getTransformMatrix(); + for (auto& model : parts) + model.render(model_uniform, transform); +} diff --git a/src/Shader.cpp b/src/Shader.cpp new file mode 100644 index 0000000..ad9b1a8 --- /dev/null +++ b/src/Shader.cpp @@ -0,0 +1,154 @@ +#include "Shader.h" + +#include +#include +#include + +std::map ShaderProgram::handler_count; // Получение количества использований по дескриптору ШП (Shared pointer) + +ShaderProgram::ShaderProgram() +{ + program = glCreateProgram(); + handler_count[program] = 1; +} + +ShaderProgram::ShaderProgram(const ShaderProgram ©) : program(copy.program) +{ + handler_count[program]++; +} + +ShaderProgram::~ShaderProgram() +{ + if (!--handler_count[program]) // Если количество ссылок = 0 + { + // Удаление шейдерной программы + glDeleteProgram(program); + } +} + +// Оператор присваивания +ShaderProgram& ShaderProgram::operator=(const ShaderProgram& other) +{ + // Если это разные шейдерные программы + if (program != other.program) + { + this->~ShaderProgram(); // Уничтожаем имеющуюся + // Заменяем новой + program = other.program; + handler_count[program]++; + } + return *this; +} + +// Использование шейдеров +void ShaderProgram::use() +{ + glUseProgram(program); +} + +// Функция чтения шейдера из файла +std::string readFile(const char* filename) +{ + std::string text; + std::ifstream file(filename, std::ios::in); // Открываем файл на чтение + // Если файл доступен и успешно открыт + if (file.is_open()) + { + std::stringstream sstr; // Буфер для чтения + sstr << file.rdbuf(); // Считываем файл + text = sstr.str(); // Преобразуем буфер к строке + file.close(); // Закрываем файл + } + + return text; +} + +// Функция для загрузки шейдеров +void ShaderProgram::load(GLuint type, const char* filename) +{ + // Создание дескрипторов шейдера + GLuint handler = glCreateShader(type); + + // Переменные под результат компиляции + GLint result = GL_FALSE; + int infoLogLength; + + // Считываем текст вершинного шейдера + std::string code = readFile(filename); + const char* pointer = code.c_str(); // Преобразование к указателю на const char, так как функция принимает массив си-строк + + // Компиляция кода вершинного шейдера + glShaderSource(handler, 1, &pointer, NULL); + glCompileShader(handler); + + // Проверка результата компиляции + glGetShaderiv(handler, GL_COMPILE_STATUS, &result); + glGetShaderiv(handler, GL_INFO_LOG_LENGTH, &infoLogLength); + if (infoLogLength > 0) + { + char* errorMessage = new char[infoLogLength + 1]; + glGetShaderInfoLog(handler, infoLogLength, NULL, errorMessage); + std::cout << errorMessage; + delete errorMessage; + } + + // Привязка скомпилированного шейдера + glAttachShader(program, handler); + + // Освобождение дескриптора шейдера + glDeleteShader(handler); +} + +// Формирование программы из загруженных шейдеров +void ShaderProgram::link() +{ + // Переменные под результат компиляции + GLint result = GL_FALSE; + int infoLogLength; + + // Формирование программы из привязанных шейдеров + glLinkProgram(program); + + // Проверка программы + glGetProgramiv(program, GL_LINK_STATUS, &result); + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength); + if (infoLogLength > 0) + { + char* errorMessage = new char[infoLogLength + 1]; + glGetProgramInfoLog(program, infoLogLength, NULL, errorMessage); + std::cout << errorMessage; + delete errorMessage; + } + + // Используем шейдерную программу объекта из которого вызван метод + this->use(); +} + +// Возвращает местоположение uniform-переменной +GLuint ShaderProgram::getUniformLoc(const char* name) +{ + GLuint result; // Результат + // Если такую переменную ещё не искали - найдем, иначе вернем уже известный дескриптор + if (!uniformLocations.count(name)) + uniformLocations[name] = result = glGetUniformLocation(program, name); + else + result = uniformLocations[name]; + + return result; +} + +// Привязка uniform-блока +void ShaderProgram::bindUniformBlock(const char* name, int binding) +{ + glUniformBlockBinding( program + , glGetUniformBlockIndex(program, name) + , binding); +} + +// Инициализация текстур на шейдере +void ShaderProgram::bindTextures(const char* textures_base_shader_names[], int count) +{ + // Цикл по всем доступным текстурам + for (int i = 0; i < count; i++) + glUniform1i(getUniformLoc(textures_base_shader_names[i]), i); +} diff --git a/src/Texture.cpp b/src/Texture.cpp new file mode 100644 index 0000000..29401ad --- /dev/null +++ b/src/Texture.cpp @@ -0,0 +1,119 @@ +#include "Texture.h" + +#define STB_IMAGE_IMPLEMENTATION +#include + +std::map Texture::filename_handler; // Получение дескриптора текстуры по её имени +std::map Texture::handler_count; // Получение количества использований по дескриптору текстуры (Shared pointer) + +// Загрузка текстуры с диска или использование "пустой" +Texture::Texture(GLuint t, const std::string& filename) : type(t) +{ + if (!filename_handler.count(filename)) + { + std::string empty = ""; + int width, height, channels; // Ширина, высота и цветовые каналы текстуры + unsigned char* image = stbi_load(filename.c_str(), &width, &height, &channels, STBI_default); // Загрузка в оперативную память изображения + // Если изображение успешно счиитано с диска или отсутствует пустая текстура + if (image || !filename_handler.count(empty)) + { + glActiveTexture(type + GL_TEXTURE0); + glGenTextures(1, &handler); // Генерация одной текстуры + glBindTexture(GL_TEXTURE_2D, handler); // Привязка текстуры как активной + + filename_handler[filename] = handler; // Запоминим её дескриптор для этого имени файла + handler_count[handler] = 0; // Создадим счетчик использований дескриптора, который будет изменен в конце + + // Если изображение успешно считано + if (image) + { + // Загрузка данных с учетом прозрачности + if (channels == 3) // RGB + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image); + else if (channels == 4) // RGBA + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image); + + glGenerateMipmap(GL_TEXTURE_2D); // Генерация мипмапа для активной текстуры + glBindTexture(GL_TEXTURE_2D, 0); // Отвязка активной текстуры + + stbi_image_free(image); // Освобождение оперативной памяти + } + // Иначе изображение не считано и надо создать пустую текстуру + else + { + image = new unsigned char[3] {255,255,255}; // RGB по 1 байту на + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, image); // Загрузка данных на видеокарту + delete image; // Освобождение оперативной памяти + + filename_handler[empty] = handler; // Запоминим дополнительно её дескриптор для NULL-строки + } + } + // Иначе используем существующую пустую текстуру (текстура не загружена, пустую создавать не нужно) + else + handler = filename_handler[empty]; + } + // Иначе используем уже существующую по имени файла + else + handler = filename_handler[filename]; + + handler_count[handler]++; +} + +// Конструктор копирования +Texture::Texture(const Texture& other) : handler(other.handler), type(other.type) +{ + // Делаем копию и увеличиваем счетчик + handler_count[handler]++; +} + +// Оператор присваивания +Texture& Texture::operator=(const Texture& other) +{ + // Если это разные текстуры + if (handler != other.handler) + { + this->~Texture(); // Уничтожаем имеющуюся + // Заменяем новой + handler = other.handler; + handler_count[handler]++; + } + type = other.type; + + return *this; +} + +Texture::~Texture() +{ + if (!--handler_count[handler]) // Если количество ссылок = 0 + { + glDeleteTextures(1, &handler); // Удаление текстуры + // Удаление из словаря имен файлов и дескрипторов + for (auto it = filename_handler.begin(); it != filename_handler.end();) + { + if (it->second == handler) + it = filename_handler.erase(it); + else + it++; + } + } +} + +// Привязка текстуры +void Texture::use() +{ + glActiveTexture(type + GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, handler); // Привязка текстуры как активной +} + +// Отвязка текстуры по типу +void Texture::disable(GLuint type) +{ + glActiveTexture(type + GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, 0); // Отвязка текстуры +} + +// Возвращает тип текстуры +GLuint Texture::getType() +{ + return type; +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..257605c --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,138 @@ + +#include +#include +#include + +#include + +#include "Camera.h" +#include "Model.h" +#include "Texture.h" +#include "Shader.h" + +#define WINDOW_WIDTH 800 +#define WINDOW_HEIGHT 600 +#define WINDOW_CAPTION "OPENGL notes on rekovalev.site" + +// Функция-callback для изменения размеров буфера кадра в случае изменения размеров поверхности окна +void framebuffer_size_callback(GLFWwindow* window, int width, int height) +{ + glViewport(0, 0, width, height); +} + +Camera camera(800.0f/600.0f); + +bool firstMouse = true; +float lastX, lastY; + +void mouse_callback(GLFWwindow* window, double xpos, double ypos) +{ + if (firstMouse) + { + lastX = xpos; + lastY = ypos; + firstMouse = false; + } + + glm::vec2 offset(xpos - lastX, lastY - ypos); + lastX = xpos; + lastY = ypos; + + camera.rotate(offset); +} + +int main(void) +{ + GLFWwindow* window; // Указатель на окно GLFW3 + + // Инициализация GLFW3 + if (!glfwInit()) + { + std::cout << "GLFW init error\n"; + return -1; + } + + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // Мажорная версия спецификаций OpenGL + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Минорная версия спецификаций OpenGL + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Контекст OpenGL, который поддерживает только основные функции + + // Создание окна GLFW3 с заданными шириной, высотой и заголовком окна + window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_CAPTION, NULL, NULL); + if (!window) + { + std::cout << "GLFW create window error\n"; + glfwTerminate(); // Завершение работы с GLFW3 в случае ошибки + return -1; + } + + // Установка основного контекста окна + glfwMakeContextCurrent(window); + // Установка callback-функции для изменения размеров окна и буфера кадра + glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); + + glfwSwapInterval(1); // Вертикальная синхронизация + + // Установка callback-функции для мыши и камеры + glfwSetCursorPosCallback(window, mouse_callback); + + // Загрузка функций OpenGL с помощью GLAD + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) + { + std::cout << "GLAD load GL error\n"; + glfwTerminate(); // Завершение работы с GLFW3 в случае ошибки + return -1; + } + + // Включаем проверку по буферу глубины + glEnable(GL_DEPTH_TEST); + + // Базовый шейдер + ShaderProgram base; + // Загрузка и компиляция шейдеров + base.load(GL_VERTEX_SHADER, "shaders/shader.vert"); + base.load(GL_FRAGMENT_SHADER, "shaders/shader.frag"); + base.link(); + // Установим значения текстур + const char* textures_base_shader_names[] = {"tex_diffuse"}; + base.bindTextures(textures_base_shader_names, sizeof(textures_base_shader_names)/sizeof(const char*)); + // camera.move({0,0,-20}); + + // Загрузка сцены из obj файла + GrouptedModel scene = loadOBJtoGroupted("../resources/models/cubes.obj", "../resources/models/", "../resources/textures/"); + + // Установка цвета очистки буфера цвета + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + + // Расположение Uniform-переменной + GLuint model_uniform = base.getUniformLoc("model"); + + // Uniform-буферы + UBO cameraUB(sizeof(glm::mat4)*2, 0); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // Использование уменьшенных версий mipmap + + // Пока не произойдет событие запроса закрытия окна + while(!glfwWindowShouldClose(window)) + { + // Очистка буфера цвета + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // Загрузка данных о камере + cameraUB.loadSub(&camera.getProjection(), sizeof(glm::mat4), 0); + cameraUB.loadSub(&camera.getView(), sizeof(glm::mat4), sizeof(glm::mat4)); + + // Тут производится рендер + scene.render(model_uniform); + + // Представление содержимого буфера цепочки показа на окно + glfwSwapBuffers(window); + // Обработка системных событий + glfwPollEvents(); + } + + // Отключение атрибутов + glDisableVertexAttribArray(0); + + // Завершение работы с GLFW3 перед выходом + glfwTerminate(); +}