diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..04cf9c6 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,20 @@ +{ + "configurations": [ + { + "name": "some_name", + "includePath": [ + "${workspaceFolder}/include", + "${workspaceFolder}/../dependencies/GLFW/include", + "${workspaceFolder}/../dependencies/glad/include", + "${workspaceFolder}/../dependencies/glm", + "${workspaceFolder}/../dependencies/stb", + "${workspaceFolder}/../dependencies/tinyobjloader" + ], + "compilerPath": "C:/MinGW/bin/g++.exe", + "cStandard": "c11", + "cppStandard": "c++11", + "intelliSenseMode": "gcc-x86" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..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..c270acc --- /dev/null +++ b/Makefile @@ -0,0 +1,101 @@ +# Компилятор и директория проекта +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 + +# Опции линкера +LDFLAGS += --std=c++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/Buffers.h b/include/Buffers.h new file mode 100644 index 0000000..999dba4 --- /dev/null +++ b/include/Buffers.h @@ -0,0 +1,50 @@ +#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 + }; + +// Объект вершинного буфера +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; // Счетчик использований дескриптора +}; + +#endif // BUFFERS_H \ No newline at end of file diff --git a/include/Camera.h b/include/Camera.h new file mode 100644 index 0000000..73840de --- /dev/null +++ b/include/Camera.h @@ -0,0 +1,62 @@ +#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 + +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); // Конструктор камеры с проекцией перспективы + Camera(float width, float height, const glm::vec3 &position = glm::vec3(0.0f), const glm::vec3 &initialRotation = CAMERA_DEFAULT_ROTATION); // Конструктор ортографической камеры + 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); // Устанавливает заданную матрицу перспективы + void setOrtho(float width, float height); // Устанавливает заданную ортографическую матрицу + void setSensitivity(float sensitivity); // Изменяет чувствительность мыши + const float& getSensitivity() const; // Возвращает чувствительность мыши + + void use(); // Использование этой камеры как текущей + static Camera& current(); // Ссылка на текущую используемую камеру + protected: + Camera(const glm::vec3 &position, const glm::vec3 &initialRotation); // Защищенный (protected) конструктор камеры без перспективы + + glm::mat4 view; // Матрица вида + glm::mat4 projection; // Матрица проекции + glm::mat4 vp; // Матрица произведения вида и проекции + bool requiredRecalcVP; // Необходимость пересчета матрицы вида и проекции камеры + + float sensitivity; // Чувствительность мыши + + virtual void recalcMatrices(); // Метод пересчета матрицы вида и произведения Вида*Проекции по необходимости, должен сбрасывать флаг changed + + static Camera* p_current; // Указатель на текущую используемую камеру +}; + + +#endif // CAMERA_H \ No newline at end of file diff --git a/include/Model.h b/include/Model.h new file mode 100644 index 0000000..907a13d --- /dev/null +++ b/include/Model.h @@ -0,0 +1,81 @@ +#ifndef MODEL_H +#define MODEL_H + +#include "Buffers.h" +#include "Texture.h" + +#include +#include +#include + +#include + +// Класс узла сцены +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 +}; + +// Класс модели +class Model : public Node +{ + public: + Model(Node *parent = NULL); // Конструктор по умолчанию + Model(const Model& copy); // Конструктор копирования + Model& operator=(const Model& other); // Оператор присваивания + virtual ~Model(); + + void render(const GLuint &model_uniform); // Вызов отрисовки + + void load_verteces(glm::vec3* verteces, GLuint count); // Загрузка вершин в буфер + void load_indices(GLuint* indices, GLuint count); // Загрузка индексов в буфер + void load_texCoords(glm::vec2* texCoords, GLuint count); // Загрузка текстурных координат в буфер + void load_normals(glm::vec3* normals, GLuint count); // Загрузка нормалей в буфер + void set_index_range(size_t first_byteOffset, size_t count); // Ограничение диапазона из буфера индексов + void set_texture(Texture& texture); // Привязка текстуры к модели + + private: + VAO vao; + BO vertex_vbo, index_vbo; // вершинный и индексный буферы + BO normals_vbo, texCoords_vbo; // буферы с нормалями и текстурными координатами + GLuint verteces_count; // Количество вершин + size_t first_index_byteOffset, indices_count; // Сдвиг в байтах для первого и количество индексов + Texture texture_diffuse; // Диффузная текстура +}; + +#endif // MODEL_H diff --git a/include/Scene.h b/include/Scene.h new file mode 100644 index 0000000..0639c13 --- /dev/null +++ b/include/Scene.h @@ -0,0 +1,37 @@ +#ifndef SCENE_H +#define SCENE_H + +#include + +#include "Model.h" +#include "Camera.h" + +#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 +{ + public: + Scene(); // Конструктор пустой сцены + Scene(const Scene ©); // Конструктор копирования + Scene& operator=(const Scene& other); // Оператор присваивания + + void render(const GLuint &model_uniform); // Рендер сцены + + Node root; // Корневой узел + + // Списки объектов, выступающих узлами + std::list nodes; // Список пустых узлов + std::list models; // Список моделей для рендера + std::list cameras; // Список камер + + 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); // Сдвигает родителя узла между двумя списками при условии его принадлежности к оригинальному +}; + +#endif // SCENE_H diff --git a/include/Texture.h b/include/Texture.h new file mode 100644 index 0000000..650ee11 --- /dev/null +++ b/include/Texture.h @@ -0,0 +1,34 @@ +#ifndef TEXTURE_H +#define TEXTURE_H + +#include + +#include +#include + +enum TexType { + TEX_DIFFUSE, + TEX_AVAILABLE_COUNT +}; + +class Texture +{ + public: + Texture(GLuint type = TEX_AVAILABLE_COUNT, const std::string& filename = ""); // Загрузка текстуры с диска или использование "пустой" + Texture(const Texture& other); // Конструктор копирования + ~Texture(); + + Texture& operator=(const Texture& other); // Оператор присваивания + + static void init_textures(GLuint programID); // Инициализация текстур на шейдере + void use(); // Привязка текстуры + static void disable(GLuint type); // Отвязка текстуры по типу + GLuint getType(); // Возвращает тип текстуры + private: + GLuint handler; // Дескриптор текстуры + GLuint type; // Тип текстуры, соответствует её слоту + static std::map filename_handler; // Получение дескриптора текстуры по её имени + static std::map handler_count; // Получение количества использований по дескриптору текстуры (Shared pointer) +}; + +#endif // TEXTURE_H diff --git a/shaders/shader.frag b/shaders/shader.frag new file mode 100644 index 0000000..801807d --- /dev/null +++ b/shaders/shader.frag @@ -0,0 +1,13 @@ +#version 330 core + +in vec2 texCoord; + +uniform sampler2D tex_diffuse; + +out vec4 color; + +void main() +{ + color = texture(tex_diffuse, texCoord); +} + \ No newline at end of file diff --git a/shaders/shader.vert b/shaders/shader.vert new file mode 100644 index 0000000..4947387 --- /dev/null +++ b/shaders/shader.vert @@ -0,0 +1,16 @@ +#version 330 core + +layout(location = 0) in vec3 pos; +layout(location = 1) in vec2 inTexCoord; +layout(location = 2) in vec3 normals; + +uniform mat4 vp; +uniform mat4 model; + +out vec2 texCoord; + +void main() +{ + gl_Position = vp * model * vec4(pos, 1.0); + texCoord = inTexCoord; +} diff --git a/src/Buffers.cpp b/src/Buffers.cpp new file mode 100644 index 0000000..cb47cd2 --- /dev/null +++ b/src/Buffers.cpp @@ -0,0 +1,119 @@ +#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); // Привязка элементного буфера +} diff --git a/src/Camera.cpp b/src/Camera.cpp new file mode 100644 index 0000000..6fb0fb2 --- /dev/null +++ b/src/Camera.cpp @@ -0,0 +1,183 @@ +#include "Camera.h" + +// Указатель на текущую используемую камеру +Camera* Camera::p_current = NULL; + +// Защищенный (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) +: Camera(position, initialRotation) +{ + setPerspective(fovy, aspect); +} + +// Конструктор ортографической камеры +Camera::Camera(float width, float height, const glm::vec3 &position, const glm::vec3 &initialRotation) +: Camera(position, initialRotation) +{ + setOrtho(width, height); +} + +// Конструктор копирования камеры +Camera::Camera(const Camera& copy) +: Node(copy), projection(copy.projection), requiredRecalcVP(copy.requiredRecalcVP), sensitivity(copy.sensitivity) +{ + // Если у оригинала не было изменений - перепишем матрицу вида-проекции + 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) +{ + projection = glm::perspective(glm::radians(fovy), aspect, CAMERA_NEAR, CAMERA_FAR); + requiredRecalcVP = true; +} + +// Устанавливает заданную ортографическую матрицу +void Camera::setOrtho(float width, float height) +{ + const float aspect = width / height; + projection = glm::ortho(-1.0f, 1.0f, -1.0f/aspect, 1.0f/aspect, CAMERA_NEAR, CAMERA_FAR); + requiredRecalcVP = true; +} + +// Изменяет чувствительность мыши +void Camera::setSensitivity(float sens) +{ + sensitivity = sens; +} + +// Возвращает чувствительность мыши +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; + 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; +} diff --git a/src/Model.cpp b/src/Model.cpp new file mode 100644 index 0000000..74254af --- /dev/null +++ b/src/Model.cpp @@ -0,0 +1,381 @@ +#include "Model.h" + +#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), +vertex_vbo(VERTEX), index_vbo(ELEMENT), normals_vbo(VERTEX), texCoords_vbo(VERTEX) +{ + +} + +// Конструктор копирования +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), +vertex_vbo(copy.vertex_vbo), index_vbo(copy.index_vbo), normals_vbo(copy.normals_vbo), texCoords_vbo(copy.texCoords_vbo), +texture_diffuse(copy.texture_diffuse) +{ + +} + +// Оператор присваивания +Model& Model::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; + + texture_diffuse = other.texture_diffuse; + + return *this; +} + +Model::~Model() +{ + +} + +// Вызов отрисовки +void Model::render(const GLuint &model_uniform) +{ + // Загрузим матрицу трансформации + glUniformMatrix4fv(model_uniform, 1, GL_FALSE, &getTransformMatrix()[0][0]); + + // Подключаем текстуры + texture_diffuse.use(); + + // Подключаем VAO + vao.use(); + // Если есть индексы - рисуем с их использованием + if (indices_count) + { + index_vbo.use(); + glDrawElements(GL_TRIANGLES, indices_count, GL_UNSIGNED_INT, (void*)(first_index_byteOffset)); + } + // Если есть вершины - рисуем на основании массива вершин + else if (verteces_count) + glDrawArrays(GL_TRIANGLES, 0, verteces_count); +} + +// Функция для конфигурации атрибута вершинного буфера +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) +{ + first_index_byteOffset = first_byteOffset; + indices_count = count; +} + +// Привязка текстуры к модели +void Model::set_texture(Texture& texture) +{ + GLuint type = texture.getType(); + switch(type) + { + case TEX_DIFFUSE: + texture_diffuse = texture; + break; + }; +} diff --git a/src/Scene.cpp b/src/Scene.cpp new file mode 100644 index 0000000..701c261 --- /dev/null +++ b/src/Scene.cpp @@ -0,0 +1,213 @@ +#include "Scene.h" + +// Конструктор пустой сцены +Scene::Scene() +{ + +} + +// Конструктор копирования +Scene::Scene(const Scene ©): root(copy.root), +nodes(copy.nodes), models(copy.models), cameras(copy.cameras) +{ + rebuld_tree(copy); +} + +// Оператор присваивания +Scene& Scene::operator=(const Scene& other) +{ + root = other.root; + nodes = other.nodes; + models = other.models; + cameras = other.cameras; + + rebuld_tree(other); + + return *this; +} + +// Рендер сцены +void Scene::render(const GLuint &model_uniform) +{ + for (auto & model : models) + model.render(model_uniform); +} + +// Перестройка узлов выбранного списка +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)); +} + +// Перестройка дерева после копирования или присваивания +void Scene::rebuld_tree(const Scene& from) +{ + // Восстановим родителей в пустых узлах для копии + rebuild_Nodes_list(nodes, from); + rebuild_Nodes_list(models, from); + rebuild_Nodes_list(cameras, from); +} + +#define TINYOBJLOADER_IMPLEMENTATION +#include "tiny_obj_loader.h" + +#include + +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; + + // Если в процессе загрузки возникли ошибки - выдадим исключение + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename, mtl_directory)) + throw std::runtime_error(err); + + std::vector indices; // индексы модели + std::vector verteces; // вершины + std::vector normals; // нормали + std::vector texCords; // текстурные координаты + size_t hash; // Для уникальных вершин + std::map uniqueVerteces; // словарь для уникальных вершин: ключ - хеш, значение - индекс вершины + + int last_material_index = 0; // индекс последнего материала (для группировки моделей) + int count = 0, offset; // для индексов начала и конца в индексном буфере + std::vector materials_range; // хранилище индексов + std::vector materials_ids; // индексы материалов + + materials_range.push_back(count); // Закидываем начало отрезка в индексном буфере + // Цикл по считанным моделям + for (const auto& shape : shapes) + { + offset = count; // Переменная для + last_material_index = shape.mesh.material_ids[(count - offset)/3]; // Запоминаем индекс материала + + // Цикл по индексам модели + for (const auto& index : shape.mesh.indices) + { + hash = 0; + hash_combine( hash + , attrib.vertices[3 * index.vertex_index + 0], attrib.vertices[3 * index.vertex_index + 1], attrib.vertices[3 * index.vertex_index + 2] + , attrib.normals[3 * index.normal_index + 0], attrib.normals[3 * index.normal_index + 1], attrib.normals[3 * index.normal_index + 2] + , attrib.texcoords[2 * index.texcoord_index + 0], attrib.texcoords[2 * index.texcoord_index + 1]); + + if (!uniqueVerteces.count(hash)) + { + uniqueVerteces[hash] = verteces.size(); + + // группируем вершины в массив на основании индексов + verteces.push_back({ attrib.vertices[3 * index.vertex_index + 0] + , attrib.vertices[3 * index.vertex_index + 1] + , attrib.vertices[3 * index.vertex_index + 2] + }); + // группируем нормали в массив на основании индексов + normals.push_back({ attrib.normals[3 * index.normal_index + 0] + , attrib.normals[3 * index.normal_index + 1] + , attrib.normals[3 * index.normal_index + 2] + }); + // группируем текстурные координаты в массив на основании индексов + texCords.push_back({ attrib.texcoords[2 * index.texcoord_index + 0] + , 1-attrib.texcoords[2 * index.texcoord_index + 1] + }); + } + // Сохраняем индекс в массив + indices.push_back(uniqueVerteces[hash]); + + // Если индекс последнего материала изменился, то необходимо сохранить его + if (last_material_index != shape.mesh.material_ids[(count - offset)/3]) + { + materials_range.push_back(count); // как конец отрезка + materials_ids.push_back(last_material_index); // как используемый материал + last_material_index = shape.mesh.material_ids[(count - offset)/3]; + } + count++; + } // for (const auto& index : shape.mesh.indices) + + // Если последний материал не загружен - загружаем его + if (materials_range[materials_range.size()-1] != count-1) + { + materials_range.push_back(count); // последний конец отрезка + materials_ids.push_back(last_material_index); // последний используемый материал + } + } // for (const auto& shape : shapes) + + + + // Загрузка в буферы + model.load_verteces (&verteces[0], verteces.size()); + model.load_normals (&normals[0], normals.size()); + model.load_texCoords(&texCords[0], texCords.size()); + // Загрузка индексного буфера + model.load_indices (&indices[0], indices.size()); + + + // Создаем копии модели, которые будут рендериться в заданном диапазоне + // И присваиваем текстуры копиям на основании материала + for (int i = 0; i < materials_range.size()-1; i++) + { + result.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]); + + Texture diffuse(TEX_DIFFUSE, texture_directory + materials[materials_ids[i]].diffuse_texname); + s->set_texture(diffuse); + } + + return result; +} diff --git a/src/Texture.cpp b/src/Texture.cpp new file mode 100644 index 0000000..181db4b --- /dev/null +++ b/src/Texture.cpp @@ -0,0 +1,129 @@ +#include "Texture.h" + +#define STB_IMAGE_IMPLEMENTATION +#include + +std::map Texture::filename_handler; // Получение дескриптора текстуры по её имени +std::map Texture::handler_count; // Получение количества использований по дескриптору текстуры (Shared pointer) + +// Загрузка текстуры с диска или использование "пустой" +Texture::Texture(GLuint t, const std::string& filename) : type(t) +{ + if (!filename_handler.count(filename)) + { + std::string empty = ""; + int width, height, channels; // Ширина, высота и цветовые каналы текстуры + unsigned char* image = stbi_load(filename.c_str(), &width, &height, &channels, STBI_default); // Загрузка в оперативную память изображения + // Если изображение успешно счиитано с диска или отсутствует пустая текстура + if (image || !filename_handler.count(empty)) + { + glActiveTexture(type + GL_TEXTURE0); + glGenTextures(1, &handler); // Генерация одной текстуры + glBindTexture(GL_TEXTURE_2D, handler); // Привязка текстуры как активной + + filename_handler[filename] = handler; // Запоминим её дескриптор для этого имени файла + handler_count[handler] = 0; // Создадим счетчик использований дескриптора, который будет изменен в конце + + // Если изображение успешно считано + if (image) + { + // Загрузка данных с учетом прозрачности + if (channels == 3) // RGB + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image); + else if (channels == 4) // RGBA + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image); + + glGenerateMipmap(GL_TEXTURE_2D); // Генерация мипмапа для активной текстуры + glBindTexture(GL_TEXTURE_2D, 0); // Отвязка активной текстуры + + stbi_image_free(image); // Освобождение оперативной памяти + } + // Иначе изображение не считано и надо создать пустую текстуру + else + { + image = new unsigned char[3] {255,255,255}; // RGB по 1 байту на + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, image); // Загрузка данных на видеокарту + delete[] image; // Освобождение оперативной памяти + + filename_handler[empty] = handler; // Запоминим дополнительно её дескриптор для NULL-строки + } + } + // Иначе используем существующую пустую текстуру (текстура не загружена, пустую создавать не нужно) + else + handler = filename_handler[empty]; + } + // Иначе используем уже существующую по имени файла + else + handler = filename_handler[filename]; + + handler_count[handler]++; +} + +// Конструктор копирования +Texture::Texture(const Texture& other) : handler(other.handler), type(other.type) +{ + // Делаем копию и увеличиваем счетчик + handler_count[handler]++; +} + +// Оператор присваивания +Texture& Texture::operator=(const Texture& other) +{ + // Если это разные текстуры + if (handler != other.handler) + { + this->~Texture(); // Уничтожаем имеющуюся + // Заменяем новой + handler = other.handler; + handler_count[handler]++; + } + type = other.type; + + return *this; +} + +Texture::~Texture() +{ + if (!--handler_count[handler]) // Если количество ссылок = 0 + { + glDeleteTextures(1, &handler); // Удаление текстуры + // Удаление из словаря имен файлов и дескрипторов + for (auto it = filename_handler.begin(); it != filename_handler.end();) + { + if (it->second == handler) + it = filename_handler.erase(it); + else + it++; + } + } +} + +const char* textures_base_shader_names[] = {"tex_diffuse"}; + +// Инициализация текстур на шейдере +void Texture::init_textures(GLuint programID) +{ + // Цикл по всем доступным текстурам + for (int i = 0; i < TEX_AVAILABLE_COUNT; i++) + glUniform1i(glGetUniformLocation(programID, textures_base_shader_names[i]), i); +} + +// Привязка текстуры +void Texture::use() +{ + glActiveTexture(type + GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, handler); // Привязка текстуры как активной +} + +// Отвязка текстуры по типу +void Texture::disable(GLuint type) +{ + glActiveTexture(type + GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, 0); // Отвязка текстуры +} + +// Возвращает тип текстуры +GLuint Texture::getType() +{ + return type; +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100755 index 0000000..2d4c102 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,222 @@ + +#include +#include +#include + +#include + +#include "Scene.h" + +#define WINDOW_WIDTH 800 +#define WINDOW_HEIGHT 600 +#define WINDOW_CAPTION "OPENGL notes on rekovalev.site" + +// Функция-callback для изменения размеров буфера кадра в случае изменения размеров поверхности окна +void framebuffer_size_callback(GLFWwindow* window, int width, int height) +{ + glViewport(0, 0, width, height); +} + +#include +#include + +// Функция чтения шейдера из файла +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; +} + +// Функция для загрузки шейдеров +// Принимает два адреса вершинного и фрагментного шейдеров +// Возвращает дескриптор шейдерной программы +GLuint LoadShaders(const char *vertex_file, const char *fragment_file) +{ + // Создание дескрипторов вершинного и фрагментного шейдеров + GLuint vertexShaderID = glCreateShader(GL_VERTEX_SHADER); + GLuint fragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER); + + // Переменные под результат компиляции + GLint result = GL_FALSE; + int infoLogLength; + + // Считываем текст вершинного шейдера + std::string code = readFile(vertex_file); + const char* pointer = code.c_str(); // Преобразование к указателю на const char, так как функция принимает массив си-строк + + // Компиляция кода вершинного шейдера + glShaderSource(vertexShaderID, 1, &pointer, NULL); + glCompileShader(vertexShaderID); + + // Проверка результата компиляции + glGetShaderiv(vertexShaderID, GL_COMPILE_STATUS, &result); + glGetShaderiv(vertexShaderID, GL_INFO_LOG_LENGTH, &infoLogLength); + if (infoLogLength > 0) + { + char* errorMessage = new char[infoLogLength + 1]; + glGetShaderInfoLog(vertexShaderID, infoLogLength, NULL, errorMessage); + std::cout << errorMessage; + delete[] errorMessage; + } + + // Считываем текст вершинного шейдера + code = readFile(fragment_file); + pointer = code.c_str(); // Преобразование к указателю на const char, так как функция принимает массив си-строк + + // Компиляция кода фрагментного шейдера + glShaderSource(fragmentShaderID, 1, &pointer, NULL); + glCompileShader(fragmentShaderID); + + // Проверка результата компиляции + glGetShaderiv(fragmentShaderID, GL_COMPILE_STATUS, &result); + glGetShaderiv(fragmentShaderID, GL_INFO_LOG_LENGTH, &infoLogLength); + if (infoLogLength > 0) + { + char* errorMessage = new char[infoLogLength + 1]; + glGetShaderInfoLog(fragmentShaderID, infoLogLength, NULL, errorMessage); + std::cout << errorMessage; + delete[] errorMessage; + } + + // Привязка скомпилированных шейдеров + GLuint programID = glCreateProgram(); + glAttachShader(programID, vertexShaderID); + glAttachShader(programID, fragmentShaderID); + glLinkProgram(programID); + + // Проверка программы + glGetProgramiv(programID, GL_LINK_STATUS, &result); + glGetProgramiv(programID, GL_INFO_LOG_LENGTH, &infoLogLength); + if (infoLogLength > 0) + { + char* errorMessage = new char[infoLogLength + 1]; + glGetProgramInfoLog(programID, infoLogLength, NULL, errorMessage); + std::cout << errorMessage; + delete[] errorMessage; + } + + // Освобождение дескрипторов шейдеров + glDeleteShader(vertexShaderID); + glDeleteShader(fragmentShaderID); + + return programID; +} + + +bool firstMouse = true; +float lastX, lastY; + +void mouse_callback(GLFWwindow* window, double xpos, double ypos) +{ + if (firstMouse) + { + lastX = xpos; + lastY = ypos; + firstMouse = false; + } + + glm::vec2 offset(xpos - lastX, lastY - ypos); + lastX = xpos; + lastY = ypos; + + Camera::current().rotate(offset); +} + +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); + + // Загрузка функций OpenGL с помощью GLAD + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) + { + std::cout << "GLAD load GL error\n"; + return -1; + } + + // Включаем проверку по буферу глубины + glEnable(GL_DEPTH_TEST); + + // Компиляция шейдеров + GLuint shaderProgram = LoadShaders("shaders/shader.vert", "shaders/shader.frag"); + // Активация шейдера + glUseProgram(shaderProgram); + // Установим значения текстур + Texture::init_textures(shaderProgram); + + // Загрузка сцены из obj файла + Scene scene = loadOBJtoScene("../resources/models/cubes.obj", "../resources/models/", "../resources/textures/"); + + // Установка цвета очистки буфера цвета + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + + // Расположение Uniform-переменной + GLuint vp_uniform = glGetUniformLocation(shaderProgram, "vp"); + GLuint model_uniform = glGetUniformLocation(shaderProgram, "model"); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); // Использование уменьшенных версий mipmap + + // Пока не произойдет событие запроса закрытия окна + while(!glfwWindowShouldClose(window)) + { + // Загрузим матрицу проекции*вида + glUniformMatrix4fv(vp_uniform, 1, GL_FALSE, &Camera::current().getVP()[0][0]); + + // Очистка буфера цвета + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // Тут производится рендер + scene.render(model_uniform); + + // Представление содержимого буфера цепочки показа на окно + glfwSwapBuffers(window); + // Обработка системных событий + glfwPollEvents(); + } + + // Удаление шейдерной программы + glDeleteProgram(shaderProgram); + + return 0; +}