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..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..2af7602 --- /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); // Конструктор камеры с проекцией перспективы + 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(); // Ссылка на текущую используемую камеру + + 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..5016a30 --- /dev/null +++ b/include/Model.h @@ -0,0 +1,99 @@ +#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); // Генерирует сферу заданного радиуса с определенным количеством сегментов + +// Класс узла сцены +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 ka; // коэф. фонового отражения (цвет фонового освещения) + alignas(16) glm::vec3 kd; // коэф. диффузного отражения (цвет объекта) + alignas(16) glm::vec3 ks; // коэф. зеркального блика + float p; // показатель глянцевости + // Значения по умолчанию + Material() : ka(0.2f), kd(0.2f), ks(0.2f), p(1) { }; +}; + +// Класс модели +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 set_index_range(size_t first_byteOffset, size_t count); // Ограничение диапазона из буфера индексов + void set_texture(Texture& texture); // Привязка текстуры к модели + + Material material; // Материал модели + 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; // Диффузная текстура + Texture texture_ambient; // Текстура фонового освщения + Texture texture_specular; // Текстура зеркального отражения +}; + +#endif // MODEL_H diff --git a/include/Scene.h b/include/Scene.h new file mode 100644 index 0000000..d01eae2 --- /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(ShaderProgram &shaderProgram, UBO &material_buffer); // Рендер сцены + + 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/Shader.h b/include/Shader.h new file mode 100644 index 0000000..0562dd7 --- /dev/null +++ b/include/Shader.h @@ -0,0 +1,30 @@ +#ifndef SHADER_H +#define SHADER_H + +#include + +#include +#include + +// Класс шейдерной программы +class ShaderProgram +{ + public: + ShaderProgram(); + ShaderProgram(const ShaderProgram ©); + ~ShaderProgram(); + ShaderProgram& operator=(const ShaderProgram& other); + + void use(); // Использование шейдеров + void load(GLuint type, const char* filename); // Функция для загрузки шейдеров + void link(); // Формирование программы из загруженных шейдеров + GLuint getUniformLoc(const char* name); // Возвращает местоположение uniform-переменной + void bindUniformBlock(const char* name, int binding); // Привязка uniform-блока + void bindTextures(const char* textures_base_shader_names[], int count); // Инициализация текстур на шейдере + private: + GLuint program; // Дескриптор + static std::map handler_count; // Получение количества использований по дескриптору шейдера (Shared pointer) + std::map uniformLocations; // Местоположения uniform-переменных +}; + +#endif // SHADER_H \ No newline at end of file diff --git a/include/Texture.h b/include/Texture.h new file mode 100644 index 0000000..17d30cc --- /dev/null +++ b/include/Texture.h @@ -0,0 +1,91 @@ +#ifndef TEXTURE_H +#define TEXTURE_H + +#include + +#include +#include + +enum TexType { + TEX_DIFFUSE, + TEX_AMBIENT, + TEX_SPECULAR, + 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_DIFFUSE, GLint internalformat = GL_RGBA, GLint format = GL_RGBA, GLenum dataType = GL_FLOAT); // Конструктор текстуры заданного размера для использования в буфере + Texture(GLuint width, GLuint height, void* data, GLuint texType = TEX_DIFFUSE, 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_DIFFUSE, 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_DIFFUSE, 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_DIFFUSE, 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_DIFFUSE, 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_DIFFUSE, 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_DIFFUSE, 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_DIFFUSE, 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..533af2b --- /dev/null +++ b/shaders/bulb.frag @@ -0,0 +1,30 @@ +#version 420 core + +layout(std140, binding = 1) uniform Material +{ + vec3 ka; + vec3 kd; + vec3 ks; + float p; +}; + +in vec3 pos_local; + +layout(std140, binding = 4) uniform gamma +{ + float inv_gamma; +}; + +out vec4 color; + +uniform float angle; +uniform vec3 direction; + +void main() +{ + float cosA = dot(normalize(pos_local), normalize(direction)); + if (degrees(acos(cosA)) <= angle) + color = vec4(pow(ka, vec3(inv_gamma)), 1); + else + discard; +} \ 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..4e35b24 --- /dev/null +++ b/shaders/gshader.frag @@ -0,0 +1,38 @@ +#version 420 core + +layout(std140, binding = 1) uniform Material +{ + vec3 ka; + vec3 kd; + vec3 ks; + float p; +}; + +layout (location = 0) out vec3 gPosition; +layout (location = 1) out vec3 gNormal; +layout (location = 2) out vec4 gDiffuseP; +layout (location = 3) out vec4 gAmbientSpecular; + +in vec3 vertex; // Позиция вершины в пространстве +in vec3 N; // Нормаль трансформированноая +in vec2 texCoord; // Текстурные координаты + +uniform sampler2D tex_diffuse; +uniform sampler2D tex_ambient; +uniform sampler2D tex_specular; + +void main() +{ + // Сохранение позиции фрагмента в G-буфере + gPosition = vertex; + // Сохранение нормали в G-буфере + gNormal = N; + // Сохранение диффузного цвета + gDiffuseP.rgb = texture(tex_diffuse, texCoord).rgb * kd; + // Сохранение глянцевости + gDiffuseP.a = p; + // Сохранение фоновой составляющей + gAmbientSpecular.rgb = texture(tex_ambient, texCoord).rgb * ka; + // Сохранение зеркальной составляющей + gAmbientSpecular.a = texture(tex_specular, texCoord).r * ks.r; +} \ No newline at end of file diff --git a/shaders/gshader.vert b/shaders/gshader.vert new file mode 100644 index 0000000..0d7ea72 --- /dev/null +++ b/shaders/gshader.vert @@ -0,0 +1,30 @@ +#version 420 core + +layout(location = 0) in vec3 pos; +layout(location = 1) in vec2 inTexCoord; +layout(location = 2) in vec3 normals; + +layout(std140, binding = 0) uniform Camera +{ + mat4 projection; + mat4 view; + vec3 position; +} camera; + +uniform mat4 model; + +out vec3 vertex; // Позиция вершины в пространстве +out vec3 N; // Нормаль трансформированноая +out vec2 texCoord; // Текстурные координаты + +void main() +{ + vec4 P = model * vec4(pos, 1.0); // трансформация вершины + vertex = P.xyz; + + N = normalize(mat3(model) * normals); // трансформация нормали + + texCoord = inTexCoord; // Текстурные координаты + + 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..87944d3 --- /dev/null +++ b/shaders/lighting.frag @@ -0,0 +1,201 @@ +#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 gDiffuseP; +uniform sampler2D gAmbientSpecular; +uniform sampler2DArray sunShadowDepth; +uniform samplerCubeArray pointShadowDepth; +uniform sampler2D ssao; + +layout(std140, binding = 4) uniform gamma +{ + float inv_gamma; +}; + +out vec4 color; + +void main() +{ + // Получим данные из текстур буфера + vec3 fragPos = texture(gPosition, texCoord).rgb; + vec3 N = texture(gNormal, texCoord).rgb; + vec3 kd = texture(gDiffuseP, texCoord).rgb; + vec3 ka = texture(gAmbientSpecular, texCoord).rgb; + float ks = texture(gAmbientSpecular, texCoord).a; + float p = texture(gDiffuseP, texCoord).a; + float ssao_value = texture(ssao, texCoord).r; + + // Переменные используемые в цикле: + vec3 L_vertex; // Данные об источнике относительно фрагмента + vec3 Cam_vertex = normalize(camera.position - fragPos); // Данные о камере относительно фрагмента + float diffuse; // Диффузная составляющая + vec3 H; // Вектор половины пути + float specular; // Зеркальная составляющая + float L_distance; // Расстояние от поверхности до источника + 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; // Индекс текущего каскада для вычисления теней + + // Определение индекса каскада в который попадает фрагмент (цикл на 1 меньше чем кол-во каскадов) + for (cascade_index = 0; cascade_index < 3; cascade_index++) + if (abs(fragPosCamSpace.z) < camera_cascade_distances[cascade_index]) + break; + + // Фоновая освещенность + color = vec4(ka, 1) * ssao_value; + + // Расчет солнца, если его цвет не черный + 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; + 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; + // Рассчитываем освещенность, если значение тени меньше 1 + if (shadowValue < 1.0) + { + // Данные об источнике относительно фрагмента + L_vertex = normalize(sun.direction); + // Диффузная составляющая + diffuse = max(dot(L_vertex, N), 0.0); // скалярное произведение с отсеканием значений < 0 + + // Вектор половины пути + H = normalize(L_vertex + Cam_vertex); + // Зеркальная составляющая + specular = pow(max(dot(H, N), 0.0), p*4); // скалярное произведение с отсеканием значений < 0 в степени p + // Результирующий цвет с учетом солнца + color += ( vec4(sun.color*kd*diffuse, 1) + + vec4(sun.color*ks*specular, 1) ) * (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, light_f.data[i].direction_angle.xyz)), 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.xyz)))); + // Если угол меньше угла источника или угол источника минимален, то считаем освещенность + if(acosA <= light_f.data[i].direction_angle.a) + { + // Диффузная составляющая + diffuse = max(dot(L_vertex, N), 0.0); // скалярное произведение с отсеканием значений < 0 + + // Вектор половины пути + H = normalize(L_vertex + Cam_vertex); + // Зеркальная составляющая + specular = pow(max(dot(H, N), 0.0), p*4); // скалярное произведение с отсеканием значений < 0 в степени p + + // Угасание с учетом расстояния + attenuation = 1 / (1 + light_f.data[i].attenuation[1] * L_distance + light_f.data[i].attenuation[2] * L_distance * L_distance); + + // Если источник - прожектор, то добавим смягчение + if (light_f.data[i].direction_angle.a < 180) + { + intensity = clamp((light_f.data[i].direction_angle.a - acosA) / 5, 0.0, 1.0); + diffuse *= intensity; + specular *= intensity; + } + + color += ( vec4(light_f.data[i].color*kd*diffuse * attenuation, 1) + + vec4(light_f.data[i].color*ks*specular * attenuation, 1) ) * (1.0 - shadowValue); + } + } + } + } + + // Применение гамма-коррекции + color.rgb = pow(color.rgb, vec3(inv_gamma)); +} \ 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/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..b2434bd --- /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) +: 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), +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) +{ + projection = glm::perspective(glm::radians(fovy), aspect, CAMERA_NEAR, CAMERA_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) +{ + const float aspect = width / height; + projection = glm::ortho(-1.0f, 1.0f, -1.0f/aspect, 1.0f/aspect, CAMERA_NEAR, CAMERA_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..303ae2e --- /dev/null +++ b/src/Lights.cpp @@ -0,0 +1,375 @@ +#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++) + { + // Загрузим направление + 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.ka = sphere.material.ka = 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; +} \ No newline at end of file diff --git a/src/Model.cpp b/src/Model.cpp new file mode 100644 index 0000000..510a668 --- /dev/null +++ b/src/Model.cpp @@ -0,0 +1,478 @@ +#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), texture_ambient(copy.texture_ambient), texture_specular(copy.texture_specular), +material(copy.material) +{ + +} + +// Оператор присваивания +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; + texture_ambient = other.texture_ambient; + texture_specular = other.texture_specular; + + 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, GL_UNSIGNED_INT, (void*)(first_index_byteOffset)); + } + // Если есть вершины - рисуем на основании массива вершин + else if (verteces_count) + glDrawArrays(GL_TRIANGLES, 0, verteces_count); +} + +// Вызов отрисовки +void Model::render(ShaderProgram &shaderProgram, UBO &material_buffer) +{ + // Расчитаем матрицу трансформации + glUniformMatrix4fv(shaderProgram.getUniformLoc("model"), 1, GL_FALSE, &this->getTransformMatrix()[0][0]); + + // Подключаем текстуры + texture_diffuse.use(); + texture_ambient.use(); + texture_specular.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) +{ + 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; + case TEX_AMBIENT: + texture_ambient = texture; + break; + case TEX_SPECULAR: + texture_specular = texture; + break; + }; +} + +// Генерирует сферу заданного радиуса с определенным количеством сегментов +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; +} diff --git a/src/Scene.cpp b/src/Scene.cpp new file mode 100644 index 0000000..5e56492 --- /dev/null +++ b/src/Scene.cpp @@ -0,0 +1,227 @@ +#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(ShaderProgram &shaderProgram, UBO &material_buffer) +{ + 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)); +} + +// Перестройка дерева после копирования или присваивания +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; + + // Значение гамма-коррекции + 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; // текстурные координаты + 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); + Texture ambient(TEX_AMBIENT, texture_directory + materials[materials_ids[i]].ambient_texname); + s->set_texture(ambient); + Texture specular(TEX_SPECULAR, texture_directory + materials[materials_ids[i]].specular_texname); + s->set_texture(specular); + + // Материал + s->material.ka = pow(glm::vec3(materials[materials_ids[i]].ambient[0], materials[materials_ids[i]].ambient[1], materials[materials_ids[i]].ambient[2]), glm::vec3(1/inv_gamma)); + s->material.kd = 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.ks = glm::vec3(materials[materials_ids[i]].specular[0], materials[materials_ids[i]].specular[1], materials[materials_ids[i]].specular[2]); + s->material.p = (materials[materials_ids[i]].shininess > 0.0f) ? 1000.0f / materials[materials_ids[i]].shininess : 1000.0f; + } + + 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/Texture.cpp b/src/Texture.cpp new file mode 100644 index 0000000..28817ae --- /dev/null +++ b/src/Texture.cpp @@ -0,0 +1,404 @@ +#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) + { + // Загрузка данных с учетом прозрачности + if (channels == 3) // RGB + glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image); + else if (channels == 4) // RGBA + glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB_ALPHA, 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(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) + { + // Загрузка данных с учетом прозрачности + if (channels == 3) // RGB + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_SRGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image); + else if (channels == 4) // RGBA + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_SRGB_ALPHA, width, height, 0, GL_RGBA, 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); + 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); + + 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..93ac2f6 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,559 @@ + +#include +#include +#include + +#include +#include + +#include "Scene.h" +#include "Shader.h" +#include "Lights.h" + +#define WINDOW_CAPTION "OPENGL notes on rekovalev.site" + +// Указатели на текстуры для изменения размеров окна +Texture* pgPosition = NULL; +Texture* pgNormal = NULL; +Texture* pgDiffuseP = NULL; +Texture* pgAmbientSpecular = 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 (pgDiffuseP) + pgDiffuseP->reallocate(width, height, 2, GL_RGBA16F); + if (pgAmbientSpecular) + pgAmbientSpecular->reallocate(width, height, 3); + // И буфера глубины + 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); +} + +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); + + // Шейдер для 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_diffuse", "tex_ambient", "tex_specular"}; + gShader.bindTextures(textures_base_shader_names, sizeof(textures_base_shader_names)/sizeof(const char*)); + + // Загрузка сцены из obj файла + Scene scene = loadOBJtoScene("../resources/models/blob.obj", "../resources/models/", "../resources/textures/"); + scene.root.e_scale() = glm::vec3(0.01); + scene.root.e_position().z = 1; + scene.models.begin()->material.kd = {0.5,0.5,0.5}; + scene.models.begin()->material.ka = {0.05,0.05,0.05}; + + // Установка цвета очистки буфера цвета + 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 }; + 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 gDiffuseP(WINDOW_WIDTH, WINDOW_HEIGHT, GL_COLOR_ATTACHMENT2, 2, GL_RGBA16F); // Диффузная составляющая и коэф. глянцевости + Texture gAmbientSpecular(WINDOW_WIDTH, WINDOW_HEIGHT, GL_COLOR_ATTACHMENT3, 3); // Фоновая составляющая и один канал зеркальной + // Создадим буфер рендера под буфер глубины и привяжем его + RBO grbo(WINDOW_WIDTH, WINDOW_HEIGHT); + gbuffer.assignRenderBuffer(grbo.getHandler()); + // Активируем базовый буфер кадра + FBO::useDefault(); + + // Сохраним указатели на текстуры для изменения размеров окна + pgPosition = &gPosition; + pgNormal = &gNormal; + pgDiffuseP = &gDiffuseP; + pgAmbientSpecular = &gAmbientSpecular; + pgrbo = &grbo; + + // Шейдер для расчета освещенности + 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", "gDiffuseP", "gAmbientSpecular", "sunShadowDepth", "pointShadowDepth", "ssao"}; + 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(); + + // Модель прямоугольника + Model rectangle; + + // Вершины прямоугольника + glm::vec3 rectangle_verticies[] = { {-0.5f, 0.0f, -0.5f} + , { 0.5f, 0.0f, -0.5f} + , { 0.5f, 0.0f, 0.5f} + , {-0.5f, 0.0f, 0.5f} + }; + // Загрузка вершин модели + rectangle.load_verteces(rectangle_verticies, sizeof(rectangle_verticies)/sizeof(glm::vec3)); + + // индексы вершин + GLuint rectangle_indices[] = {0, 1, 2, 2, 3, 0}; + // Загрузка индексов модели + rectangle.load_indices(rectangle_indices, sizeof(rectangle_indices)/sizeof(GLuint)); + + // Нормали + glm::vec3 rectangle_normals[] = { {0.0f, 1.0f, 0.0f} + , {0.0f, 1.0f, 0.0f} + , {0.0f, 1.0f, 0.0f} + , {0.0f, 1.0f, 0.0f} + }; + // Загрузка нормалей модели + rectangle.load_normals(rectangle_normals, sizeof(rectangle_normals)/sizeof(glm::vec3)); + + // Зададим горизонтальное положение перед камерой + rectangle.e_position().y = -1; + rectangle.e_position().z = 2; + rectangle.e_scale() = glm::vec3(4); + + // Параметры материала + rectangle.material.ka = {0.05, 0.05, 0.05}; + rectangle.material.kd = {1, 1, 1}; + + // Шейдер для рисования отладочных лампочек + 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_DIFFUSE, { "../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); + + // Пока не произойдет событие запроса закрытия окна + 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); + rectangle.render(gShader, material_data); + + // Активируем буфер 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(); + gDiffuseP.use(); + gAmbientSpecular.use(); + // Подключаем текстуры теней + 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); + + // Отрисовка отладочных лампочек со специальным шейдером + bulbShader.use(); + Light::render(bulbShader, material_data); + + // Представление содержимого буфера цепочки показа на окно + glfwSwapBuffers(window); + // Обработка системных событий + glfwPollEvents(); + } + + return 0; +}