diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..f41ec15 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,21 @@ +{ + "configurations": [ + { + "name": "some_name", + "includePath": [ + "${workspaceFolder}/include", + "${workspaceFolder}/../dependencies/GLFW/include", + "${workspaceFolder}/../dependencies/glad/include", + "${workspaceFolder}/../dependencies/glm", + "${workspaceFolder}/../dependencies/stb", + "${workspaceFolder}/../dependencies/tinyobjloader", + "${workspaceFolder}/../dependencies/tinyglTF" + ], + "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..d7f31e6 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,46 @@ +{ + "files.associations": { + "fstream": "cpp", + "iosfwd": "cpp", + "map": "cpp", + "atomic": "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..f8939c6 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,32 @@ +{ + "tasks": [ + { + "type": "cppbuild", + "label": "C/C++: make сборка", + "command": "make", + "args": [ + "${input:target}" + ], + "options": { + "cwd": "${workspaceRoot}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "detail": "Задача создана отладчиком." + } + ], + "inputs": [ + { + "id": "target", + "description": "Цель сборки (all, list, clean)", + "default": "all", + "type": "promptString" + }, + ], + "version": "2.0.0" +} \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..dfc9036 --- /dev/null +++ b/Makefile @@ -0,0 +1,102 @@ +# Компилятор и директория проекта +ifeq ($(OS), Windows_NT) + # С возможностью сборки x32 + ifeq ($(MAKECMDGOALS), x32) + CC = C:/MinGW/bin/g++.exe + else + CC = C:/MinGW64/bin/g++.exe + endif + PROJECT_DIR = $(shell echo %cd%) + PATH_SEPARATOR = \\ + # Имя исполняемого файла + EXECUTABLE = $(notdir $(strip $(PROJECT_DIR))).exe +else + CC = g++ + PROJECT_DIR = $(shell pwd) + PATH_SEPARATOR = / + # Имя исполняемого файла + EXECUTABLE = $(notdir $(strip $(PROJECT_DIR))) +endif + +# Опции компилятора +CFLAGS += -c +CFLAGS += -I./include +CFLAGS += -I../dependencies/GLFW/include +CFLAGS += -I../dependencies/glad/include +CFLAGS += -I../dependencies/glm +CFLAGS += -I../dependencies/stb +CFLAGS += -I../dependencies/tinyobjloader +CFLAGS += -I../dependencies/tinyglTF + +# Опции линкера +LDFLAGS += --std=gnu++11 +# Архитектурозависимые опции линкера +ifeq ($(OS), Windows_NT) + # GLFW в зависимости от архитектуры + ifeq ($(MAKECMDGOALS), x32) + LDFLAGS += -L../dependencies/GLFW/lib-mingw + else + LDFLAGS += -L../dependencies/GLFW/lib-mingw-w64 + endif + + LDFLAGS += -static + LDFLAGS += -lglfw3dll + LDFLAGS += -lopengl32 +else + LDFLAGS += -lglfw + LDFLAGS += -lGL +endif + +# Библиотека GLAD +GLAD := ../dependencies/glad/src/glad.c +GLAD_O := $(GLAD:.c=.o) + +# Файлы из директории src +SOURCES_C = $(wildcard src/*.c) +SOURCES_CPP = $(wildcard src/*.cpp) + +# Директория с объектными файлами +OBJ_DIR := Obj +# Объектные файлы +OBJECTS = $(addprefix $(OBJ_DIR)/,$(SOURCES_C:src/%.c=%.o) $(SOURCES_CPP:src/%.cpp=%.o)) + +# Для x32 сборки под Windows +ifeq ($(OS), Windows_NT) + ifeq ($(MAKECMDGOALS), x32) +x32: all + endif +endif + +# Цель по умолчанию, зависит от EXECUTABLE +all: $(EXECUTABLE) + +# Цель сборки исполняемого файла, зависит от OBJ_DIR, OBJECTS и GLAD_O +$(EXECUTABLE): $(OBJ_DIR) $(OBJECTS) $(GLAD_O) + $(CC) $(OBJECTS) $(GLAD_O) $(LDFLAGS) -o $@ + +# Цель для сборки GLAD +$(GLAD_O): $(GLAD) + $(CC) $(CFLAGS) $< -o $@ + +# Цель для создания директории с объектными файлами +$(OBJ_DIR): + @mkdir $(OBJ_DIR) + +# Цель сборки объектных файлов +$(OBJ_DIR)/%.o: src/%.c + $(CC) $(CFLAGS) $< -o $@ +$(OBJ_DIR)/%.o: src/%.cpp + $(CC) $(CFLAGS) $< -o $@ + +# Цель вывода всех файлов, учавствтующих в сборке +list: + @echo "В сборке участвуют:" $(OBJECTS) + +# Очистка +ifeq ($(OS), Windows_NT) +clean: $(OBJ_DIR) + @rmdir /s /q $(OBJ_DIR) +else +clean: $(OBJ_DIR) + @rm -f $(EXECUTABLE) $(OBJECTS) +endif diff --git a/include/Animation.h b/include/Animation.h new file mode 100644 index 0000000..5522e32 --- /dev/null +++ b/include/Animation.h @@ -0,0 +1,74 @@ +#ifndef ANIMATION_H +#define ANIMATION_H + +#include "Model.h" + +#include // Время + +// Тип интерполяции +enum INTERPOLIATION_TYPE +{ + STEP, + LINEAR, + CUBICSPLINE +}; + +// Анимируемый параметр +enum TARGET_PATH +{ + POSITION, + ROTATION, + SCALE +}; + +// Поведение при завершении шкалы ключевых кадров +enum ANIM_ENDINGS +{ + STOP, + TO_BEGIN, + CYCLED +}; + +union PARAMETER_TYPE +{ + glm::quat quat; + glm::vec3 vec3; +}; + +// Касательные для кубической-сплайн интерполяции +struct Tangents +{ + PARAMETER_TYPE in; + PARAMETER_TYPE out; +}; + +// Канал анимации +struct Channel +{ + void process(float dtime, ANIM_ENDINGS endings); // Выполнить анимацию для канала с учетом времени относительно начала + + class Node* target = NULL; // Анимируемый узел + TARGET_PATH path = POSITION; // Анимируемый параметр + + INTERPOLIATION_TYPE interpolation = STEP; // Тип интерполяции + std::vector timestamps; // Временные метки !ОБЯЗАТЕЛЬНО ОТСОРТИРОВАНЫ ПО ВОЗРАСТАНИЮ! + std::vector values; // Данные по параметру (ИНДЕКС СООТВЕТСТВУЕТ ВРЕМЕННЫМ МЕТКАМ) + std::vector tangents; // Касательные для CUBICSPLINE +}; + +// Класс анимации +class Animation +{ + public: + void begin(); // Задает состояние анимации как начало через запоминание времени + void end(); // Заканчивает выполнение анимации + void process(); // Вычисляет анимацию для каждого канала на основании текущего времени + bool isEnabled(); // Возвращает состояние анимации (вкл/выкл) + std::vector channels; // Каналы анимации + ANIM_ENDINGS endings = CYCLED; + private: + std::chrono::steady_clock::time_point begin_time; // Время начала анимации + bool enabled = false; +}; + +#endif // ANIMATION_H diff --git a/include/Buffers.h b/include/Buffers.h new file mode 100644 index 0000000..c56907d --- /dev/null +++ b/include/Buffers.h @@ -0,0 +1,90 @@ +#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); // Загрузка с отступом +}; + +// Объект буфера кадра +class FBO +{ + public: + FBO(GLuint *attachments = 0, int count = 0); // Создает буфер кадра с нужным числом прикреплений текстур + ~FBO(); // Уничтожение буфера + + void use(GLuint mode = GL_FRAMEBUFFER); // Активирует буфер кадра в заданном режиме + static void useDefault(GLuint mode = GL_FRAMEBUFFER); // Активирует базовый буфер в заданном режиме + void assignRenderBuffer(GLuint hander, GLuint attachment = GL_DEPTH_ATTACHMENT); // Привязка рендер буфера + protected: + GLuint handler; // Дескриптор +}; + +// Объект буфера рендера +class RBO +{ + public: + RBO(int w, int h, GLuint component = GL_DEPTH_COMPONENT); // Создает буфер рендера с заданными параметрами размеров и используемых компонент + ~RBO(); // Уничтожение буфера + + void reallocate(int w, int h, GLuint component = GL_DEPTH_COMPONENT); // Изменяет размеры буфера рендера + + GLuint getHandler(); // Возвращает дескриптор буфера рендера + protected: + GLuint handler; // Дескриптор +}; + +#endif // BUFFERS_H diff --git a/include/Camera.h b/include/Camera.h new file mode 100644 index 0000000..53d4663 --- /dev/null +++ b/include/Camera.h @@ -0,0 +1,82 @@ +#ifndef CAMERA_H +#define CAMERA_H + +#include +#include +#include +#include + +#include "Model.h" + +// Ближняя граница области отсечения +#define CAMERA_NEAR 0.1f +// Дальняя граница области отсечения +#define CAMERA_FAR 100.0f +// Вектор, задающий верх для камеры +#define CAMERA_UP_VECTOR glm::vec3(0.0f, 1.0f, 0.0f) +// Вектор, задающий стандартный поворот углами Эйлера (в положительном направлении оси Z) +#define CAMERA_DEFAULT_ROTATION glm::vec3(0.0f, 180.0f, 0.0f) +// Стандартный угол обзора +#define CAMERA_FOVy 60.0f +// Стандартная чувствительность +#define CAMERA_DEFAULT_SENSIVITY 0.005f +// Количество каскадов для карт теней +#define CAMERA_CASCADE_COUNT 4 + +// Данные о дистанциях каскадов +extern const float camera_cascade_distances[]; // src/Camera.cpp + +// Данные о камере для шейдера +struct CameraData +{ + glm::mat4 projection; + glm::mat4 view; + glm::vec3 position; +}; + +// Класс камеры +class Camera : public Node +{ + public: + Camera(float aspect, const glm::vec3 &position = glm::vec3(0.0f), const glm::vec3 &initialRotation = CAMERA_DEFAULT_ROTATION, float fovy = CAMERA_FOVy, float near = CAMERA_NEAR, float far = CAMERA_FAR); // Конструктор камеры с проекцией перспективы + Camera(float width, float height, const glm::vec3 &position = glm::vec3(0.0f), const glm::vec3 &initialRotation = CAMERA_DEFAULT_ROTATION, float near = CAMERA_NEAR, float far = CAMERA_FAR); // Конструктор ортографической камеры + Camera(const Camera& copy); // Конструктор копирования камеры + Camera& operator=(const Camera& other); // Оператор присваивания + virtual ~Camera(); // Деструктор + + const glm::mat4& getVP(); // Возвращает ссылку на константную матрицу произведения матриц вида и проекции + const glm::mat4& getProjection(); // Возвращает ссылку на константную матрицу проекции + const glm::mat4& getView(); // Возвращает ссылку на константную матрицу вида + + void rotate(const glm::vec2 &xyOffset); // Поворачивает камеру на dx и dy пикселей с учетом чувствительности + + void setPerspective(float fov, float aspect, float near = CAMERA_NEAR, float far = CAMERA_FAR); // Устанавливает заданную матрицу перспективы + void setOrtho(float width, float height, float near = CAMERA_NEAR, float far = CAMERA_FAR); // Устанавливает заданную ортографическую матрицу + void setSensitivity(float sensitivity); // Изменяет чувствительность мыши + const float& getSensitivity() const; // Возвращает чувствительность мыши + + void use(); // Использование этой камеры как текущей + static Camera& current(); // Ссылка на текущую используемую камеру + + CameraData& getData(); // Данные о камере для шейдера + std::pair getProjCoords(); // Доступ к координатам с флагом изменения, описывающим пространство вида с пересчетом, если это требуется + protected: + Camera(const glm::vec3 &position, const glm::vec3 &initialRotation); // Защищенный (protected) конструктор камеры без перспективы + + glm::mat4 view; // Матрица вида + glm::mat4 projection; // Матрица проекции + glm::mat4 vp; // Матрица произведения вида и проекции + bool requiredRecalcVP; // Необходимость пересчета матрицы вида и проекции камеры + bool requiredRecalcCoords; // Необходимость пересчета точек, описывающих пространство камеры + glm::vec4 coords[CAMERA_CASCADE_COUNT][8]; // Координаты, описывающие пространство камеры + glm::mat4 cascade_proj[CAMERA_CASCADE_COUNT]; // Матрицы проекций каскадов + + float sensitivity; // Чувствительность мыши + + virtual void recalcMatrices(); // Метод пересчета матрицы вида и произведения Вида*Проекции по необходимости, должен сбрасывать флаг changed + + static Camera* p_current; // Указатель на текущую используемую камеру +}; + + +#endif // CAMERA_H \ No newline at end of file diff --git a/include/Lights.h b/include/Lights.h new file mode 100644 index 0000000..8b43506 --- /dev/null +++ b/include/Lights.h @@ -0,0 +1,108 @@ +#ifndef LIGHTS_H +#define LIGHTS_H + +#include + +#include "Model.h" +#include "Camera.h" + +// Максимальное число источников света +#define MAX_LIGHTS 64 +// Стандартное направление источника без поворота +#define DEFAULT_LIGHT_DIRECTION glm::vec4(0.0f, 0.0f, 1.0f, 0.0f) +// Максимальное число образцов для SSAO +#define MAX_SSAO 64 + +// Точечный источник света +struct LightData +{ + alignas(16) glm::vec3 position; // Позиция + alignas(16) glm::vec3 color; // Цвет + alignas(16) glm::vec3 attenuation; // Радиус действия источника, линейный и квадратичный коэф. угасания + alignas(16) glm::vec4 direction_angle; // Направление и половинный угол освещенности + alignas(16) glm::mat4 vp[6]; // Матрицы проекции и трансформации в пространство источника +}; + +// Источник света +class Light : public Node +{ + public: + static int getUBOsize(); // Возвращает размер буфера в байтах + static void upload(UBO& lights_data); // Загрузка данных в буфер + + static Light& getNew(); // Возвращает ссылку на новый источник света + void destroy(); // Уничтожает источник света + + static int getCount(); // Возвращает количество источников + const glm::vec3& c_color() const; // Константный доступ к цвету + glm::vec3& e_color(); // Неконстантная ссылка для изменений цвета + + const float& c_radius() const; // Константный доступ к радиусу + float& e_radius(); // Неконстантная ссылка для изменений радиуса + + const float& c_angle() const; // Константный доступ к углу освещенности + float& e_angle(); // Неконстантная ссылка для изменений угла освещенности + + static void render(ShaderProgram &shaderProgram, UBO &material_buffer); // Рисование отладочных лампочек + private: + Light(); // Конструктор без параметров + Light(const Light& copy) = delete; // Конструктор копирования ОТКЛЮЧЕН + Light& operator=(const Light& other); // Оператор присваивания + virtual ~Light(); + + glm::vec3 color; // Цвет + float radius; // Радиус действия источника + float angle; // Угол полный освещенности + + int index; // Индекс в массиве отправки (может не совпадать с lights) для дефрагментированного доступа + static Light& findByIndex(GLuint index); // Возвращает ссылку на источник с нужным индексом + + bool uploadReq; // Необходимость загрузки в следствии изменений + void check_id(); // Проверка что не взаимодествуем с пустым источником + void toData(); // Преобразует информацию об источнике в структуру LightData + + virtual void recalcMatrices(); // Метод пересчета матрицы трансформации по необходимости, должен сбрасывать флаг changed + void recalcVP(); // Пересчитывает по необходимости матрицу вида-проекции + + static GLuint count; // количество используемых источников (должно быть <= MAX_LIGHTS) + static LightData data[MAX_LIGHTS]; // Массив данных по источникам света + static Light lights[MAX_LIGHTS]; // Массив источников-узлов сцены +}; + +// Класс направленного источника освещения +class Sun +{ + public: + static Sun& get(); // Доступ к синглтону + static void upload(UBO& sun_data); // Загрузка данных об источнике в буфер + + const glm::vec3& c_direction() const; // Константный доступ к направлению лучей источника + glm::vec3& e_direction(); // Неконстантная ссылка для изменений направления лучей источника + + const glm::vec3& c_color() const; // Константный доступ к цвету + glm::vec3& e_color(); // Неконстантная ссылка для изменений цвета + + private: + Sun(const glm::vec3 &direction = glm::vec3(0.0f, 1.0f, 0.0f), const glm::vec3 &color = glm::vec3(0.4f, 0.4f, 0.4f)); + + alignas(16) glm::vec3 direction; // Направление лучей источника + alignas(16) glm::vec3 color; // Цвет + alignas(16) glm::mat4 vp[CAMERA_CASCADE_COUNT]; // Матрица вида-проекции источника + + void recalcVP(); // Пересчитывает по необходимости матрицу вида-проекции + + static Sun instance; // Экземпляр синглтона + static bool uploadReq; // Необходимость загрузки в следствии изменений +}; + +// Данные для SSAO +struct SSAO_data +{ + float radius = 0.05f; + float bias = 0.025f; + int size = MAX_SSAO; + alignas(16) glm::vec2 scale; + glm::vec3 samples[MAX_SSAO]; +}; + +#endif // LIGHTS_H diff --git a/include/Model.h b/include/Model.h new file mode 100644 index 0000000..3d3ae18 --- /dev/null +++ b/include/Model.h @@ -0,0 +1,123 @@ +#ifndef MODEL_H +#define MODEL_H + +#include "Buffers.h" +#include "Texture.h" +#include "Shader.h" + +#include +#include +#include + +#include + +class Model genShpere(float radius, int sectorsCount, class Node* parent = NULL); // Генерирует сферу заданного радиуса с определенным количеством сегментов + +void calc_tb(const GLuint* indices, const int indices_count, const glm::vec3* verteces, const glm::vec2* texCords, glm::vec3* tangent, glm::vec3* bitangent); // Расчет касательных и бикасательных векторов + +// Класс узла сцены +class Node +{ + public: + Node(Node* parent = NULL); // Конструктор с заданным родителем (по умолчанию NULL) + Node(const Node& copy); // Конструктор копирования + Node& operator=(const Node& other); // Оператор присваивания + virtual ~Node(); + + void setParent(Node * parent); // Устанавливает родителя для узла + + virtual const glm::mat4& getTransformMatrix(); // Возвращает матрицу трансформации модели + bool isChanged(); // Возвращает необходимость пересчета матрицы трансформации + + const glm::vec3& c_position() const; // Константный доступ к позиции + const glm::quat& c_rotation() const; // Константный доступ к повороту + const glm::vec3& c_scale() const; // Константный доступ к масштабированию + virtual glm::vec3& e_position(); // Неконстантная ссылка для изменений позиции + virtual glm::quat& e_rotation(); // Неконстантная ссылка для изменений поворота + virtual glm::vec3& e_scale(); // Неконстантная ссылка для изменений масштабирования + + Node* getParent(); // Возвращает указатель на родителя + const std::vector& getChildren() const; // Возвращает ссылку на вектор дочерних узлов + + protected: + Node *parent; // Родительский узел + std::vector children; // Узлы-потомки !Не должны указывать на NULL! + + glm::vec3 position; // позиция модели + glm::quat rotation; // поворот модели + glm::vec3 scale; // масштабирование модели + + bool changed; // Флаг необходимости пересчета матрицы трансформации + glm::mat4 transform; // Матрица трансформации модели + bool parent_changed; // Флаг изменений у родителя - необходимость пересчета итоговой трансформации + glm::mat4 result_transform; // Итоговая трансформация с учетом родительской + + virtual void recalcMatrices(); // Метод пересчета матрицы трансформации по необходимости, должен сбрасывать флаг changed + void invalidateParent(); // Проход потомков в глубину с изменением флага parent_changed +}; + +// Материал модели +struct Material +{ + alignas(16) glm::vec3 base_color; // Базовый цвет материала + float roughness; // Шероховатость поверхности + float metallic; // Металличность поверхности + float specular; // Интенсивность блика диэлектриков + alignas(16) glm::vec3 emitted; // Излучаемый поверхностью свет + int normalmapped; // Использование карт нормалей + int parallaxmapped; // Использование параллакса + int displacementmapped; // Использование карт высот для сдвига вершин + // Значения по умолчанию + Material() : base_color(0.8f), roughness(0.5f), metallic(0.0f), specular(0.5f), emitted(0.0f), normalmapped(false), parallaxmapped(false), displacementmapped(false) { }; +}; + +// Идентификатор модели +struct ID +{ + GLuint64 value = 0; // Идентификатор + GLuint etc = 0; // Дополнительная информация +}; + +// Класс модели +class Model : public Node +{ + public: + Model(Node *parent = NULL); // Конструктор по умолчанию + Model(const Model& copy); // Конструктор копирования + Model& operator=(const Model& other); // Оператор присваивания + virtual ~Model(); + + void render(); // Вызов отрисовки без uniform-данных + void render(ShaderProgram &shaderProgram, UBO &material_buffer); // Вызов отрисовки + + 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 load_tangent(glm::vec3* tangent, GLuint count); // Загрузка касательных векторов в буфер + void load_bitangent(glm::vec3* bitangent, GLuint count); // Загрузка бикасательных векторов в буфер + void set_index_range(size_t first_byteOffset, size_t count, size_t type = GL_UNSIGNED_INT); // Ограничение диапазона из буфера индексов + void set_texture(Texture& texture); // Привязка текстуры к модели + void setBO(int attribute, BO & bo); // Замена вершинного буфера по номеру его привязки + void setIndicesBO(BO & data); // Замена индексного буфера + + Material material; // Материал модели + + ID id; // ID модели + private: + VAO vao; + BO vertex_vbo, index_vbo; // вершинный и индексный буферы + BO normals_vbo, texCoords_vbo; // буферы с нормалями и текстурными координатами + BO tangent_vbo, bitangent_vbo; // буферы с касательными и бикасательными векторами + GLuint verteces_count; // Количество вершин + size_t first_index_byteOffset, indices_count, indices_datatype; // Сдвиг в байтах для первого, количество индексов и тип данных индексов + Texture texture_albedo; // Текстура альбедо (цвет поверхности) + Texture texture_roughness; // Текстура шероховатостей + Texture texture_metallic; // Текстура металличности + Texture texture_specular; // Текстура интенсивности блика диэлектриков + Texture texture_emitted; // Текстура излучаемого света + Texture texture_heights; // Текстура высот + Texture texture_normals; // Текстура нормалей +}; + +#endif // MODEL_H diff --git a/include/Scene.h b/include/Scene.h new file mode 100644 index 0000000..2d59f7e --- /dev/null +++ b/include/Scene.h @@ -0,0 +1,50 @@ +#ifndef SCENE_H +#define SCENE_H + +#include + +#include "Model.h" +#include "Camera.h" +#include "Animation.h" + +#include +#include +#include +#include + +#define DEFAULT_MTL_DIR "./" +class Scene loadOBJtoScene(const char* filename, const char* mtl_directory = DEFAULT_MTL_DIR, const char* texture_directory = DEFAULT_MTL_DIR); +class Scene loadGLTFtoScene(std::string filename); + +// Класс сцены +class Scene +{ + public: + Scene(); // Конструктор пустой сцены + Scene(const Scene ©); // Конструктор копирования + Scene& operator=(const Scene& other); // Оператор присваивания + + void render(ShaderProgram &shaderProgram, UBO &material_buffer, bool recalc_animations = false); // Рендер сцены + + void set_group_id(GLuint64 id, GLuint etc = 0); // Изменение флага записи идентификатора для всех моделей + + Node root; // Корневой узел + + // Списки объектов, выступающих узлами + std::list nodes; // Список пустых узлов + std::list models; // Список моделей для рендера + std::list cameras; // Список камер + + std::vector animations; // Список анимаций + std::map animation_names; // Имя анимации - индекс + protected: + void rebuld_tree(const Scene& from); // Перестройка дерева после копирования или присваивания + template + void rebuild_Nodes_list(T& nodes, const Scene& from); // Перестройка узлов выбранного списка + template + void move_parent(Node& for_node, const std::list& from_nodes, std::list& this_nodes); // Сдвигает родителя узла между двумя списками при условии его принадлежности к оригинальному + template + void move_animation_target(Node*& target, const std::list& from_nodes, std::list& this_nodes); // Перестройка узлов анимации +}; + +#endif // SCENE_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/TRS.h b/include/TRS.h new file mode 100644 index 0000000..864f617 --- /dev/null +++ b/include/TRS.h @@ -0,0 +1,45 @@ +#ifndef TRS_H +#define TRS_H + +#define T_SENSITIVITY 0.001f +#define R_SENSITIVITY 0.01f +#define S_SENSITIVITY 0.00001f + +#include "Scene.h" + +// Интерфейс инструмента +class TRS +{ + public: + void render(GLuint64 selectedID, ShaderProgram &shaderProgram, UBO &material_buffer); // Рендер инструмента нужного типа для выбранного объекта + virtual void process(GLuint64 selectedID, GLuint etc, const glm::vec4& dpos) = 0; // Взаимодействие с инструментом + protected: + void init_etc(); // Инициализирует дополнительную информацию модели + Scene tool; // Модель +}; + +// Инструмент трансформации +class Transform : public TRS +{ + public: + Transform(); + virtual void process(GLuint64 selectedID, GLuint etc, const glm::vec4& dpos); // Взаимодействие с инструментом +}; + +// Инструмент поворота +class Rotate : public TRS +{ + public: + Rotate(); + virtual void process(GLuint64 selectedID, GLuint etc, const glm::vec4& drot); // Взаимодействие с инструментом +}; + +// Инструмент масштабирования +class Scale : public TRS +{ + public: + Scale(); + virtual void process(GLuint64 selectedID, GLuint etc, const glm::vec4& dscale); // Взаимодействие с инструментом +}; + +#endif // TRS_H \ No newline at end of file diff --git a/include/Texture.h b/include/Texture.h new file mode 100644 index 0000000..fcc365e --- /dev/null +++ b/include/Texture.h @@ -0,0 +1,95 @@ +#ifndef TEXTURE_H +#define TEXTURE_H + +#include + +#include +#include + +enum TexType { + TEX_ALBEDO, + TEX_ROUGHNESS, + TEX_METALLIC, + TEX_SPECULAR, + TEX_EMITTED, + TEX_HEIGHTS, + TEX_NORMAL, + TEX_AVAILABLE_COUNT +}; + +// Абстрактный класс базовой текстуры +class BaseTexture +{ + public: + ~BaseTexture(); + virtual void use() = 0; // Привязка текстуры + static void disable(GLuint type); // Отвязка текстуры по типу + GLuint getType(); // Возвращает тип текстуры + void setType(GLuint type); // Задает тип текстуры + protected: + GLuint handler; // Дескриптор текстуры + GLuint type; // Тип текстуры, соответствует её слоту + static std::map filename_handler; // Получение дескриптора текстуры по её имени + static std::map handler_count; // Получение количества использований по дескриптору текстуры (Shared pointer) +}; + +// Класс 2D текстуры +class Texture : public BaseTexture +{ + public: + Texture(GLuint type = TEX_AVAILABLE_COUNT, const std::string& filename = ""); // Загрузка текстуры с диска или использование "пустой" + Texture(GLuint width, GLuint height, GLuint attachment, GLuint texType = TEX_ALBEDO, GLint internalformat = GL_RGBA, GLint format = GL_RGBA, GLenum dataType = GL_FLOAT); // Конструктор текстуры заданного размера для использования в буфере + Texture(GLuint width, GLuint height, void* data, GLuint texType = TEX_ALBEDO, GLint internalformat = GL_RGBA, GLint format = GL_RGBA, GLenum dataType = GL_FLOAT); // Конструктор текстуры заданного размера без привязки к буферу с загрузкой пикселей по указателю + Texture(const Texture& other); // Конструктор копирования + + Texture& operator=(const Texture& other); // Оператор присваивания + + void reallocate(GLuint width, GLuint height, GLuint texType = TEX_ALBEDO, GLint internalformat = GL_RGBA, GLint format = GL_RGBA, GLenum dataType = GL_FLOAT); // Пересоздает текстуру для имеющегося дескриптора + + virtual void use(); // Привязка текстуры +}; + +// Класс 3D текстуры +class TextureArray : public BaseTexture +{ + public: + TextureArray(GLuint levels, GLuint width, GLuint height, GLuint attachment, GLuint texType = TEX_ALBEDO, GLint internalformat = GL_RGBA, GLint format = GL_RGBA, GLenum dataType = GL_FLOAT); // Конструктор текстуры заданного размера для использования в буфере + TextureArray(const TextureArray& other); // Конструктор копирования + + TextureArray& operator=(const TextureArray& other); // Оператор присваивания + + void reallocate(GLuint levels, GLuint width, GLuint height, GLuint texType = TEX_ALBEDO, GLint internalformat = GL_RGBA, GLint format = GL_RGBA, GLenum dataType = GL_FLOAT); // Пересоздает текстуру для имеющегося дескриптора + + virtual void use(); // Привязка текстуры +}; + +// Класс кубической текстуры +class TextureCube : public BaseTexture +{ + public: + TextureCube(GLuint type = TEX_AVAILABLE_COUNT, const std::string (&filename)[6] = {""}); // Загрузка текстуры с диска или использование "пустой" + TextureCube(GLuint width, GLuint height, GLuint attachment, GLuint texType = TEX_ALBEDO, GLint internalformat = GL_RGBA, GLint format = GL_RGBA, GLenum dataType = GL_FLOAT); // Конструктор текстуры заданного размера для использования в буфере + TextureCube(const TextureCube& other); // Конструктор копирования + + TextureCube& operator=(const TextureCube& other); // Оператор присваивания + + void reallocate(GLuint width, GLuint height, GLuint texType = TEX_ALBEDO, GLint internalformat = GL_RGBA, GLint format = GL_RGBA, GLenum dataType = GL_FLOAT); // Пересоздает текстуру для имеющегося дескриптора + + virtual void use(); // Привязка текстуры +}; + +// Класс 3D кубической текстуры +class TextureCubeArray : public BaseTexture +{ + public: + TextureCubeArray(GLuint levels, GLuint width, GLuint height, GLuint attachment, GLuint texType = TEX_ALBEDO, GLint internalformat = GL_RGBA, GLint format = GL_RGBA, GLenum dataType = GL_FLOAT); // Конструктор текстуры заданного размера для использования в буфере + TextureCubeArray(const TextureCubeArray& other); // Конструктор копирования + + TextureCubeArray& operator=(const TextureCubeArray& other); // Оператор присваивания + + void reallocate(GLuint levels, GLuint width, GLuint height, GLuint texType = TEX_ALBEDO, GLint internalformat = GL_RGBA, GLint format = GL_RGBA, GLenum dataType = GL_FLOAT); // Пересоздает текстуру для имеющегося дескриптора + + virtual void use(); // Привязка текстуры +}; + +#endif // TEXTURE_H diff --git a/shaders/bulb.frag b/shaders/bulb.frag new file mode 100644 index 0000000..97f9259 --- /dev/null +++ b/shaders/bulb.frag @@ -0,0 +1,43 @@ +#version 420 core + +layout(std140, binding = 1) uniform Material +{ + vec3 base_color; + float roughness; + float metallic; + float specular; + vec3 emitted; + bool normalmapped; + bool parallaxmapped; + bool displacementmapped; +}; + +in vec3 pos_local; + +layout(std140, binding = 4) uniform gamma +{ + float inv_gamma; +}; + +layout (location = 1) out vec3 gNormal; +layout (location = 4) out uvec3 gID; +layout (location = 5) out vec3 gEmittedColor; + +uniform float angle; +uniform vec3 direction; + +uniform uvec3 ID = uvec3(0); + +void main() +{ + float cosA = dot(normalize(pos_local), normalize(direction)); + if (degrees(acos(cosA)) <= angle) + gEmittedColor = pow(base_color, vec3(inv_gamma)); + else + discard; + + gNormal = vec3(0); + + // Сохранение идентификатора объекта + gID = ID; +} \ No newline at end of file diff --git a/shaders/bulb.vert b/shaders/bulb.vert new file mode 100644 index 0000000..e84ca7e --- /dev/null +++ b/shaders/bulb.vert @@ -0,0 +1,20 @@ +#version 420 core + +layout(location = 0) in vec3 pos; + +layout(std140, binding = 0) uniform Camera +{ + mat4 projection; + mat4 view; + vec3 position; +} camera; + +uniform mat4 model; + +out vec3 pos_local; + +void main() +{ + pos_local = pos; + gl_Position = camera.projection * camera.view * model * vec4(pos, 1.0); +} \ No newline at end of file diff --git a/shaders/empty.frag b/shaders/empty.frag new file mode 100644 index 0000000..0f8d755 --- /dev/null +++ b/shaders/empty.frag @@ -0,0 +1,6 @@ +#version 330 core + +void main() +{ + +} \ No newline at end of file diff --git a/shaders/gshader.frag b/shaders/gshader.frag new file mode 100644 index 0000000..786423b --- /dev/null +++ b/shaders/gshader.frag @@ -0,0 +1,126 @@ +#version 420 core + +layout(std140, binding = 1) uniform Material +{ + vec3 base_color; + float roughness; + float metallic; + float specular; + vec3 emitted; + bool normalmapped; + bool parallaxmapped; + bool displacementmapped; +}; + +layout (location = 0) out vec3 gPosition; +layout (location = 1) out vec3 gNormal; +layout (location = 2) out vec3 gBaseColor; +layout (location = 3) out vec3 gRMS; +layout (location = 4) out uvec3 gID; +layout (location = 5) out vec3 gEmittedColor; + +in vec3 vertex; // Позиция вершины в пространстве +in vec3 N; // Нормаль трансформированноая +in vec2 texCoord; // Текстурные координаты +in vec3 T; // Касательный вектор +in vec3 B; // Бикасательный вектор +in vec3 view; // Вектор от поверхности к камере + +uniform sampler2D tex_albedo; +uniform sampler2D tex_roughness; +uniform sampler2D tex_metallic; +uniform sampler2D tex_specular; +uniform sampler2D tex_emitted; +uniform sampler2D tex_heights; +uniform sampler2D tex_normal; + +uniform float parallax_heightScale = 0.1; + +uniform uvec3 ID = uvec3(0); + +void main() +{ + // Сформируем TBN матрицу + mat3 TBN = mat3(T, B, N); + // Перевод вектора в касательное пространство + vec3 viewTBN = normalize(transpose(TBN) * view); + // Измененные текстурные координаты + vec2 new_texCoord = texCoord; + + // Сохранение позиции фрагмента в G-буфере + gPosition = vertex; + + if (parallaxmapped) + { + // Число слоев + float layersCount = 32; + // Вычислим размер каждого слоя + float layerDepth = 1.0 / layersCount; + // Глубина текущего слоя + float currentLayerDepth = 0.0; + // Величина сдвига между слоями + vec2 deltaTexCoords = (parallax_heightScale * viewTBN.xy / viewTBN.z) / layersCount; + + // Переменные для вычислений + vec2 currentTexCoords = texCoord; + float currentDepthMapValue = 1.0 - texture(tex_heights, currentTexCoords).r; + + // Пока глубина текущего слоя меньше текущего значения глубины из текстуры + while(currentLayerDepth < currentDepthMapValue) + { + // Сдвигаем координаты + currentTexCoords -= deltaTexCoords; + // Обновляем значение глубины из текстуры + currentDepthMapValue = 1.0 - texture(tex_heights, currentTexCoords).r; + // Сдвигаем глубину на следующий слой + currentLayerDepth += layerDepth; + } + + // Получим значение текстурных координат с предыдущего шага + vec2 prevTexCoords = currentTexCoords + deltaTexCoords; + + // Значения глубины до и после пересечения + float afterDepth = currentDepthMapValue - currentLayerDepth; + float beforeDepth = 1.0 - texture(tex_heights, prevTexCoords).r - currentLayerDepth + layerDepth; + + // Интерполяция текстурных координат + float weight = afterDepth / (afterDepth - beforeDepth); + new_texCoord = prevTexCoords * weight + currentTexCoords * (1.0 - weight); + + // Проверка диапазона [0;1] + if(new_texCoord.x > 1.0 || new_texCoord.y > 1.0 || new_texCoord.x < 0.0 || new_texCoord.y < 0.0) + discard; + } + + // Сохранение нормали в G-буфере + gNormal = N; + // Если используется карта нормалей + if (normalmapped) + { + // Получим значение из карты нормалей и приведем их к диапазону [-1;1] + gNormal = texture(tex_normal, new_texCoord).rgb * 2 - 1.0f; + gNormal = normalize(TBN * gNormal); // Из касательного пространства в мировые координаты + } + + // Сохранение базового цвета + gBaseColor.rgb = base_color.r<0?texture(tex_albedo, new_texCoord).rgb:base_color; + // Если используется двухканальная текстура + if (roughness < -1) + { + // Сохранение шероховатости и металличности + gRMS.rg = texture(tex_metallic, new_texCoord).bg; + } + else + { + // Сохранение шероховатости + gRMS.r = roughness<0?texture(tex_roughness, new_texCoord).r:roughness; + // Сохранение металличности + gRMS.g = metallic<0?texture(tex_metallic, new_texCoord).r:metallic; + } + // Сохранение интенсивности блика диэлектриков + gRMS.b = specular<0?texture(tex_specular, new_texCoord).r:specular; + // Сохранение идентификатора объекта + gID = ID; + // Сохранение излучаемого света + gEmittedColor.rgb = emitted.r<0?texture(tex_emitted, new_texCoord).rgb:emitted; +} \ No newline at end of file diff --git a/shaders/gshader.vert b/shaders/gshader.vert new file mode 100644 index 0000000..1b070e3 --- /dev/null +++ b/shaders/gshader.vert @@ -0,0 +1,61 @@ +#version 420 core + +layout(location = 0) in vec3 pos; +layout(location = 1) in vec2 inTexCoord; +layout(location = 2) in vec3 normals; +layout(location = 3) in vec3 tangent; +layout(location = 4) in vec3 bitangent; + +layout(std140, binding = 0) uniform Camera +{ + mat4 projection; + mat4 view; + vec3 position; +} camera; + +layout(std140, binding = 1) uniform Material +{ + vec3 base_color; + float roughness; + float metallic; + float specular; + vec3 emitted; + bool normalmapped; + bool parallaxmapped; + bool displacementmapped; +}; + +uniform sampler2D tex_heights; +uniform float displacement_heightScale = 0.1; + +uniform mat4 model; + +out vec3 vertex; // Позиция вершины в пространстве +out vec3 N; // Нормаль трансформированноая +out vec2 texCoord; // Текстурные координаты +out vec3 T; // Касательный вектор +out vec3 B; // Бикасательный вектор +out vec3 view; // Вектор от поверхности к камере + +void main() +{ + vec4 P = model * vec4(pos, 1.0); // трансформация вершины + vertex = P.xyz; + + N = normalize(mat3(model) * normals); // трансформация нормали + + texCoord = inTexCoord; // Текстурные координаты + + T = normalize(mat3(model) * tangent); + B = normalize(mat3(model) * bitangent); + + view = camera.position - vertex; + + if (displacementmapped) + { + float height = texture(tex_heights, texCoord).r * displacement_heightScale; + P.xyz += mat3(T, B, N) * vec3(0, 0, height); + } + + gl_Position = camera.projection * camera.view * P; +} \ No newline at end of file diff --git a/shaders/lighting.frag b/shaders/lighting.frag new file mode 100644 index 0000000..352e0cc --- /dev/null +++ b/shaders/lighting.frag @@ -0,0 +1,300 @@ +#version 420 core + +in vec2 texCoord; + +layout(std140, binding = 0) uniform Camera +{ + mat4 projection; + mat4 view; + vec3 position; +} camera; + +struct LightData +{ + vec3 position; + vec3 color; + vec3 attenuation; + vec4 direction_angle; + mat4 vp[6]; +}; + +layout(std140, binding = 2) uniform Light +{ + LightData data[64]; + int count; +} light_f; + +layout(std140, binding = 3) uniform Sun +{ + vec3 direction; + vec3 color; + mat4 vp[4]; +} sun; + +uniform float camera_cascade_distances[4]; // Размер массива должен соответствовать количеству каскадов + +uniform sampler2D gPosition; +uniform sampler2D gNormal; +uniform sampler2D gBaseColor; +uniform sampler2D gRMS; +uniform sampler2D gEmittedColor; +uniform sampler2DArray sunShadowDepth; +uniform samplerCubeArray pointShadowDepth; +uniform sampler2D ssao; +uniform usampler2D gID; +uniform samplerCube reflections; + +uniform uvec3 selectedID; + +layout(std140, binding = 4) uniform gamma +{ + float inv_gamma; +}; + +out vec4 color; + +const float PI = 3.14159265359; + +float D(vec3 H, vec3 N, float a) +{ + float tmp = max(dot(N, H), 0); + tmp = tmp*tmp*(a*a-1)+1; + return a*a/(PI * tmp*tmp); +} + +float G_Sclick_Beckmann(float NDotDir, float a) +{ + float tmp = (a+1)*(a+1) / 8; + return 1 / (NDotDir * (1 - tmp) + tmp); +} + +float G_Smith(float LDotN, float CamDotN, float a) +{ + return G_Sclick_Beckmann(LDotN, a) * G_Sclick_Beckmann(CamDotN, a); +} + +vec3 F(vec3 H, vec3 Cam_vertex, float metallic, float specular, vec3 base_color) +{ + vec3 F0 = mix(vec3(0.08 * specular), base_color, metallic); + return F0 + (1 - F0) * pow(1 - max(dot(H, Cam_vertex),0), 5); +} + +float G_Sclick_Beckmann_HS(float NDotDir, float a) +{ + float tmp = (a+1)*(a+1) / 2; + return 1 / (NDotDir * (1 - tmp) + tmp); +} + +float G_Smith_HS(float LDotN, float CamDotN, float a) +{ + return G_Sclick_Beckmann_HS(LDotN, a) * G_Sclick_Beckmann_HS(CamDotN, a); +} + +vec3 F_roughness(vec3 H, vec3 Cam_vertex, float metallic, float roughness, float specular, vec3 base_color) +{ + vec3 F0 = mix(vec3(0.08 * specular), base_color, metallic); + return F0 + (max(vec3(1.0 - roughness), F0) - F0) * pow(1 - max(dot(H, Cam_vertex),0), 5); +} + +void main() +{ + // Получим данные из текстур буфера + vec3 fragPos = texture(gPosition, texCoord).rgb; + vec3 N = normalize(texture(gNormal, texCoord).rgb); + vec3 base_color = texture(gBaseColor, texCoord).rgb; + float roughness = texture(gRMS, texCoord).r; + float metallic = texture(gRMS, texCoord).g; + float specular = texture(gRMS, texCoord).b; + float ssao_value = texture(ssao, texCoord).r; + + // Переменные используемые в цикле: + vec3 L_vertex; // Расположение источника относительно фрагмента + float L_distance; // Расстояние от поверхности до источника + vec3 Cam_vertex = normalize(camera.position - fragPos); // Расположение камеры относительно фрагмента + vec3 ks; // Интенсивность зеркального отражения + vec3 fd, fs; // Диффузное и зеркальное отражения + vec3 H; // Вектор половины пути + float attenuation; // Угасание с учетом расстояния + float acosA; // Косинус между вектором от поверхности к источнику и обратным направлением источника + float intensity; // Интенсивность для прожектора + vec3 fragPosLightSpace; // Фрагмент в пространстве источника + float shadowValue; // Значение затененности + vec2 texelSize = 1.0 / textureSize(sunShadowDepth, 0).xy; // Размер текселя текстуры теней + int x, y, z; // Счетчик для PCF + float pcfDepth; // Глубина PCF + float cubemap_offset = 0.05f; // Отступ в текстурных координатах для PCF + float cubemap_depth; // Дистанция между фрагментом и источником в диапазоне [0;1] + + vec4 fragPosCamSpace = camera.view * vec4(fragPos, 1); // Фрагмент в пространстве камеры + int cascade_index; // Индекс текущего каскада для вычисления теней + float CamDotN = dot(Cam_vertex,N); // Скалярное произведение вектора на камеру и нормали + float LDotN; // Скалярное произведение вектора на источник и нормали + + // Определение индекса каскада в который попадает фрагмент (цикл на 1 меньше чем кол-во каскадов) + for (cascade_index = 0; cascade_index < 3; cascade_index++) + if (abs(fragPosCamSpace.z) < camera_cascade_distances[cascade_index]) + break; + + // Фоновая освещенность + color = vec4(texture(gEmittedColor, texCoord).rgb, 1); + + // Если у модели есть нормаль + if (length(N) > 0) + { + // Отражения на основании карт отражений + vec3 reflectedVec = reflect(-Cam_vertex, N); + vec3 reflectedColor = textureLod(reflections, reflectedVec, 6*roughness).rgb; + + LDotN = dot(reflectedVec, N); + + // Вектор половины пути + H = normalize(reflectedVec + Cam_vertex); + + // Зеркальное отражение + ks = F_roughness(N, Cam_vertex, metallic, roughness, specular, base_color); + fs = ks * min(D(H, N, roughness*roughness) / 4, 1) * G_Smith_HS(LDotN, CamDotN, roughness*roughness); + + // Результирующий цвет с учетом солнца + color.rgb += fs * reflectedColor * LDotN; + + + // Расчет солнца, если его цвет не черный + if (length(sun.color) > 0) + { + // Расположение фрагмента в координатах теневой карты + fragPosLightSpace = (sun.vp[cascade_index] * vec4(fragPos, 1.0)).xyz; + // Переход от [-1;1] к [0;1] + fragPosLightSpace = (fragPosLightSpace + vec3(1.0)) / 2; + // Сдвиг для решения проблемы акне + fragPosLightSpace.z -= max(0.05 * (1.0 - dot(N, sun.direction)), 0.005); + // Проверка PCF + shadowValue = 0.0; + texelSize = 1.0 / textureSize(sunShadowDepth, 0).xy; // Размер текселя текстуры теней + for(x = -1; x <= 1; ++x) + { + for(y = -1; y <= 1; ++y) + { + pcfDepth = texture(sunShadowDepth, vec3(fragPosLightSpace.xy + vec2(x, y) * texelSize, cascade_index)).r; + shadowValue += fragPosLightSpace.z > pcfDepth ? 1.0 : 0.0; + } + } + shadowValue /= 9.0; + // Рассчитываем освещенность, если значение тени меньше 1 + if (shadowValue < 1.0) + { + // Данные об источнике относительно фрагмента + L_vertex = normalize(sun.direction); + LDotN = dot(L_vertex,N); + if (LDotN > 0) + { + // Вектор половины пути + H = normalize(L_vertex + Cam_vertex); + + // Зеркальное отражение + ks = F(H, Cam_vertex, metallic, specular, base_color); + fs = ks * min(D(H, N, roughness*roughness) / 4, 1) * G_Smith(LDotN, CamDotN, roughness*roughness); + + // Диффузное отражение + fd = (1 - length(ks)/length(base_color)) * base_color; + + // Результирующий цвет с учетом солнца + color.rgb += (fd + fs) * sun.color * LDotN * (1.0 - shadowValue); + } + } + } + + // Цикл по источникам света + int i; + for (i = 0; i < light_f.count; i++) + { + // Обнулим значение тени + shadowValue = 0; + // Позиция фрагмента относительно источника + fragPosLightSpace = fragPos - light_f.data[i].position; + // Дистанция между фрагментом и источником в диапазоне [0;1] + cubemap_depth = length(fragPosLightSpace) / light_f.data[i].attenuation.r; + // Сдвиг для решения проблемы акне + cubemap_depth -= max(0.05 * (1.0 - dot(N, sun.direction)), 0.005); + for(x = -1; x <= 1; ++x) + { + for(y = -1; y <= 1; ++y) + { + for(z = -1; z <= 1; ++z) + { + // Значение из кубической текстуры с учетом источника (i) + pcfDepth = texture(pointShadowDepth, vec4(fragPosLightSpace + vec3(x, y, z)*cubemap_offset, i)).r; + if(cubemap_depth > pcfDepth) + shadowValue += 1.0; + } + } + } + shadowValue /= (27); + if (shadowValue < 1.0) + { + // Данные об источнике относительно фрагмента + L_vertex = light_f.data[i].position - fragPos; + // Расстояние от поверхности до источника + L_distance = length(L_vertex); + + // Проверка на дистанцию + if (L_distance < light_f.data[i].attenuation.r) + { + // Нормирование вектора + L_vertex = normalize(L_vertex); + // арккосинус между вектором от поверхности к источнику и обратным направлением источника + acosA = degrees(acos(dot(-L_vertex, normalize(light_f.data[i].direction_angle.rgb)))); + // Если угол меньше угла источника или угол источника минимален, то считаем освещенность + if(acosA <= light_f.data[i].direction_angle.a) + { + LDotN = dot(L_vertex,N); + if (LDotN > 0) + { + // Вектор половины пути + H = normalize(L_vertex + Cam_vertex); + + // Угасание с учетом расстояния + attenuation = 1 / (1 + light_f.data[i].attenuation[1] * L_distance + light_f.data[i].attenuation[2] * L_distance * L_distance); + + // Зеркальное отражение + ks = F(H, Cam_vertex, metallic, specular, base_color); + fs = ks * min(D(H, N, roughness*roughness) / 4, 1) * G_Smith(LDotN, CamDotN, roughness*roughness); + + // Диффузное отражение + fd = (1 - length(ks)/length(base_color)) * base_color; + + // Если источник - прожектор, то добавим смягчение + if (light_f.data[i].direction_angle.a < 180) + { + intensity = clamp((light_f.data[i].direction_angle.a - acosA) / 5, 0.0, 1.0); + fd *= intensity; + fs *= intensity; + } + + color.rgb += (fd + fs) * light_f.data[i].color * attenuation * LDotN * (1.0 - shadowValue); + } + } + } + } + } + } + + // Применение гамма-коррекции + color.rgb = pow(color.rgb * ssao_value, vec3(inv_gamma)); + + vec3 ID = texture(gID, texCoord).rgb; + // Обводка выбранного объекта + if (length(selectedID.rg) > 0 && selectedID.rg == ID.rg && ID.b == 0) + { + int border_width = 3; + vec2 size = 1.0f / textureSize(gID, 0); + for (int i = -border_width; i <= +border_width; i++) + for (int j = -border_width; j <= +border_width; j++) + { + if (i == 0 && j == 0) + continue; + + if (texture(gID, texCoord + vec2(i, j) * size).rg != selectedID.rg) + color.rgb = vec3(1.0); + } + } +} \ No newline at end of file diff --git a/shaders/point_shadow.frag b/shaders/point_shadow.frag new file mode 100644 index 0000000..32b41d8 --- /dev/null +++ b/shaders/point_shadow.frag @@ -0,0 +1,17 @@ +#version 330 core + +in vec4 FragPos; +in vec3 lightPos; +in float radius; + +void main() +{ + // Расстояние между источником и фрагментом + float lightDistance = length(FragPos.xyz - lightPos); + + // Приведение к диапазону [0;1] + lightDistance = lightDistance / radius; + + // Замена значения глубины + gl_FragDepth = lightDistance; +} \ No newline at end of file diff --git a/shaders/point_shadow.geom b/shaders/point_shadow.geom new file mode 100644 index 0000000..c6e8115 --- /dev/null +++ b/shaders/point_shadow.geom @@ -0,0 +1,38 @@ +#version 420 core +layout (triangles, invocations = 6) in; // здесь invocations соответствует числу сторон кубической карты теней +layout (triangle_strip, max_vertices=18) out; // здесь max_vertices = 3 вершины * 6 вызовов на стороны куба + +struct LightData +{ + vec3 position; + vec3 color; + vec3 attenuation; + vec4 direction_angle; + mat4 vp[6]; +}; + +layout(std140, binding = 2) uniform Light +{ + LightData data[64]; + int count; +} light_g; + +uniform int light_i; + +out vec4 FragPos; +out vec3 lightPos; +out float radius; + +void main() +{ + for(int i = 0; i < 3; ++i) + { + FragPos = gl_in[i].gl_Position; + lightPos = light_g.data[light_i].position; + radius = light_g.data[light_i].attenuation.r; + gl_Position = light_g.data[light_i].vp[gl_InvocationID] * gl_in[i].gl_Position; + gl_Layer = gl_InvocationID + light_i*6; + EmitVertex(); + } + EndPrimitive(); +} \ No newline at end of file diff --git a/shaders/quad.vert b/shaders/quad.vert new file mode 100644 index 0000000..fb0683c --- /dev/null +++ b/shaders/quad.vert @@ -0,0 +1,11 @@ +#version 420 core + +layout(location = 0) in vec3 pos; + +out vec2 texCoord; + +void main() +{ + gl_Position = vec4(pos, 1.0); + texCoord = (pos.xy + vec2(1.0)) / 2; // Переход от [-1;1] к [0;1] +} \ No newline at end of file diff --git a/shaders/skybox.frag b/shaders/skybox.frag new file mode 100644 index 0000000..1b291e6 --- /dev/null +++ b/shaders/skybox.frag @@ -0,0 +1,17 @@ +#version 420 core +out vec4 FragColor; + +in vec3 TexCoords; + +uniform samplerCube skybox; + +layout(std140, binding = 4) uniform gamma +{ + float inv_gamma; +}; + +void main() +{ + FragColor.rgb = pow(texture(skybox, TexCoords).rgb, vec3(inv_gamma)); + gl_FragDepth = 0.9999f; +} \ No newline at end of file diff --git a/shaders/skybox.vert b/shaders/skybox.vert new file mode 100644 index 0000000..d2fe73e --- /dev/null +++ b/shaders/skybox.vert @@ -0,0 +1,17 @@ +#version 420 core +layout (location = 0) in vec3 pos; + +out vec3 TexCoords; + +layout(std140, binding = 0) uniform Camera +{ + mat4 projection; + mat4 view; + vec3 position; +} camera; + +void main() +{ + TexCoords = pos; + gl_Position = camera.projection * mat4(mat3(camera.view)) * vec4(pos, 1.0); +} \ No newline at end of file diff --git a/shaders/ssao.frag b/shaders/ssao.frag new file mode 100644 index 0000000..548aab6 --- /dev/null +++ b/shaders/ssao.frag @@ -0,0 +1,62 @@ +#version 420 core + +in vec2 texCoord; + +out float occlusion; + +uniform sampler2D gPosition; +uniform sampler2D gNormal; +uniform sampler2D noise; + +layout(std140, binding = 0) uniform Camera +{ + mat4 projection; + mat4 view; + vec3 position; +} camera; + +layout(std140, binding = 3) uniform SSAO +{ + float radius; + float bias; + int size; + vec2 scale; + vec3 samples[64]; +} ssao; + +void main() +{ + // Получим информацию из текстур для данного фрагмента по текстурным координатам + vec3 fragPos = (camera.view * vec4(texture(gPosition, texCoord).xyz, 1)).xyz; + vec3 normal = normalize(texture(gNormal, texCoord).rgb); + vec3 randomVec = normalize(texture(noise, texCoord * ssao.scale).xyz); + // Расчет TBN матрицы + vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal)); + vec3 bitangent = cross(normal, tangent); + mat3 TBN = mat3(tangent, bitangent, normal); + + float sampleDepth; // Значение глубины образца выборки + vec3 samplePos; // Выборка, ориентированная в пространстве вида камеры + vec4 sampleCoord; // Выборка, преобразованная к текстурным координатам + float rangeCheck; // Проверка диапазона + + // Проинициализируем значение счетчика и запустим цикл по выборкам + occlusion = 0; + for(int i = 0; i < ssao.size; i++) + { + samplePos = TBN * ssao.samples[i]; // в TBN-пространстве + samplePos = fragPos + samplePos * ssao.radius; // в пространстве вида камеры + + sampleCoord = camera.projection * vec4(samplePos, 1.0); + sampleCoord.xyz /= sampleCoord.w; // Деление на значение перспективы + sampleCoord.xyz = sampleCoord.xyz * 0.5 + 0.5; // Трансформация в диапазон [0.0; 1.0] + + // Получаем значение глубины по образцу выборки + sampleDepth = (camera.view * vec4(texture(gPosition, sampleCoord.xy).rgb, 1)).z; + + rangeCheck = smoothstep(0.0, 1.0, ssao.radius / abs(fragPos.z - sampleDepth)); + occlusion += (sampleDepth >= samplePos.z + ssao.bias ? 1.0 : 0.0) * rangeCheck; + } + + occlusion = 1 - (occlusion / ssao.size); +} diff --git a/shaders/ssaoBlur.frag b/shaders/ssaoBlur.frag new file mode 100644 index 0000000..dfc9571 --- /dev/null +++ b/shaders/ssaoBlur.frag @@ -0,0 +1,23 @@ +#version 330 core + +in vec2 texCoord; + +out float occlusion; + +uniform sampler2D ssao; + +void main() +{ + vec2 texelSize = 1.0 / vec2(textureSize(ssao, 0)); + vec2 offset; + occlusion = 0.0; + for (int x = -2; x < 2; x++) + { + for (int y = -2; y < 2; y++) + { + offset = vec2(x, y) * texelSize; + occlusion += texture(ssao, texCoord + offset).r; + } + } + occlusion = occlusion / (4.0 * 4.0); +} \ No newline at end of file diff --git a/shaders/sun_shadow.geom b/shaders/sun_shadow.geom new file mode 100644 index 0000000..4912b94 --- /dev/null +++ b/shaders/sun_shadow.geom @@ -0,0 +1,22 @@ +#version 420 core + +layout(triangles, invocations = 4) in; // здесь invocations должно соответствовать количеству каскадов +layout(triangle_strip, max_vertices = 3) out; + +layout(std140, binding = 3) uniform Sun +{ + vec3 direction; + vec3 color; + mat4 vp[4]; +} sun; + +void main() +{ + for (int i = 0; i < 3; ++i) + { + gl_Position = sun.vp[gl_InvocationID] * gl_in[i].gl_Position; + gl_Layer = gl_InvocationID; + EmitVertex(); + } + EndPrimitive(); +} \ No newline at end of file diff --git a/shaders/sun_shadow.vert b/shaders/sun_shadow.vert new file mode 100644 index 0000000..cbe3f11 --- /dev/null +++ b/shaders/sun_shadow.vert @@ -0,0 +1,10 @@ +#version 420 core + +layout (location = 0) in vec3 pos; + +uniform mat4 model; + +void main() +{ + gl_Position = model * vec4(pos, 1.0); +} diff --git a/shaders/tools.frag b/shaders/tools.frag new file mode 100644 index 0000000..466946c --- /dev/null +++ b/shaders/tools.frag @@ -0,0 +1,39 @@ +#version 420 core + +layout(std140, binding = 1) uniform Material +{ + vec3 base_color; + float roughness; + float metallic; + float specular; + vec3 emitted; + bool normalmapped; + bool parallaxmapped; + bool displacementmapped; +}; + +layout (location = 1) out vec3 gNormal; +layout (location = 4) out uvec3 gID; +layout (location = 5) out vec3 gEmittedColor; + +in vec3 vertex; // Позиция вершины в пространстве +in vec3 N; // Нормаль трансформированная +in vec2 texCoord; // Текстурные координаты +in vec3 T; // Касательный вектор +in vec3 B; // Бикасательный вектор +in vec3 view; // Вектор от поверхности к камере + +uniform float parallax_heightScale = 0.1; + +uniform uvec3 ID = uvec3(0); + +void main() +{ + gNormal = vec3(0); + // Сохранение базового цвета в качестве излучаемого + gEmittedColor = base_color; + // Сохранение идентификатора объекта + gID = ID; + + gl_FragDepth = 0.01 * gl_FragCoord.z; +} \ No newline at end of file diff --git a/src/Animation.cpp b/src/Animation.cpp new file mode 100644 index 0000000..9c2e725 --- /dev/null +++ b/src/Animation.cpp @@ -0,0 +1,137 @@ +#include "Animation.h" + +#include + +// Выполнить анимацию для канала с учетом времени относительно начала +void Channel::process(float dtime, ANIM_ENDINGS endings) +{ + // Если указатель на узел не пустой и есть ключевые кадры + if (target && timestamps.size() && timestamps.size() == values.size()) + { + // Если анимация зациклена + if (endings == CYCLED) + { + // Получаем общую длительность анимации + float totalDuration = timestamps.back(); + // Обновляем dtime для зацикливания + if (totalDuration > 0) + { + dtime = fmod(dtime, totalDuration); + } + } + + // Итоговое значение параметра + PARAMETER_TYPE parameterValue; + // Если только один кадр, используем значение этого кадра + if (timestamps.size() == 1) + { + // Применяем значение к целевому узлу + parameterValue = values[0]; + } + else + { + // Поиск подходящих индексов для интерполяции + size_t index1 = 0, index2 = 1; + for (; index2 < timestamps.size(); ++index2) + { + if (timestamps[index2] >= dtime) + { + break; + } + index1 = index2; + } + + // Если время выходит за рамки последнего кадра, используем значение в зависимости от поведения при окончании анимации + if (index2 == timestamps.size()) + { + if (endings == STOP) + parameterValue = values.back(); + else + if (endings == TO_BEGIN) + parameterValue = values.front(); + } + else + { + // Вычисляем коэффициент интерполяции + float t = (dtime - timestamps[index1]) / (timestamps[index2] - timestamps[index1]); + + // Выбор метода интерполяции и интерполяция + switch (interpolation) + { + case STEP: + parameterValue = values[index1]; + break; + case LINEAR: + if (path == ROTATION) + parameterValue.quat = glm::slerp(values[index1].quat, values[index2].quat, t); + else + parameterValue.vec3 = glm::lerp(values[index1].vec3, values[index2].vec3, t); + break; + case CUBICSPLINE: + float t2 = t*t; + float t3 = t2*t; + if (path == ROTATION) + { + // Обеспечение кратчайшего пути для интерполяции + glm::quat q2Adjusted = values[index2]. quat; + glm::quat t2Adjusted = tangents[index2].in. quat; + if (glm::dot(values[index1].quat, values[index2].quat) < 0) + { + q2Adjusted = -q2Adjusted; + t2Adjusted = -t2Adjusted; + } + parameterValue.quat = (2*t3 - 3*t2 + 1) * values[index1]. quat + + (t3 - 2*t2 + t) * tangents[index1].out.quat + + (-2*t3 + 3*t2) * q2Adjusted + + (t3 - t2) * t2Adjusted; + parameterValue.quat = glm::normalize(parameterValue.quat); + } + else + parameterValue.vec3 = (2*t3 - 3*t2 + 1) * values[index1]. vec3 + + (t3 - 2*t2 + t) * tangents[index1].out.vec3 + + (-2*t3 + 3*t2) * values[index2]. vec3 + + (t3 - t2) * tangents[index2].in. vec3; + break; + } + } + } + + if (path == POSITION) + target->e_position() = parameterValue.vec3; + if (path == ROTATION) + target->e_rotation() = parameterValue.quat; + if (path == SCALE) + target->e_scale() = parameterValue.vec3; + + } +} + +// Задает состояние анимации как начало +void Animation::begin() +{ + begin_time = std::chrono::steady_clock::now(); + enabled = true; +} + +// Заканчивает выполнение анимации +void Animation::end() +{ + enabled = false; +} + +// Возвращает состояние анимации (вкл/выкл) +bool Animation::isEnabled() +{ + return enabled; +} + +// Выполняет анимацию для всех каналов +void Animation::process() +{ + float dtime = std::chrono::duration(std::chrono::steady_clock::now() - begin_time).count() / 1000.0f; + for (Channel & channel : channels) + { + // channel.interpolation = STEP; + channel.process(dtime, endings); + } +} diff --git a/src/Buffers.cpp b/src/Buffers.cpp new file mode 100644 index 0000000..2b2d448 --- /dev/null +++ b/src/Buffers.cpp @@ -0,0 +1,203 @@ +#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); +} + +// Создает буфер кадра с нужным числом прикреплений текстур +FBO::FBO(GLuint *attachments, int count) +{ + glGenFramebuffers(1, &handler); + use(); + glDrawBuffers(count, attachments); +} + +// Уничтожение буфера +FBO::~FBO() +{ + glDeleteFramebuffers(1, &handler); +} + +// Активирует буфер кадра в заданном режиме +void FBO::use(GLuint mode) +{ + glBindFramebuffer(mode, handler); +} + +// Активирует базовый буфер в заданном режиме +void FBO::useDefault(GLuint mode) +{ + glBindFramebuffer(mode, 0); +} + +// Привязка рендер буфера +void FBO::assignRenderBuffer(GLuint hander, GLuint attachment) +{ + glFramebufferRenderbuffer(GL_FRAMEBUFFER, attachment, GL_RENDERBUFFER, hander); +} + +// Создает буфер рендера с заданными параметрами размеров и используемых компонент +RBO::RBO(int w, int h, GLuint component) +{ + glGenRenderbuffers(1, &handler); + glBindRenderbuffer(GL_RENDERBUFFER, handler); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, w, h); +} + +// Уничтожение буфера +RBO::~RBO() +{ + glDeleteRenderbuffers(1, &handler); +} + +// Изменяет размеры буфера рендера +void RBO::reallocate(int w, int h, GLuint component) +{ + glBindRenderbuffer(GL_RENDERBUFFER, handler); // Привязка элементного буфера + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, w, h); +} + +// Возвращает дескриптор буфера рендера +GLuint RBO::getHandler() +{ + return handler; +} diff --git a/src/Camera.cpp b/src/Camera.cpp new file mode 100644 index 0000000..1493338 --- /dev/null +++ b/src/Camera.cpp @@ -0,0 +1,239 @@ +#include "Camera.h" + +// Указатель на текущую используемую камеру +Camera* Camera::p_current = NULL; + +// Границы каскадов +const float camera_cascade_distances[] = {CAMERA_NEAR, CAMERA_FAR / 50.0f, CAMERA_FAR / 10.0f, CAMERA_FAR / 3.0f, CAMERA_FAR}; + +// Защищенный (protected) конструктор камеры без перспективы +Camera::Camera(const glm::vec3 &pos, const glm::vec3 &initialRotation) : Node(NULL) // Пусть по умолчанию камера не относится к сцене +{ + sensitivity = CAMERA_DEFAULT_SENSIVITY; + position = pos; // задаем позицию + // Определяем начальный поворот + glm::quat rotationAroundX = glm::angleAxis( glm::radians(initialRotation.x), glm::vec3(1.0f, 0.0f, 0.0f)); + glm::quat rotationAroundY = glm::angleAxis(-glm::radians(initialRotation.y), glm::vec3(0.0f, 1.0f, 0.0f)); + glm::quat rotationAroundZ = glm::angleAxis( glm::radians(initialRotation.z), glm::vec3(0.0f, 0.0f, 1.0f)); + rotation = rotationAroundX * rotationAroundY * rotationAroundZ; + // Признак изменения + changed = true; +} + +// Конструктор камеры с проекцией перспективы +Camera::Camera(float aspect, const glm::vec3 &position, const glm::vec3 &initialRotation, float fovy, float near, float far) +: Camera(position, initialRotation) +{ + setPerspective(fovy, aspect, near, far); +} + +// Конструктор ортографической камеры +Camera::Camera(float width, float height, const glm::vec3 &position, const glm::vec3 &initialRotation, float near, float far) +: Camera(position, initialRotation) +{ + setOrtho(width, height, near, far); +} + +// Конструктор копирования камеры +Camera::Camera(const Camera& copy) +: Node(copy), projection(copy.projection), requiredRecalcVP(copy.requiredRecalcVP), sensitivity(copy.sensitivity), +requiredRecalcCoords(true) +{ + // Если у оригинала не было изменений - перепишем матрицу вида-проекции + if (!requiredRecalcVP) + vp = copy.vp; +} + +// Оператор присваивания +Camera& Camera::operator=(const Camera& other) +{ + Node::operator=(other); // Вызов родительского оператора= для переноса узла + + projection = other.projection; + requiredRecalcVP = other.requiredRecalcVP; + sensitivity = other.sensitivity; + + // Если у оригинала не было изменений - перепишем матрицу вида-проекции + if (!requiredRecalcVP) + vp = other.vp; + + return *this; +} + +// Деструктор +Camera::~Camera() +{ + if (p_current == this) + p_current = NULL; +} + +// Возвращает ссылку на константную матрицу проекции +const glm::mat4& Camera::getProjection() +{ + return projection; +} + +// Возвращает ссылку на константную матрицу вида +const glm::mat4& Camera::getView() +{ + recalcMatrices(); + + return view; +} + +// Возвращает ссылку на константную матрицу вида +const glm::mat4& Camera::getVP() +{ + recalcMatrices(); + + return vp; +} + +// Устанавливает заданную матрицу перспективы +void Camera::setPerspective(float fovy, float aspect, float near, float far) +{ + projection = glm::perspective(glm::radians(fovy), aspect, near, far); + requiredRecalcVP = true; + for (int cascade = 0; cascade < CAMERA_CASCADE_COUNT; cascade++) + cascade_proj[cascade] = glm::perspective(glm::radians(fovy), aspect, camera_cascade_distances[cascade], camera_cascade_distances[cascade+1]); +} + +// Устанавливает заданную ортографическую матрицу +void Camera::setOrtho(float width, float height, float near, float far) +{ + const float aspect = width / height; + projection = glm::ortho(-1.0f, 1.0f, -1.0f/aspect, 1.0f/aspect, near, far); + requiredRecalcVP = true; + for (int cascade = 0; cascade < CAMERA_CASCADE_COUNT; cascade++) + cascade_proj[cascade] = glm::ortho(-1.0f, 1.0f, -1.0f/aspect, 1.0f/aspect, camera_cascade_distances[cascade], camera_cascade_distances[cascade+1]); + +} + +// Изменяет чувствительность мыши +void Camera::setSensitivity(float sens) +{ + sensitivity = sens; +} + +// Возвращает чувствительность мыши +const float& Camera::getSensitivity() const +{ + return sensitivity; +} + +// Метод пересчета матрицы вида и произведения Вида*Проекции по необходимости, должен сбрасывать флаг changed +void Camera::recalcMatrices() +{ + if (changed || parent_changed) + { + glm::vec3 _position = position; + glm::quat _rotation = rotation; + if (parent) // Если есть родитель + { + glm::mat4 normalized_transform = parent->getTransformMatrix(); + for (int i = 0; i < 3; i++) + { + glm::vec3 axis = glm::vec3(normalized_transform[i]); + normalized_transform[i] = glm::vec4(glm::normalize(axis), normalized_transform[i].w); + } + glm::vec4 tmp = normalized_transform * glm::vec4(_position, 1.0f); + tmp /= tmp.w; + _position = glm::vec3(tmp); + _rotation = glm::quat_cast(normalized_transform) * _rotation; + } + glm::mat4 rotationMatrix = glm::mat4_cast(glm::conjugate(_rotation)); + glm::mat4 translationMatrix = glm::translate(glm::mat4(1.0f), -_position); + view = rotationMatrix * translationMatrix; + requiredRecalcVP = true; + } + + Node::recalcMatrices(); + + if (requiredRecalcVP) + { + vp = projection * view; + requiredRecalcCoords = true; // Требуется пересчитать точки пространства камеры + requiredRecalcVP = false; // Изменения применены + } +} + +// Поворачивает камеру на dx и dy пикселей с учетом чувствительности +void Camera::rotate(const glm::vec2 &xyOffset) +{ + // xyOffset - сдвиги координат мыши, xyOffset.x означает поворот вокруг оси Y, а xyOffset.y - поворот вокруг оси X + + // Вращение вокруг оси Y + glm::quat qY = glm::angleAxis(-xyOffset.x * sensitivity, glm::vec3(0.0f, 1.0f, 0.0f)); + + // Вращение вокруг оси X + glm::quat qX = glm::angleAxis(xyOffset.y * sensitivity, glm::vec3(1.0f, 0.0f, 0.0f)); + + // Сначала применяем вращение вокруг Y, затем вокруг X + rotation = qY * rotation * qX; + + changed = true; + invalidateParent(); // Проход потомков в глубину с изменением флага parent_changed +} + +// Использование этой камеры как текущей +void Camera::use() +{ + p_current = this; +} + +// Ссылка на текущую используемую камеру +Camera& Camera::current() +{ + static Camera default_cam(800.0f/600.0f); + + if (!p_current) + return default_cam; + else + return *p_current; +} + +// Данные о камере для шейдера +CameraData& Camera::getData() +{ + static CameraData data; + data = {getProjection(), getView(), position}; + return data; +} + +// Доступ к координатам с флагом изменения, описывающим пространство вида с пересчетом, если это требуется +std::pair Camera::getProjCoords() +{ + const glm::mat4& cam_vp = getVP(); // Получение ссылки на матрицу вида-проекции с пересчетом, если требуется и активацией флага requiredRecalcCoords + bool changes = false; // Возвращаемое значение + + if (requiredRecalcCoords) + { + // Инверсия матрицы вида/проекции камеры + glm::mat4 inv = glm::inverse(cam_vp); + // Типовые точки, описывающие пространство + glm::vec4 typical_points[8] = { { 1, 1, 1,1} + , { 1, 1,-1,1} + , { 1,-1, 1,1} + , { 1,-1,-1,1} + , {-1, 1, 1,1} + , {-1, 1,-1,1} + , {-1,-1, 1,1} + , {-1,-1,-1,1}}; + + for (int cascade = 0; cascade < CAMERA_CASCADE_COUNT; cascade++) + { + glm::mat4 inv = glm::inverse(cascade_proj[cascade] * getView()); + // Цикл по типовым точкам + for (int i = 0; i < 8; i++) + { + coords[cascade][i] = inv * typical_points[i]; + coords[cascade][i] /= coords[cascade][i].w; + } + } + + requiredRecalcCoords = false; // Сбрасываем флаг + changes = true; + } + + return std::make_pair(changes, coords); +} diff --git a/src/Lights.cpp b/src/Lights.cpp new file mode 100644 index 0000000..058b10c --- /dev/null +++ b/src/Lights.cpp @@ -0,0 +1,379 @@ +#include "Lights.h" + +#include "Scene.h" // Для отладочного вывода лампочек + +#include + +GLuint Light::count = 0; // количество используемых источников (должно быть <= MAX_LIGHTS) +LightData Light::data[MAX_LIGHTS]; // Массив данных по источникам света +Light Light::lights[MAX_LIGHTS]; // Массив источников-узлов сцены + +Sun Sun::instance; // Экземпляр синглтона +bool Sun::uploadReq = true; // Необходимость загрузки в следствии изменений + +// возвращает размер буфера в байтах +int Light::getUBOsize() +{ + return sizeof(LightData) * MAX_LIGHTS + sizeof(GLuint); +} + +// Загрузка данных в буфер +void Light::upload(UBO& lights_data) +{ + GLuint LightDataSize = sizeof(LightData); // Одного экземпляра структуры LightData + int first = MAX_LIGHTS, last = -1; // Начало и конец диапазона загрузки источников + static GLuint prev_count = -1; // Кол-во источников в прошлую посылку + + if (count) + { + for (int i = 0; i < MAX_LIGHTS; i++) + { + lights[i].recalcMatrices(); // Пересчитаем матрицы по необходимости (проверка внутри метода) + + // Если требуется загрузка + if (lights[i].uploadReq) + { + lights[i].toData(); // Перевод ноды в данные для шейдера + + // Определение диапазона загрузки + if (first > lights[i].index) + first = lights[i].index; + if (last < lights[i].index) + last = lights[i].index; + + lights[i].uploadReq = false; // Сброс флага + } + } + + // Если есть что загрузить (определен диапазон) + if (last > -1) + lights_data.loadSub(data + first, LightDataSize*(last - first +1), LightDataSize*(first)); // Загрузка данных об источниках + } + + // Если кол-во изменилось + if (prev_count != count) + { + prev_count = count; + + // Загружаем кол-во источников + lights_data.loadSub(&count, sizeof(count), LightDataSize*MAX_LIGHTS); + } +} + +// Метод пересчета матрицы трансформации по необходимости, должен сбрасывать флаг changed +void Light::recalcMatrices() +{ + // Если были изменения - необходимо загрузить данные + if (changed || parent_changed) + uploadReq = true; + + // Выполняем вычисление матриц методом родительского класса + Node::recalcMatrices(); +} + +// Константный доступ к цвету +const glm::vec3& Light::c_color() const +{ + return color; +} + +// Неконстантная ссылка для изменений цвета +glm::vec3& Light::e_color() +{ + uploadReq = true; + + return color; +} + +// Проверка что не взаимодествуем с пустым источником +void Light::check_id() +{ + if (index < 0 + || index >= count) + throw std::runtime_error("Попытка использовать ссылку на пустой или некорректный источник"); +} + +// Преобразует информацию об источнике в структуру LightData +void Light::toData() +{ + check_id(); // Проверка на работу с корректным индексом + + // Если позиция изменилась + if (data[index].position.x != result_transform[3].x + || data[index].position.y != result_transform[3].y + || data[index].position.z != result_transform[3].z + ) + { + data[index].position = glm::vec3(result_transform[3]); // Позиция из матрицы трансформации + recalcVP(); // Пересчет матрицы вида-проекции для расчета теней + } + data[index].color = color; // Цвет + // Если радиус изменился + if (data[index].attenuation.r != radius) + { + data[index].attenuation.r = radius; // Радиус действия источника + data[index].attenuation[1] = 4.5/radius; // Линейный коэф. угасания + data[index].attenuation[2] = 4 * data[index].attenuation[1] * data[index].attenuation[1]; // Квадратичный коэф. угасания + } + // Направление и угол источника + data[index].direction_angle = glm::vec4( glm::normalize(glm::vec3(result_transform * DEFAULT_LIGHT_DIRECTION)) + , angle / 2 // Половинный угол для вычислений на шейдере + ); +} + +// Возвращает ссылку на новый источник света +Light& Light::getNew() +{ + Light& refNew = findByIndex(-1); + + refNew.index = count++; + refNew.uploadReq = true; + + return refNew; +} + +// Уничтожает источник света +void Light::destroy() +{ + check_id(); // Проверка на работу с корректным индексом + // Если удаляемый элемент не последний + if (count-1 != index) + { + // Найдем элемент для замены + Light& replace = findByIndex(--count); + + replace.uploadReq = true; // Требуется загрузить данные + replace.index = index; // Заменяем индекс данных + } + + operator=(Light()); // Обнулим источник путем замены на новый +} + +// Возвращает ссылку на источник с нужным индексом +Light& Light::findByIndex(GLuint index) +{ + // Если нет источников - возвращаем нулевой + if (!count) + return lights[0]; + + // Цикл по перебору источников + for (int i = 0; i < MAX_LIGHTS; i++) + if (lights[i].index == index) + return lights[i]; + + throw std::runtime_error("Запрашиваемый источник освещения не найден, либо достигнут лимит"); +} + +// Конструктор без параметров +Light::Light() : Node(), index(-1), uploadReq(false), color(1.0f), radius(10.0f), angle(360.0f) +{ + +} + +// Оператор присваивания +Light& Light::operator=(const Light& other) +{ + // Проверка на самоприсваивание + if (this != &other) + { + index = other.index; // Переносим индекс + uploadReq = other.uploadReq; // Необходимость загрузки + color = other.color; + radius = other.radius; + angle = other.angle; + + Node::operator=(other); + } + return *this; +} + +Light::~Light() +{ + +} + +// Рисование отладочных лампочек +void Light::render(ShaderProgram &shaderProgram, UBO &material_buffer) +{ + // Загрузка модели лампочки при первом вызове функции + static Scene bulb = loadOBJtoScene("../resources/models/bulb.obj", "../resources/models/", "../resources/textures/"); + static Model sphere = genShpere(1, 16, &bulb.root); + + GLuint angle_uniform = shaderProgram.getUniformLoc("angle"); + GLuint direction_uniform = shaderProgram.getUniformLoc("direction"); + + // Цикл по источникам света + for (int i = 0; i < count; i++) + { + // Идентификатор источника как узла сцены для всей модели лампочки + bulb.set_group_id((GLuint64) &lights[i]); + sphere.id.value = (GLuint64) &lights[i]; + + // Загрузим направление + glUniform3fv(direction_uniform, 1, &data[i].direction_angle.x); + // Угол для лампочки = 180 (рисуем целую модель) + glUniform1f(angle_uniform, 180); // Зададим параметры материала сфере действия + + // Сдвиг на позицию источника + bulb.root.e_position() = data[i].position; + sphere.e_scale() = glm::vec3(data[i].attenuation.r); // Масштабирование сферы + // Задание цвета + bulb.models.begin()->material.base_color = sphere.material.base_color = data[i].color; + + // Вызов отрисовки + bulb.render(shaderProgram, material_buffer); + + // Угол для сферы (рисуем направленный конус) + glUniform1f(angle_uniform, data[i].direction_angle.a); + + // Рисование сферы покрытия источника в режиме линий + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + sphere.render(shaderProgram, material_buffer); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } +} + +// Константный доступ к радиусу +const float& Light::c_radius() const +{ + return radius; +} + +// Неконстантная ссылка для изменений радиуса +float& Light::e_radius() +{ + uploadReq = true; + + return radius; +} + +// Константный доступ к углу освещенности +const float& Light::c_angle() const +{ + return angle; +} + +// Неконстантная ссылка для изменений угла освещенности +float& Light::e_angle() +{ + uploadReq = true; + + return angle; +} + +// Конструктор направленного источника с параметрами направления и цвета +Sun::Sun(const glm::vec3 &dir, const glm::vec3 &c) : direction(dir), color(c) +{ + +} + +// Доступ к синглтону +Sun& Sun::get() +{ + return instance; +} + +// Загрузка данных об источнике на шейдер +void Sun::upload(UBO& sun_data) +{ + instance.recalcVP(); // Пересчет матрицы вида-проекции источника по необходимости (влияет на флаг uploadReq) + + if (uploadReq) + { + sun_data.loadSub(&instance, sizeof(instance)); + + uploadReq = false; + } +} + +// Константный доступ к направлению лучей источника +const glm::vec3& Sun::c_direction() const +{ + return instance.direction; +} + +// Неконстантная ссылка для изменений направления лучей источника +glm::vec3& Sun::e_direction() +{ + uploadReq = true; + + return instance.direction; +} + +// Константный доступ к цвету +const glm::vec3& Sun::c_color() const +{ + return instance.color; +} + +// Неконстантная ссылка для изменений цвета +glm::vec3& Sun::e_color() +{ + uploadReq = true; + + return instance.color; +} + +// Пересчитывает по необходимости матрицу вида-проекции +void Sun::recalcVP() +{ + // Точки по краям проекции камеры + std::pair camProjCoords = Camera::current().getProjCoords(); + + // Есть изменения по источнику или камере + if (uploadReq || camProjCoords.first) + { + uploadReq = true; // Требуется загрузка в следствии пересчета матрицы + + glm::vec3 mean; // Среднее арифметическое + glm::vec4 max, min; // макс и мин координаты + glm::vec4 point; // Точка приведенная в пространство источника света + + + for (int cascade = 0; cascade < CAMERA_CASCADE_COUNT; cascade++) + { + mean = glm::vec3(0); + // Найдем среднее арифметическое от точек для нахождения центра прямоугольника + for (int i = 0; i < 8; i++) + mean += glm::vec3(camProjCoords.second[cascade][i]); + mean /= 8; + // Используем среднее арифметическое для получения матрицы вида параллельного источника + glm::mat4 lightView = glm::lookAt(mean + glm::normalize(direction), mean, CAMERA_UP_VECTOR); + + // Примем первую точку как минимальную и максимальную (приведя в пространство вида источника) + min = max = lightView * camProjCoords.second[cascade][0]; + // Для оставшихся точек + for (int i = 1; i < 8; i++) + { + // Приведем в пространство вида источника + point = lightView * camProjCoords.second[cascade][i]; + max = glm::max(max, point); + min = glm::min(min, point); + } + + // Максимальное значение глубины + max.z = std::max(fabs(max.z), fabs(min.z)); + // На основании максимальных и минимальных координат создадим матрицу проекции источника + vp[cascade] = glm::ortho(min.x, max.x, min.y, max.y, min.z, max.z) * lightView; + } + } +} + +// Пересчитывает по необходимости матрицу вида-проекции +void Light::recalcVP() +{ + float near_plane = 0.1f; + glm::mat4 shadowProj = glm::perspective(glm::radians(90.0f), 1.0f, near_plane, radius); + data[index].vp[0] = shadowProj * glm::lookAt(position, position + glm::vec3( 1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)); + data[index].vp[1] = shadowProj * glm::lookAt(position, position + glm::vec3(-1.0f, 0.0f, 0.0f), glm::vec3(0.0f, -1.0f, 0.0f)); + data[index].vp[2] = shadowProj * glm::lookAt(position, position + glm::vec3( 0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); + data[index].vp[3] = shadowProj * glm::lookAt(position, position + glm::vec3( 0.0f, -1.0f, 0.0f), glm::vec3(0.0f, 0.0f, -1.0f)); + data[index].vp[4] = shadowProj * glm::lookAt(position, position + glm::vec3( 0.0f, 0.0f, 1.0f), glm::vec3(0.0f, -1.0f, 0.0f)); + data[index].vp[5] = shadowProj * glm::lookAt(position, position + glm::vec3( 0.0f, 0.0f, -1.0f), glm::vec3(0.0f, -1.0f, 0.0f)); +} + +// Возвращает количество источников +int Light::getCount() +{ + return count; +} diff --git a/src/Model.cpp b/src/Model.cpp new file mode 100644 index 0000000..72b8395 --- /dev/null +++ b/src/Model.cpp @@ -0,0 +1,646 @@ +#include "Model.h" + +#include +#include + +// Конструктор с заданным родителем (по умолчанию NULL) +Node::Node(Node* parent_) : parent(parent_), result_transform(1), parent_changed(false), +position(0), rotation(1.0f, 0.0f, 0.0f, 0.0f), scale(1), changed(false), transform(1) +{ + if (parent) + { + // Запишем себя в потомки + parent->children.push_back(this); + parent_changed = true; + } +} + +// Конструктор копирования +Node::Node(const Node& copy): position(copy.position), rotation(copy.rotation), scale(copy.scale), +parent(copy.parent), changed(copy.changed), parent_changed(copy.parent_changed), transform(1), result_transform(1) +{ + // Запишем себя в потомки + if (parent) + parent->children.push_back(this); + // Если у оригинала не было изменений - перепишем матрицу трансформации + if (!changed) + transform = copy.transform; + // Если у родителя не было изменений для оригинала - перепишем результирующую матрицу трансформации + if (!parent_changed) + result_transform = copy.result_transform; +} + +Node::~Node() +{ + setParent(NULL); // Удаляем себя из потомков + // Сообщаем потомкам об удалении родителя + for (Node* child : children) + child->setParent(NULL); +} + +// Возвращает необходимость пересчета матрицы трансформации +bool Node::isChanged() +{ + return changed; +} + +// Константный доступ к позиции +const glm::vec3& Node::c_position() const +{ + return position; +} + +// Константный доступ к повороту +const glm::quat& Node::c_rotation() const +{ + return rotation; +} + +// Константный доступ к масштабированию +const glm::vec3& Node::c_scale() const +{ + return scale; +} + +// Неконстантная ссылка для изменений позиции +glm::vec3& Node::e_position() +{ + changed = true; // Флаг о изменении + invalidateParent(); // Проход потомков в глубину с изменением флага parent_changed + return position; +} + +// Неконстантная ссылка для изменений поворота +glm::quat& Node::e_rotation() +{ + changed = true; // Флаг о изменении + invalidateParent(); // Проход потомков в глубину с изменением флага parent_changed + return rotation; +} + +// Неконстантная ссылка для изменений масштабирования +glm::vec3& Node::e_scale() +{ + changed = true; // Флаг о изменении + invalidateParent(); // Проход потомков в глубину с изменением флага parent_changed + return scale; +} + +// Возвращает матрицу трансформации модели +const glm::mat4& Node::getTransformMatrix() +{ + // Если требуется - пересчитаем матрицу + recalcMatrices(); + + return result_transform; +} + +// Пересчет матрицы трансформации модели, если это требуется +void Node::recalcMatrices() +{ + // Если было изменение по векторам позиции, поворота и масштабирования + if (changed) + { + transform = glm::mat4(1.0f); + // Перемещение модели + transform = glm::translate(transform, position); + // Поворот модели + transform = transform * glm::mat4_cast(rotation); + // Масштабирование + transform = glm::scale(transform, scale); + } + + // Если собственная или родительская матрицы менялись - необходимо пересчитать итоговую + if (changed || parent_changed) + { + if (parent) // Если есть родитель + result_transform = parent->getTransformMatrix() * transform; + else // Если нет родителя + result_transform = transform; + + parent_changed = changed = false; // Изменения применены + } +} + +// Проход потомков в глубину с изменением флага parent_changed +void Node::invalidateParent() +{ + // Цикл по потомкам + for (Node* child : children) + { + child->parent_changed = true; // Флаг + child->invalidateParent(); // Рекурсивный вызов для потомков выбранного потомка + } +} + +// Устанавливает родителя для узла +void Node::setParent(Node * parent) +{ + // Если замена происходит на другого родителя + if (parent != this->parent) + { + Node* tmp = parent; + // Проверка на зацикливание об самого себя + while (tmp) + { + if (tmp == this) + return; // Можно выдать exception + tmp = tmp->parent; + } + // Если есть старый родитель - удалим себя из его потомков + if (this->parent) + { + // Поиск в списке родительских потомков + auto position = std::find(this->parent->children.begin(), this->parent->children.end(), this); + // Если итератор указывает в конец - ничего не найдено + if (position != this->parent->children.end()) + this->parent->children.erase(position); // Само удаление + } + + this->parent = parent; // Заменяем указатель на родителя + // Если родитель не NULL - добавляем себя в детей + if (parent) + parent->children.push_back(this); + // В любом случае необходимо пересчитать собственную итоговую матрицу + parent_changed = true; + } +} + +// Возвращает указатель на родителя +Node* Node::getParent() +{ + return parent; +} + +// Возвращает ссылку на вектор дочерних узлов +const std::vector& Node::getChildren() const +{ + return children; +} + +// Оператор присваивания +Node& Node::operator=(const Node& other) +{ + position = other.position; + rotation = other.rotation; + scale = other.scale; + changed = other.changed; + + if (!changed) + transform = other.transform; + + setParent(other.parent); + + // Если у other флаг parent_changed == false, то можно переписать матрицу результата с него + if (!other.parent_changed) + { + result_transform = other.result_transform; + parent_changed = false; // Сбрасываем флаг после смены родителя + } + + return *this; +} + +// Конструктор по умолчанию +Model::Model(Node *parent) : Node(parent), verteces_count(0), first_index_byteOffset(0), indices_count(0), indices_datatype(GL_UNSIGNED_INT), +vertex_vbo(VERTEX), index_vbo(ELEMENT), normals_vbo(VERTEX), texCoords_vbo(VERTEX), +tangent_vbo(VERTEX), bitangent_vbo(VERTEX) +{ + // Приведение указателя к целому 8байт + id.value = (GLuint64) this; + id.etc = 0; +} + +// Конструктор копирования +Model::Model(const Model& copy) : Node(copy), +vao(copy.vao), +verteces_count(copy.verteces_count), first_index_byteOffset(copy.first_index_byteOffset), indices_count(copy.indices_count), indices_datatype(copy.indices_datatype), +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) +{ + // Приведение указателя к целому 8байт + id.value = (GLuint64) this; + id.etc = copy.id.etc; +} + +// Оператор присваивания +Model& Model::operator=(const Model& other) +{ + Node::operator=(other); // Явный вызов родительского оператора копирования + + vao = other.vao; + verteces_count = other.verteces_count; + first_index_byteOffset = other.first_index_byteOffset; + indices_count = other.indices_count; + + vertex_vbo = other.vertex_vbo; + index_vbo = other.index_vbo; + texCoords_vbo = other.texCoords_vbo; + + tangent_vbo = other.tangent_vbo; + bitangent_vbo = other.bitangent_vbo; + + texture_albedo = other.texture_albedo; + texture_roughness = other.texture_roughness; + texture_metallic = other.texture_metallic; + texture_specular = other.texture_specular; + texture_emitted = other.texture_emitted; + + texture_heights = other.texture_heights; + texture_normals = other.texture_normals; + + material = other.material; + + return *this; +} + +Model::~Model() +{ + +} + +// Вызов отрисовки без uniform-данных +void Model::render() +{ + // Подключаем VAO + vao.use(); + // Если есть индексы - рисуем с их использованием + if (indices_count) + { + index_vbo.use(); + glDrawElements(GL_TRIANGLES, indices_count, indices_datatype, (void*)(first_index_byteOffset)); + } + // Если есть вершины - рисуем на основании массива вершин + else if (verteces_count) + glDrawArrays(GL_TRIANGLES, 0, verteces_count); +} + +// Вызов отрисовки +void Model::render(ShaderProgram &shaderProgram, UBO &material_buffer) +{ + // Загрузка идентификатора объекта + glUniform3uiv(shaderProgram.getUniformLoc("ID"), 1, (GLuint*) &id); + + // Расчитаем матрицу трансформации + glUniformMatrix4fv(shaderProgram.getUniformLoc("model"), 1, GL_FALSE, &this->getTransformMatrix()[0][0]); + + // Подключаем текстуры + texture_albedo.use(); + texture_roughness.use(); + texture_metallic.use(); + texture_specular.use(); + texture_emitted.use(); + texture_heights.use(); + texture_normals.use(); + + // Загружаем данные о материале + material_buffer.load(&material, sizeof(material)); + + render(); +} + +// Функция для конфигурации атрибута вершинного буфера +void vertex_attrib_config() +{ + // Определим спецификацию атрибута + glVertexAttribPointer( 0 // индекс атрибута, должен совпадать с Layout шейдера + , 3 // количество компонент одного элемента + , GL_FLOAT // тип + , GL_FALSE // необходимость нормировать значения + , 0 // шаг + , (void *)0 // отступ с начала массива + ); + // Включаем необходимый атрибут у выбранного VAO + glEnableVertexAttribArray(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() +{ + // Определим спецификацию атрибута + glVertexAttribPointer( 1 // индекс атрибута, должен совпадать с Layout шейдера + , 2 // количество компонент одного элемента + , GL_FLOAT // тип + , GL_FALSE // необходимость нормировать значения + , 0 // шаг + , (void *)0 // отступ с начала массива + ); + // Включаем необходимый атрибут у выбранного VAO + glEnableVertexAttribArray(1); +} + +// Загрузка текстурных координат в буфер +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 и привязанным VBO + glVertexAttribPointer( 2 // индекс атрибута, должен совпадать с Layout шейдера + , 3 // количество компонент одного элемента + , GL_FLOAT // тип + , GL_FALSE // необходимость нормировать значения + , 0 // шаг + , (void *)0 // отступ с начала массива + ); + // Включаем необходимый атрибут у выбранного VAO + glEnableVertexAttribArray(2); +} + +// Загрузка нормалей в буфер +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(); +} + +// Ограничение диапазона из буфера индексов +void Model::set_index_range(size_t first_byteOffset, size_t count, size_t type) +{ + first_index_byteOffset = first_byteOffset; + indices_count = count; + indices_datatype = type; +} + +// Привязка текстуры к модели +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 tangent_attrib_config() +{ + // Определим спецификацию атрибута + glVertexAttribPointer( 3 // индекс атрибута, должен совпадать с Layout шейдера + , 3 // количество компонент одного элемента + , GL_FLOAT // тип + , GL_FALSE // необходимость нормировать значения + , 0 // шаг + , (void *)0 // отступ с начала массива + ); + // Включаем необходимый атрибут у выбранного VAO + glEnableVertexAttribArray(3); +} + +// Функция для конфигурации атрибута вершинного буфера +void bitangent_attrib_config() +{ + // Определим спецификацию атрибута + glVertexAttribPointer( 4 // индекс атрибута, должен совпадать с Layout шейдера + , 3 // количество компонент одного элемента + , GL_FLOAT // тип + , GL_FALSE // необходимость нормировать значения + , 0 // шаг + , (void *)0 // отступ с начала массива + ); + // Включаем необходимый атрибут у выбранного VAO + glEnableVertexAttribArray(4); +} + +// Загрузка касательных векторов в буфер +void Model::load_tangent(glm::vec3* tangent, GLuint count) +{ + // Подключаем VAO + vao.use(); + + tangent_vbo.use(); + + // Загрузка вершин в память буфера + tangent_vbo.load(tangent, sizeof(glm::vec3)*count); + tangent_attrib_config(); +} + +// Загрузка бикасательных векторов в буфер +void Model::load_bitangent(glm::vec3* bitangent, GLuint count) +{ + // Подключаем VAO + vao.use(); + + bitangent_vbo.use(); + + // Загрузка вершин в память буфера + bitangent_vbo.load(bitangent, sizeof(glm::vec3)*count); + bitangent_attrib_config(); +} + +// Замена вершинного буфера по номеру его привязки +void Model::setBO(int attribute, BO & bo) +{ + switch(attribute) + { + case 0: + vertex_vbo = bo; + break; + case 1: + texCoords_vbo = bo; + break; + case 2: + normals_vbo = bo; + break; + case 3: + tangent_vbo = bo; + break; + case 4: + bitangent_vbo = bo; + break; + default: + throw std::runtime_error("Unknown attribute buffer"); + }; +} + +// Замена индексного буфера +void Model::setIndicesBO(BO & data) +{ + index_vbo = data; +} + +// Генерирует сферу заданного радиуса с определенным количеством сегментов +Model genShpere(float radius, int sectorsCount, Node* parent) +{ + Model result(parent); + + 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); + } + + } + } + // Загрузка в модель + result.load_verteces(&vertices[0], vertices.size()); + result.load_normals(&normals[0], normals.size()); + result.load_indices(&indices[0], indices.size()); + + return result; +} + +// Расчет касательных и бикасательных векторов +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; + // Нормируем значение + tmp = glm::normalize(tmp); + // Добавим вектор в контейнер + 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; + // Нормируем значение + tmp = glm::normalize(tmp); + // Добавим вектор в контейнер + bitangent[indices[i ]] = tmp; // Для каждого индекса полигона + bitangent[indices[i+1]] = tmp; // значение вектора + bitangent[indices[i+2]] = tmp; // одинаковое + } +} diff --git a/src/Scene.cpp b/src/Scene.cpp new file mode 100644 index 0000000..a58e576 --- /dev/null +++ b/src/Scene.cpp @@ -0,0 +1,774 @@ +#include "Scene.h" + +// Конструктор пустой сцены +Scene::Scene() +{ + +} + +// Конструктор копирования +Scene::Scene(const Scene ©): root(copy.root), +nodes(copy.nodes), models(copy.models), cameras(copy.cameras), +animations(copy.animations), animation_names(copy.animation_names) +{ + rebuld_tree(copy); +} + +// Оператор присваивания +Scene& Scene::operator=(const Scene& other) +{ + root = other.root; + nodes = other.nodes; + models = other.models; + cameras = other.cameras; + animations = other.animations; + animation_names = other.animation_names; + + rebuld_tree(other); + + return *this; +} + +// Рендер сцены +void Scene::render(ShaderProgram &shaderProgram, UBO &material_buffer, bool recalc_animations) +{ + // Если требуется пересчитаем анимации + if (recalc_animations) + for (auto & animation : animations) + if (animation.isEnabled()) + animation.process(); + + // Рендер моделей + for (auto & model : models) + model.render(shaderProgram, material_buffer); +} + +// Перестройка узлов выбранного списка +template +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(parent)) + move_parent(*it, from.models, this->models); + else + // Иначе проверяем на принадлежность к камерам + if (dynamic_cast(parent)) + move_parent(*it, from.cameras, this->cameras); + // Иначе это пустой узел + else + move_parent(*it, from.nodes, this->nodes); + + // Не нашли родителя - значит он не часть этой сцены + // и изменений по нему не требуется + } +} + +// Сдвигает родителя узла между двумя списками при условии его принадлежности к оригинальному +template +void Scene::move_parent(Node& for_node, const std::list& from_nodes, std::list& 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)); +} + +// Перестройка узлов анимации +template +void Scene::move_animation_target(Node*& target, const std::list& from_nodes, std::list& this_nodes) +{ + // Цикл по элементам списков для перемещения родителя + // Списки в процессе копирования идеинтичные, вторая проверка не требуется + 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) == target) + target = &(*it_this); +} + +// Перестройка дерева после копирования или присваивания +void Scene::rebuld_tree(const Scene& from) +{ + // Восстановим родителей в пустых узлах для копии + rebuild_Nodes_list(nodes, from); + rebuild_Nodes_list(models, from); + rebuild_Nodes_list(cameras, from); + + // Восстановим указатели на узлы для каналов анимаций + for (auto & animation : animations) + for (auto & channel : animation.channels) + { + // Если целевой узел - оригинальный корневой узел, то меняем на собственный корневой узел + if (channel.target == &from.root) + { + channel.target = &root; + continue; + } + + // Если можно привести к модели, то ищем родителя среди моделей + if (dynamic_cast(channel.target)) + move_animation_target(channel.target, from.models, this->models); + else + // Иначе проверяем на принадлежность к камерам + if (dynamic_cast(channel.target)) + move_animation_target(channel.target, from.cameras, this->cameras); + // Иначе это пустой узел + else + move_animation_target(channel.target, from.nodes, this->nodes); + + // Не нашли узел - значит он не часть этой сцены + // и изменений по каналу не требуется + } +} + +#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...); +} + +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 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.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 &nodes, tinygltf::Model &in_model) +{ + nodes.push_back(node_id); + for (auto& child : in_model.nodes[node_id].children) + collectGLTFnodes(child, nodes, in_model); +} + +float getFloatChannelOutput(int type, const void* array, int index) +{ + float result; + + switch (type) + { + case TINYGLTF_COMPONENT_TYPE_BYTE: + { + const char* bvalues = reinterpret_cast (array); + result = bvalues[index] / 127.0; + if (result < -1.0) + result = -1.0; + + break; + } + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_BYTE: + { + const unsigned char* ubvalues = reinterpret_cast (array); + result = ubvalues[index] / 255.0; + + break; + } + case TINYGLTF_COMPONENT_TYPE_SHORT: + { + const short* svalues = reinterpret_cast (array); + result = svalues[index] / 32767.0; + if (result < -1.0) + result = -1.0; + + break; + } + case TINYGLTF_COMPONENT_TYPE_UNSIGNED_SHORT: + { + const unsigned short* usvalues = reinterpret_cast(array); + result = usvalues[index] / 65535.0; + + break; + } + default: + { + const float* fvalues = reinterpret_cast (array); + result = fvalues[index]; + } + + } + + return result; +} + +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 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 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 pNodes(in_model.nodes.size(), NULL); + // Индексы родителей (-1 - корневой узел сцены) + std::vector parents_id(in_model.nodes.size(), -1); + + // Цикл по сценам + for (auto &scene : in_model.scenes) + { + // Так как у нас есть информация о потомках корневого узла сцены - пройдем рекурсивно и соберем все узлы из этой сцены: + std::vector 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 (auto &in_animation : in_model.animations) + { + Animation animation; + + for (auto &in_channel : in_animation.channels) + { + Channel channel; + + channel.target = pNodes[in_channel.target_node]; // Анимируемый узел + // Анимируемый параметр + if (in_channel.target_path == "translation") + channel.path = POSITION; + else + if (in_channel.target_path == "rotation") + channel.path = ROTATION; + else + if (in_channel.target_path == "scale") + channel.path = SCALE; + else + throw std::runtime_error("Неподдерживаемый параметр анимации"); + + + // Получение сэмплера для канала + const auto& sampler = in_animation.samplers[in_channel.sampler]; + + // Тип интерполяции + if (sampler.interpolation == "LINEAR") + channel.interpolation = LINEAR; + else + if (sampler.interpolation == "STEP") + channel.interpolation = STEP; + else + if (sampler.interpolation == "CUBICSPLINE") + channel.interpolation = CUBICSPLINE; + else + throw std::runtime_error("Неподдерживаемый тип интерполяции"); + + // Получение временных меток ключевых кадров (Input Accessor) + const auto& inputAccessor = in_model.accessors[sampler.input]; + const auto& inputBufferView = in_model.bufferViews[inputAccessor.bufferView]; + const auto& inputBuffer = in_model.buffers[inputBufferView.buffer]; + const float* keyframeTimes = reinterpret_cast(&inputBuffer.data[inputBufferView.byteOffset + inputAccessor.byteOffset]); + // Скопируем через метод insert + channel.timestamps.insert(channel.timestamps.end(), keyframeTimes, keyframeTimes + inputAccessor.count); + + // Получение данных ключевых кадров (Output Accessor) + const auto& outputAccessor = in_model.accessors[sampler.output]; + const auto& outputBufferView = in_model.bufferViews[outputAccessor.bufferView]; + const auto& outputBuffer = in_model.buffers[outputBufferView.buffer]; + + // Зарезервируем место + channel.values.resize(inputAccessor.count); + if (channel.interpolation == CUBICSPLINE) + channel.tangents.resize(inputAccessor.count); + + // Проверим формат и запишем данные с учетом преобразования + if (( (channel.path == POSITION || channel.path == SCALE) + && outputAccessor.type == TINYGLTF_TYPE_VEC3) // == 3 + || ( channel.path == ROTATION + && outputAccessor.type == TINYGLTF_TYPE_VEC4) // == 4 + ) + { + // Цикл по ключевым кадрам + for (int keyframe = 0; keyframe < inputAccessor.count; keyframe++) + // Цикл по компонентам + for (int component = 0; component < outputAccessor.type; component++) + { + // Для CUBICSPLINE интерполяции требуется дополнительно списать касательные + if (channel.interpolation == CUBICSPLINE) + { + if (channel.path == ROTATION) + { + channel.tangents[keyframe].in. quat[component] = getFloatChannelOutput(outputAccessor.componentType, &outputBuffer.data[outputBufferView.byteOffset + outputAccessor.byteOffset], keyframe*outputAccessor.type*3 + component); + channel.values [keyframe]. quat[component] = getFloatChannelOutput(outputAccessor.componentType, &outputBuffer.data[outputBufferView.byteOffset + outputAccessor.byteOffset], keyframe*outputAccessor.type*3 + outputAccessor.type + component); + channel.tangents[keyframe].out.quat[component] = getFloatChannelOutput(outputAccessor.componentType, &outputBuffer.data[outputBufferView.byteOffset + outputAccessor.byteOffset], keyframe*outputAccessor.type*3 + outputAccessor.type*2 + component); + } + else + { + channel.tangents[keyframe].in. vec3[component] = getFloatChannelOutput(outputAccessor.componentType, &outputBuffer.data[outputBufferView.byteOffset + outputAccessor.byteOffset], keyframe*outputAccessor.type*3 + component); + channel.values [keyframe]. vec3[component] = getFloatChannelOutput(outputAccessor.componentType, &outputBuffer.data[outputBufferView.byteOffset + outputAccessor.byteOffset], keyframe*outputAccessor.type*3 + outputAccessor.type + component); + channel.tangents[keyframe].out.vec3[component] = getFloatChannelOutput(outputAccessor.componentType, &outputBuffer.data[outputBufferView.byteOffset + outputAccessor.byteOffset], keyframe*outputAccessor.type*3 + outputAccessor.type*2 + component); + } + } + else + if (channel.path == ROTATION) + channel.values [keyframe]. quat[component] = getFloatChannelOutput(outputAccessor.componentType, &outputBuffer.data[outputBufferView.byteOffset + outputAccessor.byteOffset], keyframe*outputAccessor.type + component); + else + channel.values [keyframe]. vec3[component] = getFloatChannelOutput(outputAccessor.componentType, &outputBuffer.data[outputBufferView.byteOffset + outputAccessor.byteOffset], keyframe*outputAccessor.type + component); + } + } + else + throw std::runtime_error("Неподдерживаемые данные анимации"); + + animation.channels.push_back(channel); + } + + // Имя анимации + // Если имени нет - сгенерируем + if (in_animation.name.empty()) + { + std::string name = filename + std::to_string(result.animations.size()); + result.animation_names[name] = result.animations.size(); + } + else + result.animation_names[in_animation.name] = result.animations.size(); + + result.animations.push_back(animation); + } + + // Зададим трансформацию и родителей для узлов + // Цикл по всем индексам узлов + 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; +} diff --git a/src/Shader.cpp b/src/Shader.cpp new file mode 100644 index 0000000..c218673 --- /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/TRS.cpp b/src/TRS.cpp new file mode 100644 index 0000000..0f0ca44 --- /dev/null +++ b/src/TRS.cpp @@ -0,0 +1,187 @@ +#include "TRS.h" +#include + +// Инициализирует дополнительную информацию модели +void TRS::init_etc() +{ + int value = 1; + for (auto it = tool.models.begin(); it != tool.models.end(); ++it) + it->id.etc = value++; +} + +// Рендер инструмента нужного типа для выбранного объекта +void TRS::render(GLuint64 selectedID, ShaderProgram &shaderProgram, UBO &material_buffer) +{ + // Если есть выбранная модель - рендерим инструмент для неё + if (selectedID) + { + // Указатель на объект + Node* selectedObject = (Node*) selectedID; + + // Смещение выбранного объекта в глобальных координатах из его матрицы трансформации (включая родительские) + tool.root.e_position() = glm::vec3(selectedObject->getTransformMatrix()[3]); + + // Замена идентификатора инструмента идентификатором выбранного объекта + tool.set_group_id(selectedID); // без замены доп. информации + + // Рендер инструмента + tool.render(shaderProgram, material_buffer); + } +} + +Transform::Transform() +{ + tool = loadOBJtoScene("../resources/models/tools/transform.obj", "../resources/models/tools/", "../resources/textures/"); + + // Масштабирование + tool.root.e_scale() = glm::vec3(0.3); + + // Инициализация дополнительной информации + init_etc(); +} + +// Взаимодействие с инструментом +void Transform::process(GLuint64 selectedID, GLuint etc, const glm::vec4& dpos) +{ + // Если взаимодействие с осями инструмента + if (etc > 0) + // Если есть выбранная модель - рендерим инструмент для неё + if (selectedID) + { + // Указатель на объект + Node* selectedObject = (Node*) selectedID; + + glm::vec3 dVec(0.0f, 0.0f, 0.0f); + + // Сдвиг с учетом чувствительности для соответствующих осей + if (etc & 01) + dVec.x = dpos.x * T_SENSITIVITY; + if (etc & 02) + dVec.y = dpos.y * T_SENSITIVITY; + if (etc & 04) + dVec.z = dpos.z * T_SENSITIVITY; + + // Если есть родитель - требуется учесть его поворот + Node* parent = selectedObject->getParent(); + if (parent) + { + // Извлекаем 3x3 подматрицу, отвечающую за вращение и масштаб + glm::mat3 rotationMatrix = glm::mat3(parent->getTransformMatrix()); + + // Нормализуем столбцы подматрицы, чтобы исключить масштабирование + for (int i = 0; i < 3; i++) + rotationMatrix[i] = glm::normalize(rotationMatrix[i]); + + // Применим поворот родителя к вектору сдвига + dVec = glm::inverse(rotationMatrix) * dVec; + } + + // Добавим сдвиг от инструмента к позиции выбранного объекта + selectedObject->e_position() += dVec; + } +} + +Rotate::Rotate() +{ + tool = loadOBJtoScene("../resources/models/tools/rotate.obj", "../resources/models/tools/", "../resources/textures/"); + + // Масштабирование + tool.root.e_scale() = glm::vec3(0.3); + + int value = 1; + for (auto it = tool.models.begin(); it != tool.models.end(); ++it) + { + it->id.etc = value; + value *= 2; + } +} + +// Взаимодействие с инструментом +void Rotate::process(GLuint64 selectedID, GLuint etc, const glm::vec4& drot) +{ + // Если взаимодействие с осями инструмента + if (etc > 0) + // Если есть выбранная модель - рендерим инструмент для неё + if (selectedID) + { + // Указатель на объект + Node* selectedObject = (Node*) selectedID; + glm::quat &selectedRot = selectedObject->e_rotation(); + + // Матрица родительского поворота + glm::mat3 parentRotation(1); + + // Учет родительского поворота для вращения + Node* parent = selectedObject->getParent(); + if (parent) + { + // Извлекаем 3x3 подматрицу, отвечающую за вращение и масштаб + parentRotation = glm::mat3(parent->getTransformMatrix()); + + // Нормализуем столбцы подматрицы, чтобы исключить масштабирование + for (int i = 0; i < 3; i++) + parentRotation[i] = glm::normalize(parentRotation[i]); + } + + // Поворот по осям + if (etc & 01) + selectedRot = glm::angleAxis(drot.y * R_SENSITIVITY, parentRotation * glm::vec3(1.0f, 0.0f, 0.0f)) * selectedRot; + if (etc & 02) + selectedRot = glm::angleAxis(drot.x * R_SENSITIVITY, parentRotation * glm::vec3(0.0f, 1.0f, 0.0f)) * selectedRot; + if (etc & 04) + selectedRot = glm::angleAxis(drot.z * R_SENSITIVITY, parentRotation * glm::vec3(0.0f, 0.0f, 1.0f)) * selectedRot; + } +} + +Scale::Scale() +{ + tool = loadOBJtoScene("../resources/models/tools/scale.obj", "../resources/models/tools/", "../resources/textures/"); + + // Масштабирование + tool.root.e_scale() = glm::vec3(0.3); + + // Инициализация дополнительной информации + init_etc(); +} + +// Взаимодействие с инструментом +void Scale::process(GLuint64 selectedID, GLuint etc, const glm::vec4& dscale) +{ + // Если взаимодействие с осями инструмента + if (etc > 0) + // Если есть выбранная модель - рендерим инструмент для неё + if (selectedID) + { + // Указатель на объект + Node* selectedObject = (Node*) selectedID; + + // Для хранения результата + glm::vec3 dVec(0); + + // Масштабирование с учетом чувствительности для соответствующих осей + if (etc & 01) + dVec.x = dscale.x * S_SENSITIVITY; + if (etc & 02) + dVec.y = dscale.y * S_SENSITIVITY; + if (etc & 04) + dVec.z = dscale.z * S_SENSITIVITY; + + // Если есть родитель - требуется учесть его поворот + Node* parent = selectedObject->getParent(); + if (parent) + { + // Извлекаем 3x3 подматрицу, отвечающую за вращение и масштаб + glm::mat3 rotationMatrix = glm::mat3(parent->getTransformMatrix()); + + // Нормализуем столбцы подматрицы, чтобы исключить масштабирование + for (int i = 0; i < 3; i++) + rotationMatrix[i] = glm::normalize(rotationMatrix[i]); + + // Применим поворот родителя к вектору сдвига + dVec = glm::inverse(rotationMatrix) * dVec; + } + + // Прибавим вектор масштабирования к объекту + selectedObject->e_scale() += dVec; + } +} \ No newline at end of file diff --git a/src/Texture.cpp b/src/Texture.cpp new file mode 100644 index 0000000..58e00fa --- /dev/null +++ b/src/Texture.cpp @@ -0,0 +1,452 @@ +#include "Texture.h" + +#define STB_IMAGE_IMPLEMENTATION +#include + +std::map BaseTexture::filename_handler; // Получение дескриптора текстуры по её имени +std::map BaseTexture::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) + { + // Выбор формата с учетом типа текстуры (нормали не sRGB) и числа каналов + GLuint internalformat = GL_RGB, format = GL_RGB; + switch (channels) + { + case 1: + internalformat = format = GL_RED; + break; + case 2: + internalformat = format = GL_RG; + break; + case 3: + format = GL_RGB; + if (type == TEX_NORMAL || type == TEX_HEIGHTS) + internalformat = GL_RGB; + else + internalformat = GL_SRGB; + break; + case 4: + format = GL_RGBA; + if (type == TEX_NORMAL || type == TEX_HEIGHTS) + internalformat = GL_RGBA; + else + internalformat = GL_SRGB_ALPHA; + break; + + } + // Загрузка данных с учетом формата + glTexImage2D(GL_TEXTURE_2D, 0, internalformat, width, height, 0, format, 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(GLuint width, GLuint height, GLuint attachment, GLuint texType, GLint internalformat, GLint format, GLenum dataType) +{ + type = texType; + // Генерация текстуры заданного размера + glGenTextures(1, &handler); + glBindTexture(GL_TEXTURE_2D, handler); + glTexImage2D(GL_TEXTURE_2D, 0, internalformat, width, height, 0, format, dataType, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + // Привязка к буферу кадра + glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D, handler, 0); + + // Создаем счетчик использований дескриптора + handler_count[handler] = 1; +} + +// Конструктор текстуры заданного размера без привязки к буферу с загрузкой пикселей по указателю +Texture::Texture(GLuint width, GLuint height, void* data, GLuint texType, GLint internalformat, GLint format, GLenum dataType) +{ + type = texType; + // Генерация текстуры заданного размера + glGenTextures(1, &handler); + glBindTexture(GL_TEXTURE_2D, handler); + glTexImage2D(GL_TEXTURE_2D, 0, internalformat, width, height, 0, format, dataType, data); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + // Создаем счетчик использований дескриптора + handler_count[handler] = 1; +} + +// Конструктор копирования +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; +} + +BaseTexture::~BaseTexture() +{ + 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::reallocate(GLuint width, GLuint height, GLuint texType, GLint internalformat, GLint format, GLenum dataType) +{ + use(); + glTexImage2D(GL_TEXTURE_2D, 0, internalformat, width, height, 0, format, dataType, NULL); +} + +// Привязка текстуры +void Texture::use() +{ + glActiveTexture(type + GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, handler); // Привязка текстуры как активной +} + +// Отвязка текстуры по типу +void BaseTexture::disable(GLuint type) +{ + glActiveTexture(type + GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, 0); // Отвязка текстуры +} + +// Возвращает тип текстуры +GLuint BaseTexture::getType() +{ + return type; +} + +// Задает тип текстуры +void BaseTexture::setType(GLuint type) +{ + this->type = type; +} + +// Конструктор текстуры заданного размера для использования в буфере +TextureArray::TextureArray(GLuint levels, GLuint width, GLuint height, GLuint attachment, GLuint texType, GLint internalformat, GLint format, GLenum dataType) +{ + type = texType; + // Генерация текстуры заданного размера + glGenTextures(1, &handler); + glBindTexture(GL_TEXTURE_2D_ARRAY, handler); + glTexImage3D( + GL_TEXTURE_2D_ARRAY, 0, internalformat, width, height, levels, 0, format, dataType, 0); + + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + // Привязка к буферу кадра + glFramebufferTexture(GL_FRAMEBUFFER, attachment, handler, 0); + + // Создаем счетчик использований дескриптора + handler_count[handler] = 1; +} + +// Конструктор копирования +TextureArray::TextureArray(const TextureArray& other) +{ + handler = other.handler; + type = other.type; + // Делаем копию и увеличиваем счетчик + handler_count[handler]++; +} + +// Оператор присваивания +TextureArray& TextureArray::operator=(const TextureArray& other) +{ + // Если это разные текстуры + if (handler != other.handler) + { + this->~TextureArray(); // Уничтожаем имеющуюся + // Заменяем новой + handler = other.handler; + handler_count[handler]++; + } + type = other.type; + + return *this; +} + +// Пересоздает текстуру для имеющегося дескриптора +void TextureArray::reallocate(GLuint levels, GLuint width, GLuint height, GLuint texType, GLint internalformat, GLint format, GLenum dataType) +{ + use(); + glTexImage2D(GL_TEXTURE_2D, 0, internalformat, width, height, 0, format, dataType, NULL); +} + +// Привязка текстуры +void TextureArray::use() +{ + glActiveTexture(type + GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D_ARRAY, handler); // Привязка текстуры как активной +} + +// Загрузка текстуры с диска или использование "пустой" +TextureCube::TextureCube(GLuint t, const std::string (&filename)[6]) +{ + type = t; + std::string complex_name; + for (int i = 0; i < 6; i++) + complex_name += filename[i]; + if (!filename_handler.count(complex_name)) + { + std::string empty = ""; + int width, height, channels; // Ширина, высота и цветовые каналы текстуры + unsigned char* image; + + glActiveTexture(type + GL_TEXTURE0); + glGenTextures(1, &handler); // Генерация одной текстуры + glBindTexture(GL_TEXTURE_CUBE_MAP, handler); // Привязка текстуры как активной + + filename_handler[complex_name] = handler; // Запомним её дескриптор для этого имени файла + handler_count[handler] = 0; // Создадим счетчик использований дескриптора, который будет изменен в конце + + for (int i = 0; i < 6; i++) + { + image = stbi_load(filename[i].c_str(), &width, &height, &channels, STBI_default); // Загрузка в оперативную память изображения + + // Если изображение успешно считано + if (image) + { + // Выбор формата с учетом типа текстуры (нормали не sRGB) и числа каналов + GLuint internalformat = GL_RGB, format = GL_RGB; + switch (channels) + { + case 1: + internalformat = format = GL_RED; + break; + case 2: + internalformat = format = GL_RG; + break; + case 3: + format = GL_RGB; + if (type == TEX_NORMAL || type == TEX_HEIGHTS) + internalformat = GL_RGB; + else + internalformat = GL_SRGB; + break; + case 4: + format = GL_RGBA; + if (type == TEX_NORMAL || type == TEX_HEIGHTS) + internalformat = GL_RGBA; + else + internalformat = GL_SRGB_ALPHA; + break; + + } + // Загрузка данных с учетом формата + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, internalformat, width, height, 0, format, GL_UNSIGNED_BYTE, image); + + stbi_image_free(image); // Освобождение оперативной памяти + } + // Иначе изображение не считано и надо создать пустую текстуру + else + { + image = new unsigned char[3] {255,255,255}; // RGB по 1 байту на + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, image); // Загрузка данных на видеокарту + delete[] image; // Освобождение оперативной памяти + } + } + } + // Иначе используем уже существующую по имени файла + else + handler = filename_handler[complex_name]; + + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + + glGenerateMipmap(GL_TEXTURE_CUBE_MAP); + + handler_count[handler]++; +} + +// Конструктор текстуры заданного размера для использования в буфере +TextureCube::TextureCube(GLuint width, GLuint height, GLuint attachment, GLuint texType, GLint internalformat, GLint format, GLenum dataType) +{ + type = texType; + // Генерация текстуры заданного размера + glGenTextures(1, &handler); + glBindTexture(GL_TEXTURE_CUBE_MAP, handler); + for (int i = 0; i < 6; ++i) + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, internalformat, width, height, 0, format, dataType, 0); + + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + // Привязка к буферу кадра + glFramebufferTexture(GL_FRAMEBUFFER, attachment, handler, 0); + + // Создаем счетчик использований дескриптора + handler_count[handler] = 1; +} + +// Конструктор копирования +TextureCube::TextureCube(const TextureCube& other) +{ + handler = other.handler; + type = other.type; + // Делаем копию и увеличиваем счетчик + handler_count[handler]++; +} + +// Оператор присваивания +TextureCube& TextureCube::operator=(const TextureCube& other) +{ + // Если это разные текстуры + if (handler != other.handler) + { + this->~TextureCube(); // Уничтожаем имеющуюся + // Заменяем новой + handler = other.handler; + handler_count[handler]++; + } + type = other.type; + + return *this; +} + +// Пересоздает текстуру для имеющегося дескриптора +void TextureCube::reallocate(GLuint width, GLuint height, GLuint texType, GLint internalformat, GLint format, GLenum dataType) +{ + use(); + for (int i = 0; i < 6; ++i) + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, internalformat, width, height, 0, format, dataType, 0); +} + +// Привязка текстуры +void TextureCube::use() +{ + glActiveTexture(type + GL_TEXTURE0); + glBindTexture(GL_TEXTURE_CUBE_MAP, handler); // Привязка текстуры как активной +} + +// Конструктор текстуры заданного размера для использования в буфере +TextureCubeArray::TextureCubeArray(GLuint levels, GLuint width, GLuint height, GLuint attachment, GLuint texType, GLint internalformat, GLint format, GLenum dataType) +{ + type = texType; + // Генерация текстуры заданного размера + glGenTextures(1, &handler); + glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, handler); + glTexImage3D( + GL_TEXTURE_CUBE_MAP_ARRAY, 0, internalformat, width, height, 6*levels, 0, format, dataType, 0); + + glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_CUBE_MAP_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + + // Привязка к буферу кадра + glFramebufferTexture(GL_FRAMEBUFFER, attachment, handler, 0); + + // Создаем счетчик использований дескриптора + handler_count[handler] = 1; +} + +// Конструктор копирования +TextureCubeArray::TextureCubeArray(const TextureCubeArray& other) +{ + handler = other.handler; + type = other.type; + // Делаем копию и увеличиваем счетчик + handler_count[handler]++; +} + +// Оператор присваивания +TextureCubeArray& TextureCubeArray::operator=(const TextureCubeArray& other) +{ + // Если это разные текстуры + if (handler != other.handler) + { + this->~TextureCubeArray(); // Уничтожаем имеющуюся + // Заменяем новой + handler = other.handler; + handler_count[handler]++; + } + type = other.type; + + return *this; +} + +// Пересоздает текстуру для имеющегося дескриптора +void TextureCubeArray::reallocate(GLuint levels, GLuint width, GLuint height, GLuint texType, GLint internalformat, GLint format, GLenum dataType) +{ + use(); + glTexImage3D( + GL_TEXTURE_CUBE_MAP_ARRAY, 0, internalformat, width, height, 6*levels, 0, format, dataType, 0); +} + +// Привязка текстуры +void TextureCubeArray::use() +{ + glActiveTexture(type + GL_TEXTURE0); + glBindTexture(GL_TEXTURE_CUBE_MAP_ARRAY, handler); // Привязка текстуры как активной +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100755 index 0000000..061b8f0 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,621 @@ + +#include +#include +#include + +#include +#include + +#include "Scene.h" +#include "Shader.h" +#include "Lights.h" +#include "TRS.h" + +#define WINDOW_CAPTION "OPENGL notes on rekovalev.site" + +// Указатели на текстуры для изменения размеров окна +Texture* pgPosition = NULL; +Texture* pgNormal = NULL; +Texture* pgBaseColor = NULL; +Texture* pgRMS = NULL; +Texture* pgEmittedColor = NULL; +Texture* pgID = NULL; +RBO* pgrbo = NULL; +Texture* pssaoTexture = NULL; +Texture* pssaoTexture_raw = NULL; +// Размеры окна +int WINDOW_WIDTH = 800; +int WINDOW_HEIGHT = 600; + +// Значение гамма-коррекции +float inv_gamma = 1/2.2; + +// Функция-callback для изменения размеров буфера кадра в случае изменения размеров поверхности окна +void framebuffer_size_callback(GLFWwindow* window, int width, int height) +{ + glViewport(0, 0, width, height); + + // Изменение размеров текстур для G-буфера + if (pgPosition) + pgPosition->reallocate(width, height, 0, GL_RGB32F, GL_RGB); + if (pgNormal) + pgNormal->reallocate(width, height, 1, GL_RGB16F, GL_RGB); + if (pgBaseColor) + pgBaseColor->reallocate(width, height, 2, GL_RGB); + if (pgRMS) + pgRMS->reallocate(width, height, 3, GL_RGB, GL_RGB); + if (pgEmittedColor) + pgEmittedColor->reallocate(width, height, 8, GL_RGB, GL_RGB); + if (pgID) + pgID->reallocate(width, height, 7, GL_RGB32UI, GL_RGB_INTEGER, GL_UNSIGNED_INT); + // И буфера глубины + if (pgrbo) + pgrbo->reallocate(width, height); + // SSAO + if (pssaoTexture) + pssaoTexture->reallocate(width, height, 6, GL_RED, GL_RED); + if (pssaoTexture_raw) + pssaoTexture_raw->reallocate(width, height, 0, GL_RED, GL_RED); + + // Запомним новые размеры окна + WINDOW_WIDTH = width; + WINDOW_HEIGHT = height; + + // Изменим параметры перспективной матрицы проекции для камеры + Camera::current().setPerspective(CAMERA_FOVy, (float)width/height); +} + +// Данные о мыши +struct Mouse +{ + float x = 0, y = 0; // Координаты курсора + float prev_x = 0, prev_y = 0; // Координаты курсора на предыдущем кадре + uint16_t left = 040100, right = 040100; // Состояние кнопок +} mouse; + +void process_mouse_button(uint16_t& button) +{ + if ((++button & 037777) == 037777) + button &= 0140100; +} + +void mouse_callback(GLFWwindow* window, double xpos, double ypos) +{ + mouse.x = xpos; + mouse.y = ypos; +} + +void mouse_button_callback(GLFWwindow* window, int button, int action, int mods) +{ + uint16_t& mouse_button = (button == GLFW_MOUSE_BUTTON_LEFT)?mouse.left:mouse.right; + + if (action == GLFW_PRESS && !(mouse_button & 0100000)) + mouse_button = 0100000; + else if (action == GLFW_RELEASE) + mouse_button = 040000; +} + +int main(void) +{ + GLFWwindow* window; // Указатель на окно GLFW3 + + // Инициализация GLFW3 + if (!glfwInit()) + { + std::cout << "GLFW init error\n"; + return -1; + } + + // Завершение работы с GLFW3 перед выходом + atexit(glfwTerminate); + + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); // Мажорная версия спецификаций OpenGL + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6); // Минорная версия спецификаций 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"; + return -1; + } + + // Установка основного контекста окна + glfwMakeContextCurrent(window); + // Установка callback-функции для изменения размеров окна и буфера кадра + glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); + + glfwSwapInterval(1); // Вертикальная синхронизация + + // Установка callback-функции для мыши и камеры + glfwSetCursorPosCallback(window, mouse_callback); + glfwSetMouseButtonCallback(window, mouse_button_callback); + + // Загрузка функций OpenGL с помощью GLAD + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) + { + std::cout << "GLAD load GL error\n"; + return -1; + } + + // Включаем проверку по буферу глубины + glEnable(GL_DEPTH_TEST); + + // Шейдер для G-буфера + ShaderProgram gShader; + // Загрузка и компиляция шейдеров + gShader.load(GL_VERTEX_SHADER, "shaders/gshader.vert"); + gShader.load(GL_FRAGMENT_SHADER, "shaders/gshader.frag"); + gShader.link(); + // Установим значения текстур + const char* textures_base_shader_names[] = {"tex_albedo", "tex_roughness", "tex_metallic", "tex_specular", "tex_emitted", "tex_heights", "tex_normal"}; + gShader.bindTextures(textures_base_shader_names, sizeof(textures_base_shader_names)/sizeof(const char*)); + + // Загрузка сцены из obj файла + Scene scene = loadGLTFtoScene("../resources/models/rotating-cube_cubic-spline.gltf"); + scene.root.e_position().y = -1; + scene.root.e_position().z = 3; + scene.set_group_id((GLuint64) &scene.root); + // Включим первую анимацию, если есть + if (scene.animations.size()) + scene.animations[0].begin(); + + // Установка цвета очистки буфера цвета + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + + // Сдвинем направленный источник света и камеру + Sun::get().e_direction().z = -1.0; + Camera::current().e_position().x = 0.3f; + + // Источники света + Light& first = Light::getNew(); + first.e_color() = {1.0f, 0.0f, 0.0f}; // цвет + first.e_position() = {0.3f, 0.0f, 0.6f}; // Позиция + first.e_angle() = 100.0f; + Light& second = Light::getNew(); + second.e_color() = {0.0f, 0.0f, 1.0f}; // цвет + second.e_position() = {-0.3f, 0.3f, 0.5f}; // Позиция + + // Uniform-буферы + UBO cameraUB(sizeof(CameraData), 0); + UBO material_data(sizeof(Material), 1); + UBO light_data(Light::getUBOsize(), 2); + UBO sun_data(sizeof(Sun), 3); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // Использование уменьшенных версий mipmap + + // Создадим G-буфер с данными о используемых привязках + GLuint attachments[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1, GL_COLOR_ATTACHMENT2, GL_COLOR_ATTACHMENT3, GL_COLOR_ATTACHMENT4, GL_COLOR_ATTACHMENT5 }; + FBO gbuffer(attachments, sizeof(attachments) / sizeof(GLuint)); + // Создадим текстуры для буфера кадра + Texture gPosition(WINDOW_WIDTH, WINDOW_HEIGHT, GL_COLOR_ATTACHMENT0, 0, GL_RGB32F, GL_RGB); // Позиция вершины + Texture gNormal(WINDOW_WIDTH, WINDOW_HEIGHT, GL_COLOR_ATTACHMENT1, 1, GL_RGB16F, GL_RGB); // Нормали + Texture gBaseColor(WINDOW_WIDTH, WINDOW_HEIGHT, GL_COLOR_ATTACHMENT2, 2, GL_RGB, GL_RGB); // Базовый цвет материала + Texture gRMS(WINDOW_WIDTH, WINDOW_HEIGHT, GL_COLOR_ATTACHMENT3, 3, GL_RGB, GL_RGB); // Шероховатость, металличность, интенсивность блика + Texture gID(WINDOW_WIDTH, WINDOW_HEIGHT, GL_COLOR_ATTACHMENT4, 7, GL_RGB32UI, GL_RGB_INTEGER, GL_UNSIGNED_INT); // Идентификатор объекта + Texture gEmittedColor(WINDOW_WIDTH, WINDOW_HEIGHT, GL_COLOR_ATTACHMENT5, 8, GL_RGB, GL_RGB); // Излучаемый свет + // Создадим буфер рендера под буфер глубины и привяжем его + RBO grbo(WINDOW_WIDTH, WINDOW_HEIGHT); + gbuffer.assignRenderBuffer(grbo.getHandler()); + // Активируем базовый буфер кадра + FBO::useDefault(); + + // Сохраним указатели на текстуры для изменения размеров окна + pgPosition = &gPosition; + pgNormal = &gNormal; + pgBaseColor = &gBaseColor; + pgRMS = &gRMS; + pgrbo = &grbo; + pgID = &gID; + pgEmittedColor = &gEmittedColor; + + // Шейдер для расчета освещенности + ShaderProgram lightShader; + // Загрузка и компиляция шейдеров + lightShader.load(GL_VERTEX_SHADER, "shaders/quad.vert"); + lightShader.load(GL_FRAGMENT_SHADER, "shaders/lighting.frag"); + lightShader.link(); + // Привязка текстур + const char* gtextures_shader_names[] = {"gPosition", "gNormal", "gBaseColor", "gRMS", "sunShadowDepth", "pointShadowDepth", "ssao", "gID", "gEmittedColor", "reflections"}; + lightShader.bindTextures(gtextures_shader_names, sizeof(gtextures_shader_names)/sizeof(const char*)); + // Загрузка данных о границах каскадов + glUniform1fv(lightShader.getUniformLoc("camera_cascade_distances"), CAMERA_CASCADE_COUNT, &camera_cascade_distances[1]); + + glm::vec3 quadVertices[] = { {-1.0f, 1.0f, 0.0f} + , {-1.0f, -1.0f, 0.0f} + , { 1.0f, 1.0f, 0.0f} + , { 1.0f, -1.0f, 0.0f} + }; + + GLuint quadIndices[] = {0,1,2,2,1,3}; + + Model quadModel; + quadModel.load_verteces(quadVertices, 4); + quadModel.load_indices(quadIndices, 6); + + // Размер текстуры тени от солнца + const GLuint sunShadow_resolution = 1024; + // Создадим буфер кадра для рендера теней + FBO sunShadowBuffer; + // Создадим текстуры для буфера кадра + TextureArray sunShadowDepth(CAMERA_CASCADE_COUNT, sunShadow_resolution, sunShadow_resolution, GL_DEPTH_ATTACHMENT, 4, GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT); + // Правка фантомных теней + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + float shadowBorderColor[] = { 1.0, 1.0, 1.0, 1.0 }; + glTexParameterfv(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_BORDER_COLOR, shadowBorderColor); + // Отключим работу с цветом + glDrawBuffer(GL_NONE); + glReadBuffer(GL_NONE); + // Активируем базовый буфер кадра + FBO::useDefault(); + + // Шейдер для расчета теней + ShaderProgram sunShadowShader; + // Загрузим шейдер + sunShadowShader.load(GL_VERTEX_SHADER, "shaders/sun_shadow.vert"); + sunShadowShader.load(GL_GEOMETRY_SHADER, "shaders/sun_shadow.geom"); + sunShadowShader.load(GL_FRAGMENT_SHADER, "shaders/empty.frag"); + sunShadowShader.link(); + + // Размер одной стороны кубической карты + const GLuint pointShadow_resolution = 500; + // Создадим буфер кадра для рендера теней от источников света + FBO pointShadowBuffer; + // Создадим текстуры для буфера кадра + TextureCubeArray pointShadowDepth(MAX_LIGHTS, pointShadow_resolution, pointShadow_resolution, GL_DEPTH_ATTACHMENT, 5, GL_DEPTH_COMPONENT, GL_DEPTH_COMPONENT); + // Отключим работу с цветом + glDrawBuffer(GL_NONE); + glReadBuffer(GL_NONE); + // Активируем базовый буфер кадра + FBO::useDefault(); + + // Шейдер для расчета теней от точечных источников + ShaderProgram pointShadowShader; + // Загрузим шейдер + pointShadowShader.load(GL_VERTEX_SHADER, "shaders/sun_shadow.vert"); + pointShadowShader.load(GL_GEOMETRY_SHADER, "shaders/point_shadow.geom"); + pointShadowShader.load(GL_FRAGMENT_SHADER, "shaders/point_shadow.frag"); + pointShadowShader.link(); + + // Создадим буфер для вычисления SSAO + GLuint attachments_ssao[] = { GL_COLOR_ATTACHMENT0 }; + FBO ssaoBuffer(attachments_ssao, sizeof(attachments_ssao) / sizeof(GLuint)); + // Создадим текстуры для буфера кадра + Texture ssaoTexture_raw(WINDOW_WIDTH, WINDOW_HEIGHT, GL_COLOR_ATTACHMENT0, 0, GL_RED, GL_RED); + pssaoTexture_raw = &ssaoTexture_raw; + // Активируем базовый буфер кадра + FBO::useDefault(); + + // Стандартные параметры SSAO + SSAO_data ssao_data; + // Расчет масштабирования текстуры шума + ssao_data.scale = {WINDOW_WIDTH/4,WINDOW_HEIGHT/4}; + // Генерируем случайные векторы + std::uniform_real_distribution randomFloats(0.0, 1.0); // Генерирует случайные вещественные числа в заданном диапазоне + std::default_random_engine generator; + glm::vec3 sample; // Выборка + for (int i = 0; i < ssao_data.size; i++) + { + sample = { randomFloats(generator) * 2.0 - 1.0 + , randomFloats(generator) * 2.0 - 1.0 + , randomFloats(generator) + }; + sample = glm::normalize(sample); + sample *= randomFloats(generator); + + // Отмасштабируем выборку + sample *= 0.1 + 0.9 * (i / (float)ssao_data.size) * (i / (float)ssao_data.size); + ssao_data.samples[i] = sample; + } + // Загрузка данных в uniform-буфер + UBO ssaoUB(&ssao_data, sizeof(SSAO_data), 4); + + // Текстура шума + glm::vec3 noise_vecs[16]; + for (int i = 0; i < 16; i++) + noise_vecs[i] = { randomFloats(generator) * 2.0 - 1.0 + , randomFloats(generator) * 2.0 - 1.0 + , 0.0f + }; + Texture noiseTexture(4,4, noise_vecs, 2, GL_RGBA32F, GL_RGB); + + // Шейдер для расчета SSAO + ShaderProgram ssaoShader; + // Загрузим шейдер + ssaoShader.load(GL_VERTEX_SHADER, "shaders/quad.vert"); + ssaoShader.load(GL_FRAGMENT_SHADER, "shaders/ssao.frag"); + ssaoShader.link(); + // Текстуры, используемые в шейдере + const char* ssaoShader_names[] = {"gPosition", "gNormal", "noise"}; + ssaoShader.bindTextures(ssaoShader_names, sizeof(ssaoShader_names)/sizeof(const char*)); + + // Создадим буфер для размытия SSAO + FBO ssaoBlurBuffer(attachments_ssao, sizeof(attachments_ssao) / sizeof(GLuint)); + // Создадим текстуры для буфера кадра + Texture ssaoTexture(WINDOW_WIDTH, WINDOW_HEIGHT, GL_COLOR_ATTACHMENT0, 6, GL_RED, GL_RED); + pssaoTexture = &ssaoTexture; + // Активируем базовый буфер кадра + FBO::useDefault(); + + // Шейдер для размытия SSAO + ShaderProgram ssaoBlurShader; + // Загрузим шейдер + ssaoBlurShader.load(GL_VERTEX_SHADER, "shaders/quad.vert"); + ssaoBlurShader.load(GL_FRAGMENT_SHADER, "shaders/ssaoBlur.frag"); + ssaoBlurShader.link(); + + // Модель прямоугольника + Scene rectangle = loadOBJtoScene("../resources/models/plane2.obj", "../resources/models/", "../resources/textures/"); + + // Зададим горизонтальное положение перед камерой + rectangle.root.e_position().y = -1; + rectangle.root.e_position().z = 2; + rectangle.root.e_rotation() = glm::quat(0.707f, 0.707f, 0.0f, 0.0f); + rectangle.root.e_scale() = glm::vec3(4); + + // Текстуры для прямоугольника + Texture rectangle_diffuse(TEX_ALBEDO, "../resources/textures/rekovalev_diffusemap.png"); + rectangle.models.begin()->set_texture(rectangle_diffuse); + Texture rectangle_normal(TEX_NORMAL, "../resources/textures/rekovalev_normalmap.png"); + rectangle.models.begin()->set_texture(rectangle_normal); + Texture rectangle_heights(TEX_HEIGHTS, "../resources/textures/rekovalev_bumpmap.png"); + rectangle.models.begin()->set_texture(rectangle_heights); + + // Шейдер для рисования отладочных лампочек + ShaderProgram bulbShader; + // Загрузка и компиляция шейдеров + bulbShader.load(GL_VERTEX_SHADER, "shaders/bulb.vert"); + bulbShader.load(GL_FRAGMENT_SHADER, "shaders/bulb.frag"); + bulbShader.link(); + + // Вершины для скайбокса + glm::vec3 skybox_verticies[] = { + {-1.0f, 1.0f, -1.0f}, + {-1.0f, -1.0f, -1.0f}, + { 1.0f, -1.0f, -1.0f}, + { 1.0f, -1.0f, -1.0f}, + { 1.0f, 1.0f, -1.0f}, + {-1.0f, 1.0f, -1.0f}, + + {-1.0f, -1.0f, 1.0f}, + {-1.0f, -1.0f, -1.0f}, + {-1.0f, 1.0f, -1.0f}, + {-1.0f, 1.0f, -1.0f}, + {-1.0f, 1.0f, 1.0f}, + {-1.0f, -1.0f, 1.0f}, + + { 1.0f, -1.0f, -1.0f}, + { 1.0f, -1.0f, 1.0f}, + { 1.0f, 1.0f, 1.0f}, + { 1.0f, 1.0f, 1.0f}, + { 1.0f, 1.0f, -1.0f}, + { 1.0f, -1.0f, -1.0f}, + + {-1.0f, -1.0f, 1.0f}, + {-1.0f, 1.0f, 1.0f}, + { 1.0f, 1.0f, 1.0f}, + { 1.0f, 1.0f, 1.0f}, + { 1.0f, -1.0f, 1.0f}, + {-1.0f, -1.0f, 1.0f}, + + {-1.0f, 1.0f, -1.0f}, + { 1.0f, 1.0f, -1.0f}, + { 1.0f, 1.0f, 1.0f}, + { 1.0f, 1.0f, 1.0f}, + {-1.0f, 1.0f, 1.0f}, + {-1.0f, 1.0f, -1.0f}, + + {-1.0f, -1.0f, -1.0f}, + {-1.0f, -1.0f, 1.0f}, + { 1.0f, -1.0f, -1.0f}, + { 1.0f, -1.0f, -1.0f}, + {-1.0f, -1.0f, 1.0f}, + { 1.0f, -1.0f, 1.0f} + }; + + // Модель скайбокса + Model skybox; + skybox.load_verteces(skybox_verticies, sizeof(skybox_verticies)/sizeof(glm::vec3)); + TextureCube skybox_texture(TEX_ALBEDO, { "../resources/textures/skybox/px.jpg" + , "../resources/textures/skybox/nx.jpg" + , "../resources/textures/skybox/py.jpg" + , "../resources/textures/skybox/ny.jpg" + , "../resources/textures/skybox/pz.jpg" + , "../resources/textures/skybox/nz.jpg" + }); + + // Шейдер для скайбокса + ShaderProgram skyboxShader; + // Загрузим шейдеры + skyboxShader.load(GL_VERTEX_SHADER, "shaders/skybox.vert"); + skyboxShader.load(GL_FRAGMENT_SHADER, "shaders/skybox.frag"); + skyboxShader.link(); + // Привязка текстуры скайбокса + const char* skybox_shader_names[] = {"skybox"}; + skyboxShader.bindTextures(skybox_shader_names, sizeof(skybox_shader_names)/sizeof(const char*)); + + // Значение гамма-коррекции + UBO gamma(&inv_gamma, sizeof(inv_gamma), 4); + + ID selected; // Выбранная модель + + // Шейдер для инструментов + ShaderProgram toolsShader; + // Загрузим шейдеры + toolsShader.load(GL_VERTEX_SHADER, "shaders/gshader.vert"); + toolsShader.load(GL_FRAGMENT_SHADER, "shaders/tools.frag"); + toolsShader.link(); + + // Инструменты + Transform transform; + Rotate rotate; + Scale scale; + TRS& currentTool = transform; + + // Текстура для отражений скайбокса + TextureCube reflections_texture(skybox_texture); + reflections_texture.setType(9); + + // Пока не произойдет событие запроса закрытия окна + while(!glfwWindowShouldClose(window)) + { + // Загрузка данных о камере + cameraUB.loadSub(&Camera::current().getData(), sizeof(CameraData)); + // Загрузим информацию об источниках света + Light::upload(light_data); + // Загружаем информацию о направленном источнике + Sun::upload(sun_data); + + // Активируем G-кадра + gbuffer.use(); + // Используем шейдер с освещением + gShader.use(); + // Очистка буфера цвета и глубины + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // Тут производится рендер + scene.render(gShader, material_data, true); + rectangle.render(gShader, material_data); + + // Отрисовка отладочных лампочек со специальным шейдером + bulbShader.use(); + Light::render(bulbShader, material_data); + + // Используем шейдер для инструментов + toolsShader.use(); + // Рендерим инструменты для выбранного объекта + currentTool.render(selected.value, toolsShader, material_data); + + // Выбор объекта + if (mouse.left == 0100000) + { + glReadBuffer(GL_COLOR_ATTACHMENT4); + glReadPixels(mouse.x, WINDOW_HEIGHT-mouse.y, 1, 1, GL_RGB_INTEGER, GL_UNSIGNED_INT, &selected); + std::cout << (void*) selected.value << ' ' << selected.etc << '\n'; + } + + // Активируем буфер SSAO + ssaoBuffer.use(); + // Используем шейдер для расчета SSAO + ssaoShader.use(); + // Очистка буфера цвета + glClear(GL_COLOR_BUFFER_BIT); + // Подключаем текстуры G-буфера + gPosition.use(); + gNormal.use(); + // Подключаем текстуру шума для SSAO + noiseTexture.use(); + // Рендерим прямоугольник + quadModel.render(); + + // Активируем буфер размытия SSAO + ssaoBlurBuffer.use(); + // Используем шейдер для размытия SSAO + ssaoBlurShader.use(); + // Очистка буфера цвета + glClear(GL_COLOR_BUFFER_BIT); + // Подключаем текстуру сырого SSAO + ssaoTexture_raw.use(); + // Рендерим прямоугольник + quadModel.render(); + + // Изменим размер вывода для тени + glViewport(0, 0, sunShadow_resolution, sunShadow_resolution); + // Активируем буфер кадра для теней от солнца + sunShadowBuffer.use(); + // Подключим шейдер для расчета теней + sunShadowShader.use(); + // Очистка буфера глубины + glClear(GL_DEPTH_BUFFER_BIT); + // Рендерим геометрию в буфер глубины + scene.render(sunShadowShader, material_data); + rectangle.render(sunShadowShader, material_data); + + // Изменим размер вывода для стороны кубической карты точечного источника + glViewport(0, 0, pointShadow_resolution, pointShadow_resolution); + // Активируем буфер кадра для теней от солнца + pointShadowBuffer.use(); + // Подключим шейдер для расчета теней + pointShadowShader.use(); + // Очистка буфера глубины + glClear(GL_DEPTH_BUFFER_BIT); + // Для каждого источника вызывается рендер сцены + for (int i = 0; i < Light::getCount(); i++) + { + glUniform1i(pointShadowShader.getUniformLoc("light_i"), i); + // Рендерим геометрию в буфер глубины + scene.render(pointShadowShader, material_data); + rectangle.render(pointShadowShader, material_data); + } + + // Изменим размер вывода для окна + glViewport(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT); + // Активируем базовый буфер кадра + FBO::useDefault(); + // Подключаем шейдер для прямоугольника + lightShader.use(); + // Очистка буфера цвета и глубины + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + // Подключаем текстуры G-буфера + gPosition.use(); + gNormal.use(); + gBaseColor.use(); + gRMS.use(); + gID.use(); + gEmittedColor.use(); + reflections_texture.use(); + // Идентификатор выбранного объекта для обводки + glUniform3uiv(lightShader.getUniformLoc("selectedID"), 1, (GLuint*) &selected); + // Подключаем текстуры теней + sunShadowDepth.use(); + pointShadowDepth.use(); + // Подключим текстуру SSAO + ssaoTexture.use(); + // Рендерим прямоугольник с расчетом освещения + quadModel.render(); + + // Перенос буфера глубины + FBO::useDefault(GL_DRAW_FRAMEBUFFER); // Базовый в режиме записи + gbuffer.use(GL_READ_FRAMEBUFFER); // Буфер геометрии в режиме чтения + // Копирование значений глубины + glBlitFramebuffer(0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT, GL_DEPTH_BUFFER_BIT, GL_NEAREST); + FBO::useDefault(); // Использование базового буфера для дальнейших работ + + // Отрисовка скайбокса без записи глубины + glDepthMask(GL_FALSE); + // Используем шейдер для скайбокса + skyboxShader.use(); + // Подключаем текстуру скайбокса + skybox_texture.use(); + // Рендерим куб + skybox.render(); + // Возвращаем запись глубины + glDepthMask(GL_TRUE); + + // Дополнительная обработка мыши + process_mouse_button(mouse.left); + process_mouse_button(mouse.right); + mouse.prev_x = mouse.x; + mouse.prev_y = mouse.y; + + // Представление содержимого буфера цепочки показа на окно + glfwSwapBuffers(window); + // Обработка системных событий + glfwPollEvents(); + + // Поворот камеры + if (mouse.right & 0100000 + && mouse.x != mouse.prev_x + && mouse.y != mouse.prev_y) + Camera::current().rotate(glm::vec2(mouse.x - mouse.prev_x, mouse.prev_y - mouse.y)); + + // Взаимодействие с инструментом при зажатой левой кнопке + if (mouse.left > 0100000) + if (selected.etc) + currentTool.process(selected.value, selected.etc, glm::transpose(Camera::current().getVP()) * glm::vec4(mouse.x - mouse.prev_x, mouse.prev_y - mouse.y, 0, 1)); + } + + return 0; +}