From 275d47b7c6bf6e0d5322c7d0d79042888d52b8c5 Mon Sep 17 00:00:00 2001 From: ZY4N Date: Tue, 10 Dec 2024 13:50:21 +0100 Subject: [PATCH] initial commit --- .gitmodules | 6 + .idea/.name | 1 + .idea/editor.xml | 103 ++++++++++++++++++ .vscode/settings.json | 91 ++++++++++++++++ data/images/logo.png | Bin 0 -> 57372 bytes data/images/spinner.png | Bin 0 -> 10282 bytes .../generic/generic_3dtk_loader.hpp | 58 ++++++++++ include/geometry/aabb.hpp | 91 ++++++++++++++++ include/geometry/normal_estimation.hpp | 14 +++ include/opengl/data/shader_program_data.hpp | 45 ++++++++ include/opengl/error.hpp | 74 +++++++++++++ include/opengl/handles/alpha_handle.hpp | 8 ++ include/opengl/handles/material_handle.hpp | 16 +++ include/opengl/handles/matrix_handles.hpp | 12 ++ include/opengl/handles/mesh_handle.hpp | 20 ++++ include/opengl/handles/point_cloud_handle.hpp | 20 ++++ .../opengl/handles/shader_program_handle.hpp | 33 ++++++ .../handles/surface_properties_handle.hpp | 16 +++ include/opengl/handles/texture_handle.hpp | 17 +++ include/opengl/shader_program_variable.hpp | 16 +++ include/opengl/type_utils.hpp | 71 ++++++++++++ include/scene/camera_view.hpp | 27 +++++ libraries/include/glm | 1 + shaders/mesh/fragment_face.glsl | 64 +++++++++++ shaders/mesh/fragment_point.glsl | 7 ++ shaders/mesh/vertex_face.glsl | 27 +++++ shaders/mesh/vertex_point.glsl | 59 ++++++++++ shaders/point_cloud/fragment.glsl | 7 ++ shaders/point_cloud/vertex.glsl | 51 +++++++++ source/opengl/handles/mesh_handle.ipp | 16 +++ source/opengl/handles/point_cloud_handle.ipp | 16 +++ .../opengl/handles/shader_program_handle.ipp | 63 +++++++++++ source/opengl/handles/texture_handle.ipp | 16 +++ 33 files changed, 1066 insertions(+) create mode 100755 .gitmodules create mode 100644 .idea/.name create mode 100644 .idea/editor.xml create mode 100644 .vscode/settings.json create mode 100644 data/images/logo.png create mode 100644 data/images/spinner.png create mode 100644 include/assets/data_loaders/generic/generic_3dtk_loader.hpp create mode 100755 include/geometry/aabb.hpp create mode 100644 include/geometry/normal_estimation.hpp create mode 100755 include/opengl/data/shader_program_data.hpp create mode 100644 include/opengl/error.hpp create mode 100644 include/opengl/handles/alpha_handle.hpp create mode 100644 include/opengl/handles/material_handle.hpp create mode 100644 include/opengl/handles/matrix_handles.hpp create mode 100755 include/opengl/handles/mesh_handle.hpp create mode 100755 include/opengl/handles/point_cloud_handle.hpp create mode 100644 include/opengl/handles/shader_program_handle.hpp create mode 100644 include/opengl/handles/surface_properties_handle.hpp create mode 100644 include/opengl/handles/texture_handle.hpp create mode 100644 include/opengl/shader_program_variable.hpp create mode 100755 include/opengl/type_utils.hpp create mode 100644 include/scene/camera_view.hpp create mode 160000 libraries/include/glm create mode 100644 shaders/mesh/fragment_face.glsl create mode 100644 shaders/mesh/fragment_point.glsl create mode 100644 shaders/mesh/vertex_face.glsl create mode 100644 shaders/mesh/vertex_point.glsl create mode 100644 shaders/point_cloud/fragment.glsl create mode 100644 shaders/point_cloud/vertex.glsl create mode 100644 source/opengl/handles/mesh_handle.ipp create mode 100644 source/opengl/handles/point_cloud_handle.ipp create mode 100755 source/opengl/handles/shader_program_handle.ipp create mode 100644 source/opengl/handles/texture_handle.ipp diff --git a/.gitmodules b/.gitmodules new file mode 100755 index 0000000..eeed3b5 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,6 @@ +[submodule "libraries/include/stb"] + path = libraries/include/stb + url = https://github.com/nothings/stb +[submodule "libraries/include/glm"] + path = libraries/include/glm + url = https://github.com/g-truc/glm diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..c882834 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +z3d \ No newline at end of file diff --git a/.idea/editor.xml b/.idea/editor.xml new file mode 100644 index 0000000..00465ab --- /dev/null +++ b/.idea/editor.xml @@ -0,0 +1,103 @@ + + + + + \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..374fd27 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,91 @@ +{ + "files.associations": { + "*.ipp": "cpp", + "*.tcc": "cpp", + "cctype": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "csignal": "cpp", + "cstdarg": "cpp", + "cstddef": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "cwctype": "cpp", + "any": "cpp", + "array": "cpp", + "atomic": "cpp", + "strstream": "cpp", + "bit": "cpp", + "bitset": "cpp", + "cfenv": "cpp", + "charconv": "cpp", + "chrono": "cpp", + "cinttypes": "cpp", + "codecvt": "cpp", + "compare": "cpp", + "complex": "cpp", + "concepts": "cpp", + "condition_variable": "cpp", + "coroutine": "cpp", + "cstdint": "cpp", + "deque": "cpp", + "forward_list": "cpp", + "list": "cpp", + "map": "cpp", + "set": "cpp", + "string": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "vector": "cpp", + "exception": "cpp", + "algorithm": "cpp", + "functional": "cpp", + "iterator": "cpp", + "memory": "cpp", + "memory_resource": "cpp", + "numeric": "cpp", + "optional": "cpp", + "random": "cpp", + "ratio": "cpp", + "regex": "cpp", + "source_location": "cpp", + "string_view": "cpp", + "system_error": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "utility": "cpp", + "hash_map": "cpp", + "hash_set": "cpp", + "format": "cpp", + "fstream": "cpp", + "future": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "iosfwd": "cpp", + "iostream": "cpp", + "istream": "cpp", + "limits": "cpp", + "mutex": "cpp", + "new": "cpp", + "numbers": "cpp", + "ostream": "cpp", + "ranges": "cpp", + "semaphore": "cpp", + "shared_mutex": "cpp", + "span": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stdfloat": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "text_encoding": "cpp", + "thread": "cpp", + "typeindex": "cpp", + "typeinfo": "cpp", + "valarray": "cpp", + "variant": "cpp" + } +} diff --git a/data/images/logo.png b/data/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c3f292d048ecb89941e356541490bde5081efa97 GIT binary patch literal 57372 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 z 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 z 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 +#include +#include + +#include "glm/mat4x4.hpp" +#include "util/result.hpp" +#include "util/string_list.hpp" +#include "assets/prefetch_queue.hpp" + +#include "assets/dynamic_read_buffers/dynamic_point_cloud_buffer.hpp" +#include "assets/dynamic_data_stores/dynamic_point_cloud_store.hpp" +#include "assets/prefetch_lookup.hpp" + + + +template +struct generic_3dtk_loader +{ + [[nodiscard]] static std::error_code prefetch( + const ztu::string_list& filenames, + prefetch_queue& queue + ); + + [[nodiscard]] static std::error_code load( + dynamic_point_cloud_buffer& buffer, + const file_dir_list& paths, + prefetch_lookup& asset_lookup, + dynamic_point_cloud_store& store, + bool pedantic = false + ); + +protected: + + [[nodiscard]] static ztu::result parse_index( + std::string_view filename + ); + + ztu::result> analyze_component_format( + std::string_view line + ); + + void transform_point_cloud( + std::span points, + const glm::mat4& pose + ); + +private: + std::error_code read_point_file( + const std::filesystem::path& filename, + dynamic_point_cloud_buffer& point_cloud + ); +}; + +#define INCLUDE_GENERIC_3DTK_LOADER_IMPLEMENTATION +#include "assets/data_loaders/generic/generic_3dtk_loader.ipp" +#undef INCLUDE_GENERIC_3DTK_LOADER_IMPLEMENTATION diff --git a/include/geometry/aabb.hpp b/include/geometry/aabb.hpp new file mode 100755 index 0000000..de3ee64 --- /dev/null +++ b/include/geometry/aabb.hpp @@ -0,0 +1,91 @@ +#pragma once + +#include "glm/glm.hpp" + +#include +#include +#include +#include + +struct aabb +{ + using vector_type = glm::vec3; + using scalar_type = vector_type::value_type; + using index_type = vector_type::length_type; + + static constexpr auto default_min = std::numeric_limits::max(); + static constexpr auto default_max =-std::numeric_limits::max(); + + vector_type min { default_min, default_min, default_min }; + vector_type max { default_max, default_max, default_max }; + + [[nodiscard]] vector_type size() const + { + return max - min; + } + + [[nodiscard]] vector_type center() const + { + return min + 0.5f * size(); + } + + [[nodiscard]] vector_type closest_point_inside(const vector_type& point) const { + return { + std::clamp(point.x, min.x, max.x), + std::clamp(point.y, min.y, max.y), + std::clamp(point.z, min.z, max.z) + }; + } + + aabb& add_aabb(const aabb& other) + { + for (index_type i{}; i != min.length(); ++i) + { + min[i] = std::min(min[i], other.min[i]); + max[i] = std::max(max[i], other.max[i]); + } + return *this; + } + + aabb& add_point(const auto& point) + { + for (index_type i{}; i != min.length(); ++i) + { + min[i] = std::min(min[i], point[i]); + max[i] = std::max(max[i], point[i]); + } + return *this; + } + + template + aabb& add_points(std::span points) + { + for (const auto& point : points) + { + add_point(point); + } + return *this; + } + + aabb& join(const aabb& other) + { + min = glm::min(min, other.min); + max = glm::max(max, other.max); + return *this; + } + + [[nodiscard]] aabb transformed(const glm::mat4x4& matrix) const + { + const auto vertices = std::array{ + vector_type{ matrix * glm::vec4{ min.x, min.y, min.z, 1 } }, + vector_type{ matrix * glm::vec4{ min.x, min.y, max.z, 1 } }, + vector_type{ matrix * glm::vec4{ min.x, max.y, min.z, 1 } }, + vector_type{ matrix * glm::vec4{ min.x, max.y, max.z, 1 } }, + vector_type{ matrix * glm::vec4{ max.x, min.y, min.z, 1 } }, + vector_type{ matrix * glm::vec4{ max.x, min.y, max.z, 1 } }, + vector_type{ matrix * glm::vec4{ max.x, max.y, min.z, 1 } }, + vector_type{ matrix * glm::vec4{ max.x, max.y, max.z, 1 } } + }; + return aabb{}.add_points(vertices); + } +}; diff --git a/include/geometry/normal_estimation.hpp b/include/geometry/normal_estimation.hpp new file mode 100644 index 0000000..4d8bbb0 --- /dev/null +++ b/include/geometry/normal_estimation.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include +#include +#include + +#include "util/uix.hpp" +#include "assets/components/mesh_vertex_components.hpp" + +void estimate_normals( + std::span vertices, + std::span> triangles, + std::vector& normals +); \ No newline at end of file diff --git a/include/opengl/data/shader_program_data.hpp b/include/opengl/data/shader_program_data.hpp new file mode 100755 index 0000000..e94f79d --- /dev/null +++ b/include/opengl/data/shader_program_data.hpp @@ -0,0 +1,45 @@ +#pragma once + + +#include "GL/glew.h" +#include + +#include "opengl/handles/shader_handle.hpp" +#include "opengl/handles/shader_program_handle.hpp" + +namespace zgl +{ +class shader_program_data +{ +private: + explicit shader_program_data(GLuint program_id); + +public: + + shader_program_data() = default; + + [[nodiscard]] static std::error_code build_from( + const shader_handle& vertex_shader, + const shader_handle& geometry_shader, + const shader_handle& fragment_shader, + shader_program_data& data + ); + + shader_program_data(const shader_program_data& other) = delete; + shader_program_data& operator=(const shader_program_data& other) = delete; + + shader_program_data(shader_program_data&& other) noexcept ; + shader_program_data& operator=(shader_program_data&& other) noexcept; + + [[nodiscard]] shader_program_handle handle() const; + + ~shader_program_data(); + +private: + shader_program_handle m_handle{}; +}; +} + +#define INCLUDE_SHADER_PROGRAM_DATA_IMPLEMENTATION +#include "opengl/data/shader_program_data.ipp" +#undef INCLUDE_SHADER_PROGRAM_DATA_IMPLEMENTATION diff --git a/include/opengl/error.hpp b/include/opengl/error.hpp new file mode 100644 index 0000000..8b2b620 --- /dev/null +++ b/include/opengl/error.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include +#include "GL/glew.h" + +namespace zgl +{ +namespace error +{ + +enum class codes : GLenum { + ok = GL_NO_ERROR, + invalid_enum = GL_INVALID_ENUM, + invalid_value = GL_INVALID_VALUE, + invalid_operation = GL_INVALID_OPERATION, + invalid_framebuffer_operation = GL_INVALID_FRAMEBUFFER_OPERATION, + out_of_memory = GL_OUT_OF_MEMORY, + stack_overflow = GL_STACK_OVERFLOW, + stack_underflow = GL_STACK_UNDERFLOW, + context_lost = GL_CONTEXT_LOST +}; + +struct category : std::error_category +{ + [[nodiscard]] const char* name() const noexcept override + { + return "opengl"; + } + + [[nodiscard]] std::string message(int ev) const override + { + switch (static_cast(ev)) { + using enum codes; + case ok: + return "No error has been recorded."; + case invalid_enum: + return "An unacceptable value is specified for an enumerated argument."; + case invalid_value: + return "A numeric argument is out of range."; + case invalid_operation: + return "The specified operation is not allowed in the current state."; + case invalid_framebuffer_operation: + return "The framebuffer object is not complete."; + case out_of_memory: + return "There is not enough memory left to execute the command."; + case stack_overflow: + return "An attempt has been made to perform an operation that would cause an internal stack to underflow."; + case stack_underflow: + return "An attempt has been made to perform an operation that would cause an internal stack to overflow."; + case context_lost: + return "The OpenGL context has been lost."; + default: + return ""; + } + } +}; + +} // namespace error + +[[nodiscard]] inline std::error_category& error_category() noexcept +{ + static error::category category; + return category; +} + +[[nodiscard]] inline std::error_code make_error_code(const GLenum e) noexcept +{ + return { static_cast(e), error_category() }; +} + +} // namespace zgl + +template<> +struct std::is_error_code_enum : std::true_type {}; diff --git a/include/opengl/handles/alpha_handle.hpp b/include/opengl/handles/alpha_handle.hpp new file mode 100644 index 0000000..0a49f9e --- /dev/null +++ b/include/opengl/handles/alpha_handle.hpp @@ -0,0 +1,8 @@ +#pragma once + +namespace zgl +{ + +using alpha_handle = float; + +} // namespace zgl diff --git a/include/opengl/handles/material_handle.hpp b/include/opengl/handles/material_handle.hpp new file mode 100644 index 0000000..ce420df --- /dev/null +++ b/include/opengl/handles/material_handle.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include "opengl/handles/texture_handle.hpp" +#include "opengl/handles/surface_properties_handle.hpp" +#include "opengl/handles/alpha_handle.hpp" + +namespace zgl +{ +struct material_handle +{ + std::optional texture{ std::nullopt }; + std::optional surface_properties{ std::nullopt }; + std::optional alpha{ std::nullopt }; +}; +} diff --git a/include/opengl/handles/matrix_handles.hpp b/include/opengl/handles/matrix_handles.hpp new file mode 100644 index 0000000..ba646fb --- /dev/null +++ b/include/opengl/handles/matrix_handles.hpp @@ -0,0 +1,12 @@ +#pragma once + +#include "glm/glm.hpp" + +namespace zgl +{ +using model_matrix_handle = glm::mat4x4; + +using view_matrix_handle = glm::mat4x4; + +using projection_matrix_handle = glm::mat4x4; +} \ No newline at end of file diff --git a/include/opengl/handles/mesh_handle.hpp b/include/opengl/handles/mesh_handle.hpp new file mode 100755 index 0000000..8f5088c --- /dev/null +++ b/include/opengl/handles/mesh_handle.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "GL/glew.h" +#include "util/uix.hpp" + +namespace zgl +{ +struct mesh_handle +{ + inline void bind() const; + inline static void unbind(); + + GLuint vao_id{ 0 }; + GLsizei index_count{ 0 }; +}; +} + +#define INCLUDE_MESH_INSTANCE_IMPLEMENTATION +#include "opengl/handles/mesh_handle.ipp" +#undef INCLUDE_MESH_INSTANCE_IMPLEMENTATION \ No newline at end of file diff --git a/include/opengl/handles/point_cloud_handle.hpp b/include/opengl/handles/point_cloud_handle.hpp new file mode 100755 index 0000000..49dd4af --- /dev/null +++ b/include/opengl/handles/point_cloud_handle.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include "GL/glew.h" +#include "util/uix.hpp" + +namespace zgl +{ +struct point_cloud_handle +{ + inline void bind() const; + inline static void unbind(); + + GLuint vao_id{ 0 }; + GLsizei point_count{ 0 }; +}; +} + +#define INCLUDE_POINT_CLOUD_INSTANCE_IMPLEMENTATION +#include "opengl/handles/point_cloud_handle.ipp" +#undef INCLUDE_POINT_CLOUD_INSTANCE_IMPLEMENTATION \ No newline at end of file diff --git a/include/opengl/handles/shader_program_handle.hpp b/include/opengl/handles/shader_program_handle.hpp new file mode 100644 index 0000000..59fa5fc --- /dev/null +++ b/include/opengl/handles/shader_program_handle.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "GL/glew.h" +#include "opengl/shader_program_variable.hpp" + +#include "opengl/shader_program_variable.hpp" +#include "util/uix.hpp" +#include + +namespace zgl +{ +struct shader_program_handle +{ + using attribute_support_type = ztu::u32; + using uniform_support_type = ztu::u32; + + inline void bind() const; + static void unbind(); + + template + void set_uniform(const T& value) const; + + [[nodiscard]] attribute_support_type check_attribute_support(std::span attributes) const; + + [[nodiscard]] uniform_support_type check_uniform_support(std::span uniforms) const; + + GLuint program_id{ 0 }; +}; +} + +#define INCLUDE_GL_SHADER_PROGRAM_INSTANCE_IMPLEMENTATION +#include "opengl/handles/shader_program_handle.ipp" +#undef INCLUDE_GL_SHADER_PROGRAM_INSTANCE_IMPLEMENTATION diff --git a/include/opengl/handles/surface_properties_handle.hpp b/include/opengl/handles/surface_properties_handle.hpp new file mode 100644 index 0000000..8780217 --- /dev/null +++ b/include/opengl/handles/surface_properties_handle.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "glm/glm.hpp" + +namespace zgl +{ +struct surface_properties_handle +{ + glm::mat3 filters{ + 0.1986f, 0.0000f, 0.0000f, + 0.5922f, 0.0166f, 0.0000f, + 0.5974f, 0.2084f, 0.2084f + }; + float shininess{ 100.2237f }; +}; +} diff --git a/include/opengl/handles/texture_handle.hpp b/include/opengl/handles/texture_handle.hpp new file mode 100644 index 0000000..084efc2 --- /dev/null +++ b/include/opengl/handles/texture_handle.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include "GL/glew.h" + +namespace zgl { +struct texture_handle +{ + inline void bind() const; + inline static void unbind(); + + GLuint texture_id{ 0 }; +}; +} + +#define INCLUDE_TEXTURE_INSTANCE_IMPLEMENTATION +#include "opengl/handles/texture_handle.ipp" +#undef INCLUDE_TEXTURE_INSTANCE_IMPLEMENTATION diff --git a/include/opengl/shader_program_variable.hpp b/include/opengl/shader_program_variable.hpp new file mode 100644 index 0000000..63def71 --- /dev/null +++ b/include/opengl/shader_program_variable.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include "GL/glew.h" + +namespace zgl +{ +struct shader_program_variable +{ + struct info_type + { + GLenum type; + GLint location; + } info; + const char* name; +}; +} diff --git a/include/opengl/type_utils.hpp b/include/opengl/type_utils.hpp new file mode 100755 index 0000000..9956830 --- /dev/null +++ b/include/opengl/type_utils.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include +#include "GL/glew.h" +#include "util/uix.hpp" + +namespace zgl::type_utils +{ +constexpr bool is_valid_type(GLenum type) { + switch (type) { + case GL_BYTE: + case GL_UNSIGNED_BYTE: + case GL_SHORT: + case GL_UNSIGNED_SHORT: + case GL_INT: + case GL_UNSIGNED_INT: + case GL_FLOAT: + case GL_DOUBLE: + return true; + default: + return false; + } +} + +template +consteval GLenum to_gl_type() { + if constexpr (std::same_as) { + return GL_BYTE; + } else if constexpr (std::same_as) { + return GL_UNSIGNED_BYTE; + } else if constexpr (std::same_as) { + return GL_SHORT; + } else if constexpr (std::same_as) { + return GL_UNSIGNED_SHORT; + } else if constexpr (std::same_as) { + return GL_INT; + } else if constexpr (std::same_as) { + return GL_UNSIGNED_INT; + } else if constexpr (std::same_as) { + return GL_FLOAT; + } else if constexpr (std::same_as) { + return GL_DOUBLE; + } else { + T::___unknown_type; + return GL_INVALID_ENUM; + } +} + +constexpr GLsizei size_of(GLenum type) { + switch (type) { + case GL_BYTE: + return sizeof(ztu::i8); + case GL_UNSIGNED_BYTE: + return sizeof(ztu::u8); + case GL_SHORT: + return sizeof(ztu::i16); + case GL_UNSIGNED_SHORT: + return sizeof(ztu::u16); + case GL_INT: + return sizeof(ztu::i32); + case GL_UNSIGNED_INT: + return sizeof(ztu::u32); + case GL_FLOAT: + return sizeof(float); + case GL_DOUBLE: + return sizeof(double); + default: + return 0; + } +} +} // namespace zgl::type_utils diff --git a/include/scene/camera_view.hpp b/include/scene/camera_view.hpp new file mode 100644 index 0000000..c31b5ab --- /dev/null +++ b/include/scene/camera_view.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include "glm/glm.hpp" +#include "glm/gtc/matrix_transform.hpp" +#include + +struct camera_view +{ + glm::vec3 position; + glm::vec3 front; + glm::vec3 right; + glm::vec3 up; + float aspect_ratio; + float fov; + + [[nodiscard]] glm::mat4 create_view_matrix() const { + return glm::lookAt(position, position + front, up); + } + + [[nodiscard]] glm::mat4 create_projection_matrix() const { + return glm::perspective( + fov, + aspect_ratio, + 0.1f, 1000.0f + ); + } +}; diff --git a/libraries/include/glm b/libraries/include/glm new file mode 160000 index 0000000..33b4a62 --- /dev/null +++ b/libraries/include/glm @@ -0,0 +1 @@ +Subproject commit 33b4a621a697a305bc3a7610d290677b96beb181 diff --git a/shaders/mesh/fragment_face.glsl b/shaders/mesh/fragment_face.glsl new file mode 100644 index 0000000..4d7fedc --- /dev/null +++ b/shaders/mesh/fragment_face.glsl @@ -0,0 +1,64 @@ +#ifdef TEXTURE +#ifdef U_COLOR +#error Texture and color attribute are mutually exclusive. +#endif +#endif + +out vec4 pixel_color; + +#ifdef TEXTURE +layout (location = 3) uniform sampler2D tex; +layout (location = 2) in vec2 frag_tex_coord; +#endif + +#ifdef U_COLOR +layout (location = 3) uniform vec4 color; +#endif + +#ifdef LIGHTING +layout (location = 4) uniform vec3 view_pos; +layout (location = 5) uniform vec3 point_light_direction; +layout (location = 6) uniform vec3 point_light_color; +layout (location = 7) uniform vec3 ambient_light_color; +layout (location = 8) uniform vec3 ambient_filter; +layout (location = 9) uniform vec3 diffuse_filter; +layout (location = 10) uniform vec3 specular_filter; +layout (location = 11) uniform float shininess; +layout (location = 0) in vec3 frag_position; +layout (location = 1) in vec3 frag_normal; +#endif + +#ifdef U_ALPHA +layout (location = 12) uniform float alpha; +#endif + +void main() { + + pixel_color = vec4(1); + +#ifdef TEXTURE + pixel_color *= texture(tex, frag_tex_coord); +#endif + +#ifdef U_COLOR + pixel_color *= color; +#endif + +#ifdef U_ALPHA + pixel_color.w *= alpha; +#endif + +#ifdef LIGHTING + vec3 ambient = pixel_color.rgb * ambient_filter * ambient_light_color; + + float point_light_alignment = max(dot(frag_normal, point_light_direction), 0.0); + vec3 diffuse = pixel_color.rgb * diffuse_filter * point_light_color * point_light_alignment; + + vec3 reflection_dir = reflect(-point_light_direction, frag_normal); + vec3 view_dir = normalize(frag_position - view_pos); + float specular_strength = pow(max(dot(view_dir, reflection_dir), 0.0), shininess); + vec3 specular = specular_filter * point_light_color * specular_strength; + + pixel_color.rgb *= ambient + diffuse + specular; +#endif +} diff --git a/shaders/mesh/fragment_point.glsl b/shaders/mesh/fragment_point.glsl new file mode 100644 index 0000000..fc4fbfd --- /dev/null +++ b/shaders/mesh/fragment_point.glsl @@ -0,0 +1,7 @@ +layout (location = 0) in vec4 frag_color; + +out vec4 pixel_color; + +void main() { + pixel_color = frag_color; +} diff --git a/shaders/mesh/vertex_face.glsl b/shaders/mesh/vertex_face.glsl new file mode 100644 index 0000000..cbae90f --- /dev/null +++ b/shaders/mesh/vertex_face.glsl @@ -0,0 +1,27 @@ +layout (location = 0) uniform mat4 mvp_matrix; +layout (location = 0) in vec3 vertex_position; + +#ifdef LIGHTING +layout (location = 1) uniform mat4 model_matrix; +layout (location = 1) in vec3 vertex_normal; +layout (location = 0) out vec3 frag_position; +layout (location = 1) out vec3 frag_normal; +#endif + +#ifdef TEXTURE +layout (location = 2) in vec2 vertex_tex_coord; +layout (location = 2) out vec2 frag_tex_coord; +#endif + +void main() { + gl_Position = mvp_matrix * vec4(vertex_position, 1.0); + +#ifdef LIGHTING + frag_position = (model_matrix * vec4(vertex_position, 1.0)).xyz; + frag_normal = normalize(mat3(model_matrix) * vertex_normal); +#endif + +#ifdef TEXTURE + frag_tex_coord = vertex_tex_coord; +#endif +} diff --git a/shaders/mesh/vertex_point.glsl b/shaders/mesh/vertex_point.glsl new file mode 100644 index 0000000..ce9a662 --- /dev/null +++ b/shaders/mesh/vertex_point.glsl @@ -0,0 +1,59 @@ +layout (location = 0) uniform mat4 mvp_matrix; +layout (location = 2) uniform float point_size; +layout (location = 0) in vec3 vertex_position; +layout (location = 0) out vec4 frag_color; + +#ifdef TEXTURE +layout (location = 3) uniform sampler2D tex; +layout (location = 2) in vec2 vertex_tex_coord; +#endif + +#ifdef LIGHTING +layout (location = 1) uniform mat4 model_matrix; +layout (location = 4) uniform vec3 view_pos; +layout (location = 5) uniform vec3 point_light_direction; +layout (location = 6) uniform vec3 point_light_color; +layout (location = 7) uniform vec3 ambient_light_color; +layout (location = 8) uniform vec3 ambient_filter; +layout (location = 9) uniform vec3 diffuse_filter; +layout (location = 10) uniform vec3 specular_filter; +layout (location = 11) uniform float shininess; +layout (location = 1) in vec3 vertex_normal; +#endif + +#ifdef U_ALPHA +layout (location = 12) uniform float alpha; +#endif + +void main() { + + gl_Position = mvp_matrix * vec4(vertex_position, 1.0); + gl_PointSize = point_size / gl_Position.w; + + frag_color = vec4(1); + +#ifdef TEXTURE + frag_color *= texture(tex, vertex_tex_coord); +#endif + +#ifdef U_ALPHA + frag_color.w *= alpha; +#endif + +#ifdef LIGHTING + vec3 position = (model_matrix * vec4(vertex_position, 1.0)).xyz; + vec3 normal = normalize(mat3(model_matrix) * vertex_normal); + + vec3 ambient = frag_color.rgb * ambient_filter * ambient_light_color; + + float point_light_alignment = max(dot(normal, point_light_direction), 0.0); + vec3 diffuse = frag_color.rgb * diffuse_filter * point_light_color * point_light_alignment; + + vec3 reflection_dir = reflect(-point_light_direction, normal); + vec3 view_dir = normalize(position - view_pos); + float specular_strength = pow(max(dot(view_dir, reflection_dir), 0.0), shininess); + vec3 specular = specular_filter * point_light_color * specular_strength; + + frag_color.rgb *= ambient + diffuse + specular; +#endif +} diff --git a/shaders/point_cloud/fragment.glsl b/shaders/point_cloud/fragment.glsl new file mode 100644 index 0000000..fc4fbfd --- /dev/null +++ b/shaders/point_cloud/fragment.glsl @@ -0,0 +1,7 @@ +layout (location = 0) in vec4 frag_color; + +out vec4 pixel_color; + +void main() { + pixel_color = frag_color; +} diff --git a/shaders/point_cloud/vertex.glsl b/shaders/point_cloud/vertex.glsl new file mode 100644 index 0000000..7edaeb7 --- /dev/null +++ b/shaders/point_cloud/vertex.glsl @@ -0,0 +1,51 @@ +layout (location = 0) in vec3 vertex_position; +layout (location = 0) uniform mat4 mvp_matrix; +layout (location = 2) uniform float point_size; +layout (location = 0) out vec4 frag_color; + +#ifdef LIGHTING +layout (location = 4) uniform mat4 model_matrix; +layout (location = 5) uniform vec3 camera_position; +layout (location = 1) in vec3 vertex_normal; +#endif + +#ifdef RAINBOW +layout (location = 6) uniform float rainbow_offset_y; +layout (location = 7) uniform float rainbow_scale_y; + +// taken from 'https://github.com/hughsk/glsl-hsv2rgb' +vec3 hue2rgb(float hue) +{ + vec4 offsets = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(vec3(hue) + offsets.xyz) * 6.0 - offsets.www); + return clamp(p - offsets.xxx, 0.0, 1.0); +} +#endif + +#ifdef REFLECTANCE +layout (location = 2) in float vertex_reflectance; +#endif + +void main() +{ + gl_Position = mvp_matrix * vec4(vertex_position, 1.0); + gl_PointSize = point_size / gl_Position.w; + + frag_color = vec4(1); + +#ifdef LIGHTING + vec3 world_position = vec3(model_matrix * vec4(vertex_position, 1.0)); + vec3 world_normal = normalize(mat3(model_matrix) * vertex_normal); + vec3 view_direction = normalize(camera_position - world_position); + frag_color.rgb *= max(dot(world_normal, view_direction), 0.0); +#endif + +#ifdef RAINBOW + float rainbow_pos = rainbow_scale_y * (vertex_position.y + rainbow_offset_y); + frag_color.rgb *= hue2rgb(mod(rainbow_pos, 1.0f)); +#endif + +#ifdef REFLECTANCE + frag_color.rgb *= vertex_reflectance; +#endif +} diff --git a/source/opengl/handles/mesh_handle.ipp b/source/opengl/handles/mesh_handle.ipp new file mode 100644 index 0000000..7e7ae7d --- /dev/null +++ b/source/opengl/handles/mesh_handle.ipp @@ -0,0 +1,16 @@ +#ifndef INCLUDE_MESH_INSTANCE_IMPLEMENTATION +#error Never include this file directly include 'mesh_handle.hpp' +#endif + +namespace zgl +{ +inline void mesh_handle::bind() const +{ + glBindVertexArray(vao_id); +} + +inline void mesh_handle::unbind() +{ + glBindVertexArray(0); +} +} \ No newline at end of file diff --git a/source/opengl/handles/point_cloud_handle.ipp b/source/opengl/handles/point_cloud_handle.ipp new file mode 100644 index 0000000..a773a5f --- /dev/null +++ b/source/opengl/handles/point_cloud_handle.ipp @@ -0,0 +1,16 @@ +#ifndef INCLUDE_POINT_CLOUD_INSTANCE_IMPLEMENTATION +#error Never include this file directly include 'point_cloud_handle.hpp' +#endif + +namespace zgl +{ +inline void point_cloud_handle::bind() const +{ + glBindVertexArray(vao_id); +} + +inline void point_cloud_handle::unbind() +{ + glBindVertexArray(0); +} +} diff --git a/source/opengl/handles/shader_program_handle.ipp b/source/opengl/handles/shader_program_handle.ipp new file mode 100755 index 0000000..537dbbd --- /dev/null +++ b/source/opengl/handles/shader_program_handle.ipp @@ -0,0 +1,63 @@ +#ifndef INCLUDE_GL_SHADER_PROGRAM_INSTANCE_IMPLEMENTATION +#error Never include this file directly include 'shader_program_handle.hpp' +#endif + +#include "glm/glm.hpp" +#include "glm/gtc/type_ptr.hpp" + +namespace zgl +{ +inline void shader_program_handle::bind() const +{ + glUseProgram(program_id); +} + +inline void shader_program_handle::unbind() +{ + glUseProgram(0); +} + +template +void shader_program_handle::set_uniform(const T& value) const +{ + if constexpr (std::same_as) + { + static_assert(VariableInfo.type == GL_FLOAT_MAT4); + glUniformMatrix4fv(VariableInfo.location, 1, false, glm::value_ptr(value)); + } + else if constexpr (std::same_as) + { + static_assert(VariableInfo.type == GL_FLOAT_MAT3); + glUniformMatrix3fv(VariableInfo.location, 1, false, glm::value_ptr(value)); + } + else if constexpr (std::same_as) + { + static_assert(VariableInfo.type == GL_FLOAT_VEC4); + glUniform4fv(VariableInfo.location, 1, glm::value_ptr(value)); + } + else if constexpr (std::same_as) + { + static_assert(VariableInfo.type == GL_FLOAT_VEC3); + glUniform3fv(VariableInfo.location, 1, glm::value_ptr(value)); + } + else if constexpr (std::same_as) + { + static_assert(VariableInfo.type == GL_FLOAT_VEC2); + glUniform2fv(VariableInfo.location, 1, glm::value_ptr(value)); + } + else if constexpr (std::same_as) + { + static_assert(VariableInfo.type == GL_FLOAT); + glUniform1f(VariableInfo.location, value); + } + else if constexpr (std::same_as) + { + static_assert(VariableInfo.type == GL_INT or VariableInfo.type == GL_SAMPLER_2D); + glUniform1i(VariableInfo.location, value); + } + else + { + T::_unknown_shader_uniform_type_; + } +} +} \ No newline at end of file diff --git a/source/opengl/handles/texture_handle.ipp b/source/opengl/handles/texture_handle.ipp new file mode 100644 index 0000000..0693a93 --- /dev/null +++ b/source/opengl/handles/texture_handle.ipp @@ -0,0 +1,16 @@ +#ifndef INCLUDE_TEXTURE_INSTANCE_IMPLEMENTATION +#error Never include this file directly include 'texture_handle.hpp' +#endif + +namespace zgl +{ +inline void texture_handle::bind() const +{ + glBindTexture(GL_TEXTURE_2D, texture_id); +} + +inline void texture_handle::unbind() +{ + glBindTexture(GL_TEXTURE_2D, 0); +} +} // namespace zgl