From aca1afba90c159a99ee2b030da35c474b92f8547 Mon Sep 17 00:00:00 2001 From: "re.kovalev" Date: Tue, 20 Dec 2022 15:50:32 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9A=D0=BE=D0=BF=D0=B8=D1=8F=20=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D0=B5=D0=BA=D1=82=D0=B0=20=D1=81=2009?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/c_cpp_properties.json | 20 ++ .vscode/settings.json | 48 ++++ .vscode/tasks.json | 79 +++++++ include/Buffers.h | 88 +++++++ include/Camera.h | 57 +++++ include/Lights.h | 29 +++ include/Model.h | 76 ++++++ include/Shader.h | 30 +++ include/Texture.h | 37 +++ shaders/bulb.frag | 16 ++ shaders/bulb.vert | 17 ++ shaders/gshader.frag | 38 +++ shaders/gshader.vert | 30 +++ shaders/lighting.frag | 88 +++++++ shaders/quad.vert | 11 + src/Buffers.cpp | 196 ++++++++++++++++ src/Camera.cpp | 149 ++++++++++++ src/Lights.cpp | 59 +++++ src/Model.cpp | 429 ++++++++++++++++++++++++++++++++++ src/Shader.cpp | 154 ++++++++++++ src/Texture.cpp | 142 +++++++++++ src/main.cpp | 226 ++++++++++++++++++ 22 files changed, 2019 insertions(+) create mode 100644 .vscode/c_cpp_properties.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 include/Buffers.h create mode 100644 include/Camera.h create mode 100644 include/Lights.h create mode 100644 include/Model.h create mode 100644 include/Shader.h create mode 100644 include/Texture.h create mode 100644 shaders/bulb.frag create mode 100644 shaders/bulb.vert create mode 100644 shaders/gshader.frag create mode 100644 shaders/gshader.vert create mode 100644 shaders/lighting.frag create mode 100644 shaders/quad.vert create mode 100644 src/Buffers.cpp create mode 100644 src/Camera.cpp create mode 100644 src/Lights.cpp create mode 100644 src/Model.cpp create mode 100644 src/Shader.cpp create mode 100644 src/Texture.cpp create mode 100644 src/main.cpp diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..04cf9c6 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,20 @@ +{ + "configurations": [ + { + "name": "some_name", + "includePath": [ + "${workspaceFolder}/include", + "${workspaceFolder}/../dependencies/GLFW/include", + "${workspaceFolder}/../dependencies/glad/include", + "${workspaceFolder}/../dependencies/glm", + "${workspaceFolder}/../dependencies/stb", + "${workspaceFolder}/../dependencies/tinyobjloader" + ], + "compilerPath": "C:/MinGW/bin/g++.exe", + "cStandard": "c11", + "cppStandard": "c++11", + "intelliSenseMode": "gcc-x86" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..e7216c1 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,48 @@ +{ + "files.associations": { + "fstream": "cpp", + "iosfwd": "cpp", + "map": "cpp", + "atomic": "cpp", + "new": "cpp", + "array": "cpp", + "*.tcc": "cpp", + "cctype": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "deque": "cpp", + "unordered_map": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "random": "cpp", + "string": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "initializer_list": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "ostream": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "streambuf": "cpp", + "cinttypes": "cpp", + "typeinfo": "cpp" + } +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..6afc88d --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,79 @@ +{ + "tasks": [ + { + "type": "cppbuild", + "label": "C/C++: g++.exe сборка активного файла", + "command": "C:/MinGW/bin/g++.exe", + "args": [ + "-fdiagnostics-color=always", + "${workspaceRoot}/src/*.cpp", + "${workspaceRoot}/../dependencies/glad/src/glad.c", + + "-I${workspaceRoot}/include", + + "--std=c++11", + + "-I${workspaceRoot}/../dependencies/GLFW/include", + "-L${workspaceRoot}/../dependencies/GLFW/lib-mingw", + "-I${workspaceFolder}/../dependencies/glad/include", + "-I${workspaceFolder}/../dependencies/glm", + "-I${workspaceFolder}/../dependencies/stb", + "-I${workspaceFolder}/../dependencies/tinyobjloader", + "-static", + "-lopengl32", + "-lglfw3dll", + "-o", + "${workspaceRoot}/${workspaceFolderBasename}.exe" + ], + "options": { + "cwd": "${fileDirname}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "detail": "Задача создана отладчиком." + }, + { + "type": "cppbuild", + "label": "C/C++ x64: g++.exe сборка активного файла", + "command": "C:/MinGW64/bin/g++.exe", + "args": [ + "-fdiagnostics-color=always", + "${workspaceRoot}/src/*.cpp", + "${workspaceRoot}/../dependencies/glad/src/glad.c", + + "-I${workspaceRoot}/include", + + "--std=c++11", + + "-I${workspaceRoot}/../dependencies/GLFW/include", + "-L${workspaceRoot}/../dependencies/GLFW/lib-mingw-w64", + "-I${workspaceFolder}/../dependencies/glad/include", + "-I${workspaceFolder}/../dependencies/glm", + "-I${workspaceFolder}/../dependencies/stb", + "-I${workspaceFolder}/../dependencies/tinyobjloader", + "-static", + "-lopengl32", + "-lglfw3dll", + "-o", + "${workspaceRoot}/${workspaceFolderBasename}.exe" + ], + "options": { + "cwd": "${fileDirname}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "detail": "Задача создана отладчиком." + } + ], + "version": "2.0.0" +} \ No newline at end of file diff --git a/include/Buffers.h b/include/Buffers.h new file mode 100644 index 0000000..d7bae95 --- /dev/null +++ b/include/Buffers.h @@ -0,0 +1,88 @@ +#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, int count); // Создает буфер кадра с нужным числом прикреплений текстур + ~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(); // Уничтожение буфера + + GLuint getHandler(); // Возвращает дескриптор буфера рендера + protected: + GLuint handler; // Дескриптор +}; + +#endif // BUFFERS_H diff --git a/include/Camera.h b/include/Camera.h new file mode 100644 index 0000000..5939267 --- /dev/null +++ b/include/Camera.h @@ -0,0 +1,57 @@ +#ifndef CAMERA_H +#define CAMERA_H + +#include + +// Ближняя граница области отсечения +#define CAMERA_NEAR 0.1f +// Дальняя граница области отсечения +#define CAMERA_FAR 100.0f +// Вектор, задающий верх для камеры +#define CAMERA_UP_VECTOR glm::vec3(0.0f, 1.0f, 0.0f) +// Стандартный угол обзора +#define CAMERA_FOVy 60.0f + +struct CameraData +{ + glm::mat4 projection; + glm::mat4 view; + glm::vec3 position; +}; + +class Camera +{ + public: + Camera(float aspect, const glm::vec3 &position = glm::vec3(0.0f, 0.0f, 0.0f), const glm::vec2 &xyOffset = glm::vec2(90.0f, 0.0f), float fovy = CAMERA_FOVy); // Конструктор камеры с проекцией перспективы + Camera(float width, float height, const glm::vec3 &position = glm::vec3(0.0f, 0.0f, 0.0f), const glm::vec2 &xyOffset = glm::vec2(90.0f, 0.0f)); // Конструктор ортографической камеры + virtual ~Camera(); // Деструктор + const glm::mat4& getVP(); // Возвращает ссылку на константную матрицу произведения матриц вида и проекции + const glm::mat4& getProjection(); // Возвращает ссылку на константную матрицу проекции + const glm::mat4& getView(); // Возвращает ссылку на константную матрицу вида + void rotate(const glm::vec2 &xyOffset); // Поворачивает камеру на dx и dy пикселей + void move(const glm::vec3 &posOffset); // Сдвигает камеру на указанный вектор (dx,dy,dz) + void setPosition(const glm::vec3 &position); // Устанавливает местоположение + void setRotation(const glm::vec2 &xyOffset); // Устанавливает угол поворота камеры + void setPerspective(float fov, float aspect); // Устанавливает заданную матрицу перспективы + void setOrtho(float width, float height); // Устанавливает заданную ортографическую матрицу + void setSensitivity(float sensitivity); // Изменяет чувствительность мыши + CameraData& getData(); // Данные о камере для шейдера + protected: + Camera(const glm::vec3 &position, const glm::vec2 &xyOffset); // Защищенный (protected) констуктор камеры без перспективы + void recalcTarget(); // Пересчет цели, на которую смотрит камера + void recalcView(); // Пересчет матрицы вида + void recalcVP(); // Пересчет произведения матриц + + glm::vec3 position; // Местоположение камеры + glm::vec3 target; // Цель, на которую смотрит камера + glm::vec2 currentRotation; // Текущий поворот камеры + glm::mat4 projection; // Матрица проекции + glm::mat4 view; // Матрица вида + glm::mat4 vp; // Матрица произведения вида и проекции + bool requiredRecalcVP; // Необходимость пересчета матрицы вида и проекции камеры + bool requiredRecalcView; // Необходимость пересчета матрицы вида камеры + float sensitivity; // Чувствительность мыши +}; + + +#endif // CAMERA_H \ No newline at end of file diff --git a/include/Lights.h b/include/Lights.h new file mode 100644 index 0000000..48d4355 --- /dev/null +++ b/include/Lights.h @@ -0,0 +1,29 @@ +#ifndef LIGHTS_H +#define LIGHTS_H + +#include +#include + +#include "Model.h" + +// Максимальное число источников света +#define MAX_LIGHTS 300 + +// Класс лампочки (точечный источник с возможностью отладочного вывода) +class Bulb +{ + public: + Bulb(const glm::vec3 &pos = glm::vec3(0.0f, 0.0f, 0.0f), const glm::vec3 &color = glm::vec3(0.0f, 0.0f, 0.0f), float radius = 10.0f); // Конструктор с координатами, цветом и радиусом + + void render(ShaderProgram &shaderProgram, UBO &material_buffer); // Отрисовка отладочной лампы и сферы + + alignas(16) glm::vec3 position; // Позиция + alignas(16) glm::vec3 color; // Цвет + void setRadius(float radius); // Задание радиуса и расчет коэф. угасания + private: + float radius; // Радиус действия источника + glm::vec2 K; // линейный и квадратичный компоненты затухания + static GrouptedModel bulb_model; +}; + +#endif // LIGHTS_H diff --git a/include/Model.h b/include/Model.h new file mode 100644 index 0000000..f9ecd2d --- /dev/null +++ b/include/Model.h @@ -0,0 +1,76 @@ +#ifndef MODEL_H +#define MODEL_H + +#include "Buffers.h" +#include "Texture.h" +#include "Shader.h" + +#include + +#include + +#define DEFAULT_MTL_DIR "./" +class GrouptedModel loadOBJtoGroupted(const char* filename, const char* mtl_directory = DEFAULT_MTL_DIR, const char* texture_directory = DEFAULT_MTL_DIR); + +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 Movable +{ + public: + Movable(); // Конструктор без параметров + Movable(const Movable& copy); // Конструктор копирования + + glm::vec3 position; // позиция модели + glm::vec3 rotation; // поворот модели + glm::vec3 scale; // масштабирование модели + glm::mat4 getTransformMatrix(); // Матрица трансформации модели +}; + +// Класс модели с примененным материалом +class Model : public Movable +{ + public: + Model(); // Конструктор без параметров + Model(const Model& copy); // Конструктор копирования + ~Model(); + void render(); // Вызов отрисовки без 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_texture(Texture& texture); // Привязка текстуры к модели + void set_index_range(GLuint beg, GLuint count); // Ограничение диапазона из буфера индексов + + Material material; // Материал модели + private: + VAO vao; + BO vertex_vbo, index_vbo; // вершинный и индексный буферы + BO normals_vbo, texCoords_vbo; // буферы с нормалями и текстурными координатами + GLuint verteces_count; // Количество вершин + GLuint first_index, indices_count; // Первый и количество индексов + Texture texture_diffuse; // Диффузная текстура + Texture texture_ambient; // Текстура фонового освщения + Texture texture_specular; // Текстура зеркального отражения +}; + +// Класс сгруппированной модели +class GrouptedModel: public Movable +{ + public: + void render(ShaderProgram &shaderProgram, UBO &material_buffer); // Вызов отрисовки + std::vector parts; + + Model& operator[](int index); // Оператор[i] для простого доступа к частям модели +}; + +#endif // MODEL_H diff --git a/include/Shader.h b/include/Shader.h new file mode 100644 index 0000000..0562dd7 --- /dev/null +++ b/include/Shader.h @@ -0,0 +1,30 @@ +#ifndef SHADER_H +#define SHADER_H + +#include + +#include +#include + +// Класс шейдерной программы +class ShaderProgram +{ + public: + ShaderProgram(); + ShaderProgram(const ShaderProgram ©); + ~ShaderProgram(); + ShaderProgram& operator=(const ShaderProgram& other); + + void use(); // Использование шейдеров + void load(GLuint type, const char* filename); // Функция для загрузки шейдеров + void link(); // Формирование программы из загруженных шейдеров + GLuint getUniformLoc(const char* name); // Возвращает местоположение uniform-переменной + void bindUniformBlock(const char* name, int binding); // Привязка uniform-блока + void bindTextures(const char* textures_base_shader_names[], int count); // Инициализация текстур на шейдере + private: + GLuint program; // Дескриптор + static std::map handler_count; // Получение количества использований по дескриптору шейдера (Shared pointer) + std::map uniformLocations; // Местоположения uniform-переменных +}; + +#endif // SHADER_H \ No newline at end of file diff --git a/include/Texture.h b/include/Texture.h new file mode 100644 index 0000000..e64599c --- /dev/null +++ b/include/Texture.h @@ -0,0 +1,37 @@ +#ifndef TEXTURE_H +#define TEXTURE_H + +#include + +#include +#include + +enum TexType { + TEX_DIFFUSE, + TEX_AMBIENT, + TEX_SPECULAR, + TEX_AVAILABLE_COUNT +}; + +class Texture +{ + 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(const Texture& other); // Конструктор копирования + ~Texture(); + + Texture& operator=(const Texture& other); // Оператор присваивания + + void use(); // Привязка текстуры + static void disable(GLuint type); // Отвязка текстуры по типу + GLuint getType(); // Возвращает тип текстуры + void setType(GLuint type); // Задает тип текстуры + private: + GLuint handler; // Дескриптор текстуры + GLuint type; // Тип текстуры, соответствует её слоту + static std::map filename_handler; // Получение дескриптора текстуры по её имени + static std::map handler_count; // Получение количества использований по дескриптору текстуры (Shared pointer) +}; + +#endif // TEXTURE_H diff --git a/shaders/bulb.frag b/shaders/bulb.frag new file mode 100644 index 0000000..9329edf --- /dev/null +++ b/shaders/bulb.frag @@ -0,0 +1,16 @@ +#version 420 core + +layout(std140, binding = 1) uniform Material +{ + vec3 ka; + vec3 kd; + vec3 ks; + float p; +}; + +out vec4 color; + +void main() +{ + color = vec4(ka, 1); +} \ No newline at end of file diff --git a/shaders/bulb.vert b/shaders/bulb.vert new file mode 100644 index 0000000..7ce8b0b --- /dev/null +++ b/shaders/bulb.vert @@ -0,0 +1,17 @@ +#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; + +void main() +{ + gl_Position = camera.projection * camera.view * model * vec4(pos, 1.0); +} \ 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..5d26362 --- /dev/null +++ b/shaders/lighting.frag @@ -0,0 +1,88 @@ +#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; + float radius; + vec2 K; +}; + +layout(std140, binding = 2) uniform Light +{ + LightData data[300]; + int count; +} light_f; + + + +uniform sampler2D gPosition; +uniform sampler2D gNormal; +uniform sampler2D gDiffuseP; +uniform sampler2D gAmbientSpecular; + +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; + + // Переменные используемые в цикле: + vec3 L_vertex; // Расположение источника относительно фрагмента + float L_distance; // Расстояние от поверхности до источника + vec3 Cam_vertex = normalize(camera.position - fragPos); // Расположение камеры относительно фрагмента + float diffuse; // Диффузная составляющая + vec3 H; // Вектор половины пути + float specular; // Зеркальная составляющая + float attenuation; // Угасание с учетом расстояния + + + // Фоновая освещенность + color = vec4(ka, 1); + + // Цикл по источникам света + int i; + for (i = 0; i < light_f.count; i++) + { + // Данные об источнике относительно фрагмента + L_vertex = light_f.data[i].position - fragPos; + // Расстояние от поверхности до источника + L_distance = length(L_vertex); + + // Проверка на дистанцию + if (L_distance < light_f.data[i].radius) + { + // Нормирование вектора + L_vertex = normalize(L_vertex); + + // Диффузная составляющая + 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].K[0] * L_distance + light_f.data[i].K[1] * L_distance * L_distance); + + color += vec4(light_f.data[i].color*kd*diffuse * attenuation, 1) + + vec4(light_f.data[i].color*ks*specular * attenuation, 1); + } + } +} \ 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/src/Buffers.cpp b/src/Buffers.cpp new file mode 100644 index 0000000..c0acea7 --- /dev/null +++ b/src/Buffers.cpp @@ -0,0 +1,196 @@ +#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); +} + +// Возвращает дескриптор буфера рендера +GLuint RBO::getHandler() +{ + return handler; +} diff --git a/src/Camera.cpp b/src/Camera.cpp new file mode 100644 index 0000000..ab0e80e --- /dev/null +++ b/src/Camera.cpp @@ -0,0 +1,149 @@ +#include "Camera.h" + +#include +#include + +// Защищенный (protected) конструктор камеры без перспективы +Camera::Camera(const glm::vec3 &pos, const glm::vec2 &xyOffset) +: position(pos), currentRotation(xyOffset) +{ + sensitivity = 0.05; + recalcTarget(); +} + +// Конструктор камеры с проекцией перспективы +Camera::Camera(float aspect, const glm::vec3 &position, const glm::vec2 &xyOffset, float fovy) +: Camera(position, xyOffset) +{ + setPerspective(fovy, aspect); +} + +// Конструктор ортографической камеры +Camera::Camera(float width, float height, const glm::vec3 &position, const glm::vec2 &xyOffset) +: Camera(position, xyOffset) +{ + setOrtho(width, height); +} + +// Деструктор +Camera::~Camera() { } + +// Пересчет цели, на которую смотрит камера +void Camera::recalcTarget() +{ + if(currentRotation.y > 89.0f) + currentRotation.y = 89.0f; + if(currentRotation.y < -89.0f) + currentRotation.y = -89.0f; + + target.x = cos(glm::radians(currentRotation.x)) * cos(glm::radians(currentRotation.y)); + target.y = sin(glm::radians(currentRotation.y)); + target.z = sin(glm::radians(currentRotation.x)) * cos(glm::radians(currentRotation.y)); + + requiredRecalcView = true; + requiredRecalcVP = true; +} + +// Пересчет матрицы вида +void Camera::recalcView() +{ + view = glm::lookAt(position, position + target, CAMERA_UP_VECTOR); + requiredRecalcView = false; +} + +// Пересчет произведения матриц +void Camera::recalcVP() +{ + vp = projection * view; + requiredRecalcVP = false; +} + +// Возвращает ссылку на константную матрицу проекции +const glm::mat4& Camera::getProjection() +{ + return projection; +} + +// Возвращает ссылку на константную матрицу вида +const glm::mat4& Camera::getView() +{ + if (requiredRecalcView) + recalcView(); + return view; +} + +// Возвращает ссылку на константную матрицу вида +const glm::mat4& Camera::getVP() +{ + if (requiredRecalcVP) + { + if (requiredRecalcView) + recalcView(); + recalcVP(); + } + return vp; +} + +// Поворачивает камеру на dx и dy пикселей +void Camera::rotate(const glm::vec2 &xyOffset) +{ + currentRotation += xyOffset * sensitivity; + + recalcTarget(); + requiredRecalcView = true; + requiredRecalcVP = true; +} + +// Сдвигает камеру на указанный вектор (dx,dy,dz) +void Camera::move(const glm::vec3 &posOffset) +{ + position += posOffset; + + requiredRecalcView = true; + requiredRecalcVP = true; +} + +// Устанавливает местоположение +void Camera::setPosition(const glm::vec3 &pos) +{ + position = pos; + + requiredRecalcView = true; + requiredRecalcVP = true; +} + +// Устанавливает угол поворота камеры +void Camera::setRotation(const glm::vec2 &xyOffset) +{ + currentRotation = xyOffset; + recalcTarget(); +} + +// Устанавливает заданную матрицу перспективы +void Camera::setPerspective(float fovy, float aspect) +{ + projection = glm::perspective(glm::radians(fovy), aspect, CAMERA_NEAR, CAMERA_FAR); + requiredRecalcVP = true; +} + +// Устанавливает заданную ортографическую матрицу +void Camera::setOrtho(float width, float height) +{ + const float aspect = width / height; + projection = glm::ortho(-1.0f, 1.0f, -1.0f/aspect, 1.0f/aspect, CAMERA_NEAR, CAMERA_FAR); + requiredRecalcVP = true; +} + +// Изменяет чувствительность мыши +void Camera::setSensitivity(float sens) +{ + sensitivity = sens; +} + +// Данные о камере для шейдера +CameraData& Camera::getData() +{ + static CameraData data; + data = {getProjection(), getView(), position}; + return data; +} diff --git a/src/Lights.cpp b/src/Lights.cpp new file mode 100644 index 0000000..cc6a856 --- /dev/null +++ b/src/Lights.cpp @@ -0,0 +1,59 @@ +#include "Lights.h" + +void genShpere(Model& model, float radius, int sectorsCount); // Model.cpp + +// Статическое поле для модели лампочки +GrouptedModel Bulb::bulb_model; + +// Конструктор с координатами, цветом и радиусом +Bulb::Bulb(const glm::vec3 &pos, const glm::vec3 &c, float r) +{ + // Если отладочная модель не загружена - загрузим + if (!bulb_model.parts.size()) + { + bulb_model = loadOBJtoGroupted("../resources/models/bulb.obj", "../resources/models/", "../resources/textures/"); + + Model radius_sphere; + // Сгенерируем и загрузим меш сферы + genShpere(radius_sphere, 1, 16); + bulb_model.parts.insert(bulb_model.parts.begin(), radius_sphere); + } + + // Сохраним данные о параметрах источника: + position = pos; + color = c; + radius = r; + K[0] = 4.5/radius; + K[1] = 4 * K[0] * K[0]; +} + +// Отрисовка отладочной лампы и сферы +void Bulb::render(ShaderProgram &shaderProgram, UBO &material_buffer) +{ + // Зададим параметры материала сфере действия + bulb_model.parts[0].material.ka = color; + bulb_model.parts[0].position = position; + bulb_model.parts[0].scale = {radius, radius, radius}; + + // Рисование сферы покрытия источника в режиме линий + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + bulb_model.parts[0].render(shaderProgram, material_buffer); + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + + // Зададим цвет для колбы (первая в составе модели) + bulb_model.parts[1].material.ka = color; + + for (int i = 1; i < bulb_model.parts.size(); i++) + { + bulb_model.parts[i].position = position; + bulb_model.parts[i].render(shaderProgram, material_buffer); + } +} + +// Задание радиуса и расчет коэф. угасания +void Bulb::setRadius(float r) +{ + radius = r; + K[0] = 4.5/radius; + K[1] = 4 * K[0] * K[0]; +} diff --git a/src/Model.cpp b/src/Model.cpp new file mode 100644 index 0000000..e3dc32c --- /dev/null +++ b/src/Model.cpp @@ -0,0 +1,429 @@ +#include "Model.h" +#include "Camera.h" +extern Camera camera; + +Movable::Movable() : position(0), rotation(0), scale(1) {} +// Конструктор копирования +Movable::Movable(const Movable& copy) : position(copy.position), rotation(copy.rotation), scale(copy.scale) {} + +// Конструктор без параметров +Model::Model() : verteces_count(0), first_index(0), indices_count(0), +vertex_vbo(VERTEX), index_vbo(ELEMENT), normals_vbo(VERTEX), texCoords_vbo(VERTEX) +{ + +} + +// Конструктор копирования +Model::Model(const Model& copy) : Movable(copy), +vao(copy.vao), +verteces_count(copy.verteces_count), first_index(copy.first_index), indices_count(copy.indices_count), +vertex_vbo(copy.vertex_vbo), index_vbo(copy.index_vbo), normals_vbo(copy.normals_vbo), texCoords_vbo(copy.texCoords_vbo), +texture_diffuse(copy.texture_diffuse), texture_ambient(copy.texture_ambient), texture_specular(copy.texture_specular), material(copy.material) +{ + +} + +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*sizeof(GLuint))); + } + // Если есть вершины - рисуем на основании массива вершин + else if (verteces_count) + glDrawArrays(GL_TRIANGLES, 0, verteces_count); +} + +// Вызов отрисовки +void Model::render(ShaderProgram &shaderProgram, UBO &material_buffer) +{ + // Расчитаем матрицу трансформации + glm::mat4 model = this->getTransformMatrix(); + GLuint model_uniform = shaderProgram.getUniformLoc("model"); + glUniformMatrix4fv(model_uniform, 1, GL_FALSE, &model[0][0]); + + // Подключаем текстуры + texture_diffuse.use(); + texture_ambient.use(); + texture_specular.use(); + + + // Загружаем данные о материале + material_buffer.load(&material, sizeof(material)); + + render(); +} + +// Функция для конфигурации атрибута вершинного буфера +void vertex_attrib_config() +{ + // Включаем необходимый атрибут у выбранного VAO + glEnableVertexAttribArray(0); + // Устанавливаем связь между VAO и привязанным VBO + glVertexAttribPointer( 0 // индекс атрибута, должен совпадать с Layout шейдера + , 3 // количество компонент одного элемента + , GL_FLOAT // тип + , GL_FALSE // нормализованность значений + , 0 // шаг + , (void *)0 // отступ с начала массива + ); +} + +// Загрузка вершин в буфер +void Model::load_verteces(glm::vec3* verteces, GLuint count) +{ + // Подключаем VAO и вершинный буфер + vao.use(); + vertex_vbo.use(); + + // Загрузка вершин в память буфера + vertex_vbo.load(verteces, sizeof(glm::vec3)*count); + vertex_attrib_config(); + // Запоминаем количество вершин для отрисовки + verteces_count = count; +} + +// Загрузка индексов в буфер +void Model::load_indices(GLuint* indices, GLuint count) +{ + // Подключаем VAO и индексный буфер + vao.use(); + index_vbo.use(); + + // Загрузка вершин в память буфера + index_vbo.load(indices, sizeof(GLuint)*count); + // Запоминаем количество вершин для отрисовки + indices_count = count; +} + +// Функция для конфигурации атрибута вершинного буфера +void texCoords_attrib_config() +{ + // Включаем необходимый атрибут у выбранного VAO + glEnableVertexAttribArray(1); + // Устанавливаем связь между VAO и привязанным VBO + glVertexAttribPointer( 1 // индекс атрибута, должен совпадать с Layout шейдера + , 2 // количество компонент одного элемента + , GL_FLOAT // тип + , GL_FALSE // нормализованность значений + , 0 // шаг + , (void *)0 // отступ с начала массива + ); +} + +// Загрузка текстурных координат в буфер +void Model::load_texCoords(glm::vec2* texCoords, GLuint count) +{ + // Подключаем VAO + vao.use(); + + texCoords_vbo.use(); + + // Загрузка вершин в память буфера + texCoords_vbo.load(texCoords, sizeof(glm::vec2)*count); + texCoords_attrib_config(); +} + +// Функция для конфигурации атрибута вершинного буфера +void normals_attrib_config() +{ + // Включаем необходимый атрибут у выбранного VAO + glEnableVertexAttribArray(2); + // Устанавливаем связь между VAO и привязанным VBO + glVertexAttribPointer( 2 // индекс атрибута, должен совпадать с Layout шейдера + , 3 // количество компонент одного элемента + , GL_FLOAT // тип + , GL_FALSE // нормализованность значений + , 0 // шаг + , (void *)0 // отступ с начала массива + ); +} + +// Загрузка нормалей в буфер +void Model::load_normals(glm::vec3* normals, GLuint count) +{ + // Подключаем VAO + vao.use(); + + normals_vbo.use(); + + // Загрузка вершин в память буфера + normals_vbo.load(normals, sizeof(glm::vec3)*count); + normals_attrib_config(); +} + +#include + +// Матрица трансформации модели +glm::mat4 Movable::getTransformMatrix() +{ + glm::mat4 transformMatrix = glm::mat4(1.0f); + // Перемещение модели + transformMatrix = glm::translate(transformMatrix, position); + // Масштабирование + transformMatrix = glm::scale(transformMatrix, scale); + // Поворот модели + transformMatrix = glm::rotate(transformMatrix, glm::radians(rotation.x), glm::vec3(1.0, 0.0, 0.0)); + transformMatrix = glm::rotate(transformMatrix, glm::radians(rotation.y), glm::vec3(0.0, 1.0, 0.0)); + transformMatrix = glm::rotate(transformMatrix, glm::radians(rotation.z), glm::vec3(0.0, 0.0, 1.0)); + + return transformMatrix; +} + +// Привязка текстуры к модели +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; + }; +} + +// Ограничение диапазона из буфера индексов +void Model::set_index_range(GLuint beg, GLuint count) +{ + first_index = beg; + indices_count = count; +} + +#define TINYOBJLOADER_IMPLEMENTATION +#include "tiny_obj_loader.h" + +#include + +inline void hash_combine(std::size_t& seed) { } + +template +inline void hash_combine(std::size_t& seed, const T& v, Rest... rest) { + std::hash hasher; + seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2); + hash_combine(seed, rest...); +} + +GrouptedModel loadOBJtoGroupted(const char* filename, const char* mtl_directory, const char* texture_directory) +{ + GrouptedModel result; + Model model; + + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + std::string err; + + // Если в процессе загрузки возникли ошибки - выдадим исключение + if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename, mtl_directory)) + throw std::runtime_error(err); + + std::vector indices; // индексы модели + std::vector verteces; // вершины + std::vector normals; // нормали + std::vector texCords; // текстурные координаты + size_t hash; // Для уникальных вершин + std::map uniqueVerteces; // словарь для уникальных вершин: ключ - хеш, значение - индекс вершины + + int last_material_index = 0; // индекс последнего материала (для группировки моделей) + int count = 0, offset; // для индексов начала и конца в индексном буфере + std::vector materials_range; // хранилище индексов + std::vector materials_ids; // индексы материалов + + materials_range.push_back(count); // Закидываем начало отрезка в индексном буфере + // Цикл по считанным моделям + for (const auto& shape : shapes) + { + offset = count; // Переменная для + last_material_index = shape.mesh.material_ids[(count - offset)/3]; // Запоминаем индекс материала + + // Цикл по индексам модели + for (const auto& index : shape.mesh.indices) + { + hash = 0; + hash_combine( hash + , attrib.vertices[3 * index.vertex_index + 0], attrib.vertices[3 * index.vertex_index + 1], attrib.vertices[3 * index.vertex_index + 2] + , attrib.normals[3 * index.normal_index + 0], attrib.normals[3 * index.normal_index + 1], attrib.normals[3 * index.normal_index + 2] + , attrib.texcoords[2 * index.texcoord_index + 0], attrib.texcoords[2 * index.texcoord_index + 1]); + + if (!uniqueVerteces.count(hash)) + { + uniqueVerteces[hash] = verteces.size(); + + // группируем вершины в массив на основании индексов + verteces.push_back({ attrib.vertices[3 * index.vertex_index + 0] + , attrib.vertices[3 * index.vertex_index + 1] + , attrib.vertices[3 * index.vertex_index + 2] + }); + // группируем нормали в массив на основании индексов + normals.push_back({ attrib.normals[3 * index.normal_index + 0] + , attrib.normals[3 * index.normal_index + 1] + , attrib.normals[3 * index.normal_index + 2] + }); + // группируем текстурные координаты в массив на основании индексов + texCords.push_back({ attrib.texcoords[2 * index.texcoord_index + 0] + , 1-attrib.texcoords[2 * index.texcoord_index + 1] + }); + } + // Сохраняем индекс в массив + indices.push_back(uniqueVerteces[hash]); + + // Если индекс последнего материала изменился, то необходимо сохранить его + if (last_material_index != shape.mesh.material_ids[(count - offset)/3]) + { + materials_range.push_back(count); // как конец отрезка + materials_ids.push_back(last_material_index); // как используемый материал + last_material_index = shape.mesh.material_ids[(count - offset)/3]; + } + count++; + } // for (const auto& index : shape.mesh.indices) + + // Если последний материал не загружен - загружаем его + if (materials_range[materials_range.size()-1] != count-1) + { + materials_range.push_back(count); // последний конец отрезка + materials_ids.push_back(last_material_index); // последний используемый материал + } + } // for (const auto& shape : shapes) + + + + // Загрузка в буферы + model.load_verteces (&verteces[0], verteces.size()); + model.load_normals (&normals[0], normals.size()); + model.load_texCoords(&texCords[0], texCords.size()); + // Загрузка индексного буфера + model.load_indices (&indices[0], indices.size()); + + + // Создаем копии модели, которые будут рендериться в заданном диапазоне + // И присваиваем текстуры копиям на основании материала + for (int i = 0; i < materials_range.size()-1; i++) + { + result.parts.push_back(model); // Создание копии с общим VAO + auto s = --result.parts.end(); + s->set_index_range(materials_range[i], materials_range[i+1]-materials_range[i]); + + // Текстуры + Texture diffuse(TEX_DIFFUSE, texture_directory + materials[materials_ids[i]].diffuse_texname); + s->set_texture(diffuse); + 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 = glm::vec3(materials[materials_ids[i]].ambient[0], materials[materials_ids[i]].ambient[1], materials[materials_ids[i]].ambient[2]); + s->material.kd = glm::vec3(materials[materials_ids[i]].diffuse[0], materials[materials_ids[i]].diffuse[1], materials[materials_ids[i]].diffuse[2]); + 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; +} + +// Вызов отрисовки групповой модели +void GrouptedModel::render(ShaderProgram &shaderProgram, UBO &material_buffer) +{ + for (auto& model : parts) + { + model.position += position; + model.rotation += rotation; + model.scale *= scale; + model.render(shaderProgram, material_buffer); + model.position -= position; + model.rotation -= rotation; + model.scale /= scale; + } +} + +Model& GrouptedModel::operator[](int index) +{ + return parts[index]; +} + +// Генерирует сферу заданного радиуса с определенным количеством сегментов +void genShpere(Model& model, float radius, int sectorsCount) +{ + std::vector vertices; + std::vector normals; + std::vector indices; + + float x, y, z, xy; // Позиция вершины + float nx, ny, nz, lengthInv = 1.0f / radius; // Нормаль вершины + float PI = 3.14159265; + float sectorStep = PI / sectorsCount; // Шаг сектора + float longAngle, latAngle; // Углы + + for(int i = 0; i <= sectorsCount; ++i) + { + latAngle = PI / 2 - i * sectorStep; // Начиная с pi/2 до -pi/2 + xy = radius * cos(latAngle); // r * cos(lat) + z = radius * sin(latAngle); // r * sin(lat) + + // добавляем (sectorCount+1) вершин на сегмент + // Последняя и первая вершины имеют одинаковые нормали и координаты + for(int j = 0; j <= sectorsCount; ++j) + { + longAngle = j * 2 * sectorStep; // Начиная с 0 до 2*pi + + // Положение вершины (x, y, z) + x = xy * cos(longAngle); // r * cos(lat) * cos(long) + y = xy * sin(longAngle); // r * cos(lat) * sin(long) + vertices.push_back({x, y, z}); + + // Нормали (nx, ny, nz) + nx = x * lengthInv; + ny = y * lengthInv; + nz = z * lengthInv; + normals.push_back({nx, ny, nz}); + } + } + int k1, k2; + for(int i = 0; i < sectorsCount; ++i) + { + k1 = i * (sectorsCount + 1); // начало текущего сегмента + k2 = k1 + sectorsCount + 1; // начало следующего сегмента + + for(int j = 0; j < sectorsCount; ++j, ++k1, ++k2) + { + // 2 треугольника на один сегмент + // k1, k2, k1+1 + if(i != 0) + { + indices.push_back(k1); + indices.push_back(k2); + indices.push_back(k1 + 1); + } + + // k1+1, k2, k2+1 + if(i != (sectorsCount-1)) + { + indices.push_back(k1 + 1); + indices.push_back(k2); + indices.push_back(k2 + 1); + } + + } + } + // Загрузка в модель + model.load_verteces(&vertices[0], vertices.size()); + model.load_normals(&normals[0], normals.size()); + model.load_indices(&indices[0], indices.size()); +} diff --git a/src/Shader.cpp b/src/Shader.cpp new file mode 100644 index 0000000..ad9b1a8 --- /dev/null +++ b/src/Shader.cpp @@ -0,0 +1,154 @@ +#include "Shader.h" + +#include +#include +#include + +std::map ShaderProgram::handler_count; // Получение количества использований по дескриптору ШП (Shared pointer) + +ShaderProgram::ShaderProgram() +{ + program = glCreateProgram(); + handler_count[program] = 1; +} + +ShaderProgram::ShaderProgram(const ShaderProgram ©) : program(copy.program) +{ + handler_count[program]++; +} + +ShaderProgram::~ShaderProgram() +{ + if (!--handler_count[program]) // Если количество ссылок = 0 + { + // Удаление шейдерной программы + glDeleteProgram(program); + } +} + +// Оператор присваивания +ShaderProgram& ShaderProgram::operator=(const ShaderProgram& other) +{ + // Если это разные шейдерные программы + if (program != other.program) + { + this->~ShaderProgram(); // Уничтожаем имеющуюся + // Заменяем новой + program = other.program; + handler_count[program]++; + } + return *this; +} + +// Использование шейдеров +void ShaderProgram::use() +{ + glUseProgram(program); +} + +// Функция чтения шейдера из файла +std::string readFile(const char* filename) +{ + std::string text; + std::ifstream file(filename, std::ios::in); // Открываем файл на чтение + // Если файл доступен и успешно открыт + if (file.is_open()) + { + std::stringstream sstr; // Буфер для чтения + sstr << file.rdbuf(); // Считываем файл + text = sstr.str(); // Преобразуем буфер к строке + file.close(); // Закрываем файл + } + + return text; +} + +// Функция для загрузки шейдеров +void ShaderProgram::load(GLuint type, const char* filename) +{ + // Создание дескрипторов шейдера + GLuint handler = glCreateShader(type); + + // Переменные под результат компиляции + GLint result = GL_FALSE; + int infoLogLength; + + // Считываем текст вершинного шейдера + std::string code = readFile(filename); + const char* pointer = code.c_str(); // Преобразование к указателю на const char, так как функция принимает массив си-строк + + // Компиляция кода вершинного шейдера + glShaderSource(handler, 1, &pointer, NULL); + glCompileShader(handler); + + // Проверка результата компиляции + glGetShaderiv(handler, GL_COMPILE_STATUS, &result); + glGetShaderiv(handler, GL_INFO_LOG_LENGTH, &infoLogLength); + if (infoLogLength > 0) + { + char* errorMessage = new char[infoLogLength + 1]; + glGetShaderInfoLog(handler, infoLogLength, NULL, errorMessage); + std::cout << errorMessage; + delete errorMessage; + } + + // Привязка скомпилированного шейдера + glAttachShader(program, handler); + + // Освобождение дескриптора шейдера + glDeleteShader(handler); +} + +// Формирование программы из загруженных шейдеров +void ShaderProgram::link() +{ + // Переменные под результат компиляции + GLint result = GL_FALSE; + int infoLogLength; + + // Формирование программы из привязанных шейдеров + glLinkProgram(program); + + // Проверка программы + glGetProgramiv(program, GL_LINK_STATUS, &result); + glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength); + if (infoLogLength > 0) + { + char* errorMessage = new char[infoLogLength + 1]; + glGetProgramInfoLog(program, infoLogLength, NULL, errorMessage); + std::cout << errorMessage; + delete errorMessage; + } + + // Используем шейдерную программу объекта из которого вызван метод + this->use(); +} + +// Возвращает местоположение uniform-переменной +GLuint ShaderProgram::getUniformLoc(const char* name) +{ + GLuint result; // Результат + // Если такую переменную ещё не искали - найдем, иначе вернем уже известный дескриптор + if (!uniformLocations.count(name)) + uniformLocations[name] = result = glGetUniformLocation(program, name); + else + result = uniformLocations[name]; + + return result; +} + +// Привязка uniform-блока +void ShaderProgram::bindUniformBlock(const char* name, int binding) +{ + glUniformBlockBinding( program + , glGetUniformBlockIndex(program, name) + , binding); +} + +// Инициализация текстур на шейдере +void ShaderProgram::bindTextures(const char* textures_base_shader_names[], int count) +{ + // Цикл по всем доступным текстурам + for (int i = 0; i < count; i++) + glUniform1i(getUniformLoc(textures_base_shader_names[i]), i); +} diff --git a/src/Texture.cpp b/src/Texture.cpp new file mode 100644 index 0000000..82effa8 --- /dev/null +++ b/src/Texture.cpp @@ -0,0 +1,142 @@ +#include "Texture.h" + +#define STB_IMAGE_IMPLEMENTATION +#include + +std::map Texture::filename_handler; // Получение дескриптора текстуры по её имени +std::map Texture::handler_count; // Получение количества использований по дескриптору текстуры (Shared pointer) + +// Загрузка текстуры с диска или использование "пустой" +Texture::Texture(GLuint t, const std::string& filename) : type(t) +{ + if (!filename_handler.count(filename)) + { + std::string empty = ""; + int width, height, channels; // Ширина, высота и цветовые каналы текстуры + unsigned char* image = stbi_load(filename.c_str(), &width, &height, &channels, STBI_default); // Загрузка в оперативную память изображения + // Если изображение успешно счиитано с диска или отсутствует пустая текстура + if (image || !filename_handler.count(empty)) + { + glActiveTexture(type + GL_TEXTURE0); + glGenTextures(1, &handler); // Генерация одной текстуры + glBindTexture(GL_TEXTURE_2D, handler); // Привязка текстуры как активной + + filename_handler[filename] = handler; // Запоминим её дескриптор для этого имени файла + handler_count[handler] = 0; // Создадим счетчик использований дескриптора, который будет изменен в конце + + // Если изображение успешно считано + if (image) + { + // Загрузка данных с учетом прозрачности + if (channels == 3) // RGB + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image); + else if (channels == 4) // RGBA + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image); + + glGenerateMipmap(GL_TEXTURE_2D); // Генерация мипмапа для активной текстуры + glBindTexture(GL_TEXTURE_2D, 0); // Отвязка активной текстуры + + stbi_image_free(image); // Освобождение оперативной памяти + } + // Иначе изображение не считано и надо создать пустую текстуру + else + { + image = new unsigned char[3] {255,255,255}; // RGB по 1 байту на + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 1, 1, 0, GL_RGB, GL_UNSIGNED_BYTE, image); // Загрузка данных на видеокарту + delete image; // Освобождение оперативной памяти + + filename_handler[empty] = handler; // Запоминим дополнительно её дескриптор для NULL-строки + } + } + // Иначе используем существующую пустую текстуру (текстура не загружена, пустую создавать не нужно) + else + handler = filename_handler[empty]; + } + // Иначе используем уже существующую по имени файла + else + handler = filename_handler[filename]; + + handler_count[handler]++; +} + +// Конструктор текстуры заданного размера для использования в буфере +Texture::Texture(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(const Texture& other) : handler(other.handler), type(other.type) +{ + // Делаем копию и увеличиваем счетчик + handler_count[handler]++; +} + +// Оператор присваивания +Texture& Texture::operator=(const Texture& other) +{ + // Если это разные текстуры + if (handler != other.handler) + { + this->~Texture(); // Уничтожаем имеющуюся + // Заменяем новой + handler = other.handler; + handler_count[handler]++; + } + type = other.type; + + return *this; +} + +Texture::~Texture() +{ + if (!--handler_count[handler]) // Если количество ссылок = 0 + { + glDeleteTextures(1, &handler); // Удаление текстуры + // Удаление из словаря имен файлов и дескрипторов + for (auto it = filename_handler.begin(); it != filename_handler.end();) + { + if (it->second == handler) + it = filename_handler.erase(it); + else + it++; + } + } +} + +// Привязка текстуры +void Texture::use() +{ + glActiveTexture(type + GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, handler); // Привязка текстуры как активной +} + +// Отвязка текстуры по типу +void Texture::disable(GLuint type) +{ + glActiveTexture(type + GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, 0); // Отвязка текстуры +} + +// Возвращает тип текстуры +GLuint Texture::getType() +{ + return type; +} + +// Задает тип текстуры +void Texture::setType(GLuint type) +{ + this->type = type; +} diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..fdd5b39 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,226 @@ + +#include +#include +#include + +#include + +#include "Camera.h" +#include "Model.h" +#include "Texture.h" +#include "Shader.h" +#include "Lights.h" + +#define WINDOW_WIDTH 800 +#define WINDOW_HEIGHT 600 +#define WINDOW_CAPTION "OPENGL notes on rekovalev.site" + +// Функция-callback для изменения размеров буфера кадра в случае изменения размеров поверхности окна +void framebuffer_size_callback(GLFWwindow* window, int width, int height) +{ + glViewport(0, 0, width, height); +} + +Camera camera(800.0f/600.0f); + +bool firstMouse = true; +float lastX, lastY; + +void mouse_callback(GLFWwindow* window, double xpos, double ypos) +{ + if (firstMouse) + { + lastX = xpos; + lastY = ypos; + firstMouse = false; + } + + glm::vec2 offset(xpos - lastX, lastY - ypos); + lastX = xpos; + lastY = ypos; + + camera.rotate(offset); +} + +int main(void) +{ + GLFWwindow* window; // Указатель на окно GLFW3 + + // Инициализация GLFW3 + if (!glfwInit()) + { + std::cout << "GLFW init error\n"; + return -1; + } + + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); // Мажорная версия спецификаций OpenGL + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); // Минорная версия спецификаций OpenGL + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // Контекст OpenGL, который поддерживает только основные функции + + // Создание окна GLFW3 с заданными шириной, высотой и заголовком окна + window = glfwCreateWindow(WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_CAPTION, NULL, NULL); + if (!window) + { + std::cout << "GLFW create window error\n"; + glfwTerminate(); // Завершение работы с GLFW3 в случае ошибки + return -1; + } + + // Установка основного контекста окна + glfwMakeContextCurrent(window); + // Установка callback-функции для изменения размеров окна и буфера кадра + glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); + + glfwSwapInterval(1); // Вертикальная синхронизация + + // Установка callback-функции для мыши и камеры + glfwSetCursorPosCallback(window, mouse_callback); + + // Загрузка функций OpenGL с помощью GLAD + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) + { + std::cout << "GLAD load GL error\n"; + glfwTerminate(); // Завершение работы с GLFW3 в случае ошибки + return -1; + } + + // Включаем проверку по буферу глубины + glEnable(GL_DEPTH_TEST); + + // Шейдер для 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 файла + GrouptedModel scene = loadOBJtoGroupted("../resources/models/blob.obj", "../resources/models/", "../resources/textures/"); + scene.scale = glm::vec3(0.01); + scene.position.z = 1; + scene.parts[0].material.kd = {0.5,0.5,0.5}; + scene.parts[0].material.ka = {0.2,0.2,0.2}; + + // Установка цвета очистки буфера цвета + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); + + // Шейдер для рисования лампочки + ShaderProgram bulbShader; + // Загрузка и компиляция шейдеров + bulbShader.load(GL_VERTEX_SHADER, "shaders/bulb.vert"); + bulbShader.load(GL_FRAGMENT_SHADER, "shaders/bulb.frag"); + bulbShader.link(); + + // Источники света + Bulb lights[MAX_LIGHTS]; + // Количество используемых источников + GLint lights_count = 0; + + lights[lights_count].position = {0.3f, 0.1f, 0.5f}; // позиция + lights[lights_count++].color = {1.0f, 0.0f, 0.0f}; // цвет + lights[lights_count].position = {-0.3f, -0.1f, 0.5f}; // позиция + lights[lights_count++].color = {0.0f, 0.0f, 1.0f}; // цвет + + // Uniform-буферы + UBO cameraUB(sizeof(CameraData), 0); + UBO material_data(sizeof(Material), 1); + UBO light_data(sizeof(lights_count) + sizeof(lights), 2); + + 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_RGB16F, 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(); + + // Шейдер для расчета освещенности + 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"}; + lightShader.bindTextures(gtextures_shader_names, sizeof(gtextures_shader_names)/sizeof(const char*)); + + 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); + + // Пока не произойдет событие запроса закрытия окна + while(!glfwWindowShouldClose(window)) + { + // Активируем G-кадра + gbuffer.use(); + // Используем шейдер с освещением + gShader.use(); + // Очистка буфера цвета и глубины + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + // Загрузка данных о камере + cameraUB.loadSub(&camera.getData(), sizeof(CameraData)); + + // Тут производится рендер + scene.render(gShader, material_data); + + // Активируем базовый буфер кадра + FBO::useDefault(); + // Подключаем шейдер для прямоугольника + lightShader.use(); + // Очистка буфера цвета и глубины + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + // Подключаем текстуры G-буфера + gPosition.use(); + gNormal.use(); + gDiffuseP.use(); + gAmbientSpecular.use(); + // Загружаем информацию об источниках света и их количестве + light_data.loadSub(lights, sizeof(Bulb) * lights_count); + light_data.loadSub(&lights_count, sizeof(GLint), sizeof(lights)); + // Рендерим прямоугольник с расчетом освещения + 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(); // Использование базового буфера для дальнейших работ + + // Отрисовка отладочных лампочек со специальным шейдером + bulbShader.use(); + for (int i = 0; i < lights_count; i++) + lights[i].render(bulbShader, material_data); + + // Представление содержимого буфера цепочки показа на окно + glfwSwapBuffers(window); + // Обработка системных событий + glfwPollEvents(); + } + + // Отключение атрибутов + glDisableVertexAttribArray(0); + + // Завершение работы с GLFW3 перед выходом + glfwTerminate(); +}