diff --git a/.gitignore b/.gitignore index 259148f..1cf1d0f 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,9 @@ *.exe *.out *.app + +# Custom +.idea/* +build/* +bindings/* +debug/* diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0a3036c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,70 @@ +cmake_minimum_required(VERSION 3.0) +set(PROJECT_NAME "Task-3-3D-scene") + +project(${PROJECT_NAME} CXX) + +set(CMAKE_PREFIX_PATH ${CMAKE_BINARY_DIR}) +set(CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR}) +set(CMAKE_CXX_STANDARD 17) + +# CONFIG option is important so that CMake doesnt search for modules into the default modules directory +find_package(imgui CONFIG) +find_package(glfw CONFIG) +find_package(glew CONFIG) +find_package(fmt CONFIG) +find_package(glm CONFIG) +find_package(stb CONFIG) +find_package(assimp CONFIG) + +add_executable( + ${PROJECT_NAME} + + src/main.cpp + src/settings.h + + src/utils/mesh.h + src/utils/model.h + src/utils/opengl.h + src/utils/shader.cpp + + src/elements/camera.h + src/elements/controller.h + src/elements/light.h + src/elements/object.h + src/elements/cubemap.h + src/elements/torus.h + src/elements/window.h + + bindings/imgui_impl_glfw.cpp + bindings/imgui_impl_opengl3.cpp +) + +add_custom_command( + TARGET ${PROJECT_NAME} + POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory ${PROJECT_SOURCE_DIR}/assets ${PROJECT_BINARY_DIR}/assets +) + +target_compile_definitions( + ${PROJECT_NAME} + PUBLIC + IMGUI_IMPL_OPENGL_LOADER_GLEW +) + +target_link_libraries( + ${PROJECT_NAME} + imgui::imgui + GLEW::glew_s + glfw::glfw + fmt::fmt + glm::glm + stb::stb + assimp::assimp +) + +install( + DIRECTORY + ${PROJECT_SOURCE_DIR}/assets + + DESTINATION ${PROJECT_BINARY_DIR} +) diff --git a/README.md b/README.md index 985e0a8..b9c9a08 100644 --- a/README.md +++ b/README.md @@ -1 +1,34 @@ -# ComputerGraphics-OpenGL \ No newline at end of file +# 3D сцена + +### Торическая земля: +- **Ландшафт** (max 1.25) + - Генерируется по карте высот (позиции вершин, нормали и текстурные координаты) (0.5) + - Текстурируется набором тайлов (0.25) + - переключение по высоте/нормали либо по карте материалов (+0.25) + - detail текстура при приближении (+0.25) +- **Торический ландшафт** (1) + - Карта высот применяется к тору, как к базовой поверхности. +- **Управляемая 3D модель** (1/1.25) + - Перемещается по поверхности торического ландшафта + - Ориентирована по карте высот + - "Приклеена" к карте высот + - Камера крепится к модели + - "Пружинная" камера (+0.25) +- **Источники света** (0.25/1.25) + - Фары/источник света, связанный с объектом, без теней (0.25) + - Направленный (солнце) + тени + - без каскадов (только объекта на ландшафт) (0.5) + - с каскадами (тени от ландшафта и объекта с самозатенением) (1) + +## Запуск +Собираем проект: +1) Windows `build.bat` +2) Linux `build.sh` + +Далее запускаем `build/Task-3-3Dscene` + +## Примеры +![](screenshots/HW3.png) + +![](screenshots/HW3-2.png) + diff --git a/assets/objects/aircraft/piper_pa18.mtl b/assets/objects/aircraft/piper_pa18.mtl new file mode 100644 index 0000000..813fa90 --- /dev/null +++ b/assets/objects/aircraft/piper_pa18.mtl @@ -0,0 +1,111 @@ +# Blender MTL File: 'piper_pa18.blend' +# Material Count: 10 + +newmtl base +Ns 96.078431 +Ka 0.300000 0.300000 0.300000 +Kd 0.683972 0.355875 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 5 +map_Kd textures/piper_diffuse.jpg +map_Bump textures/piper_bump.jpg + +newmtl base_NONE +Ns 96.078431 +Ka 0.300000 0.300000 0.300000 +Kd 0.683972 0.355875 0.000000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 5 +map_Bump textures/piper_bump.jpg +map_Kd textures/piper_diffuse.jpg + +newmtl glass +Ns 96.078431 +Ka 0.080000 0.080000 0.080000 +Kd 0.683972 0.683972 0.683972 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.300000 +d 0.100000 +illum 7 +map_Kd textures/piper_diffuse.jpg + +newmtl glass_NONE +Ns 96.078431 +Ka 0.080000 0.080000 0.080000 +Kd 0.683972 0.683972 0.683972 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.300000 +d 0.100000 +illum 7 + +newmtl internals_alu +Ns 96.078431 +Ka 0.500000 0.500000 0.500000 +Kd 0.336015 0.336015 0.336015 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 5 +map_Kd textures/piper_diffuse.jpg + +newmtl internals_alu_NONE +Ns 96.078431 +Ka 0.500000 0.500000 0.500000 +Kd 0.336015 0.336015 0.336015 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 5 + +newmtl internals_b +Ns 96.078431 +Ka 0.100000 0.100000 0.100000 +Kd 0.007214 0.007214 0.007214 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 3 + +newmtl mirror +Ns 96.078431 +Ka 0.080000 0.080000 0.080000 +Kd 0.560000 0.560000 0.560000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 0.200000 +illum 5 + +newmtl prop +Ns 96.078431 +Ka 0.100000 0.100000 0.100000 +Kd 0.011735 0.011735 0.011735 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 5 +map_Kd textures/piper_diffuse.jpg + +newmtl tires +Ns 96.078431 +Ka 1.000000 1.000000 1.000000 +Kd 0.005754 0.005754 0.005754 +Ks 0.300000 0.300000 0.300000 +Ke 0.000000 0.000000 0.000000 +Ni 1.000000 +d 1.000000 +illum 2 +map_Kd textures/piper_diffuse.jpg +map_Ks textures/piper_refl.jpg diff --git a/assets/objects/aircraft/textures/piper_bump.jpg b/assets/objects/aircraft/textures/piper_bump.jpg new file mode 100644 index 0000000..7828b52 Binary files /dev/null and b/assets/objects/aircraft/textures/piper_bump.jpg differ diff --git a/assets/objects/aircraft/textures/piper_diffuse.jpg b/assets/objects/aircraft/textures/piper_diffuse.jpg new file mode 100644 index 0000000..c3edd50 Binary files /dev/null and b/assets/objects/aircraft/textures/piper_diffuse.jpg differ diff --git a/assets/objects/aircraft/textures/piper_refl.jpg b/assets/objects/aircraft/textures/piper_refl.jpg new file mode 100644 index 0000000..4afe297 Binary files /dev/null and b/assets/objects/aircraft/textures/piper_refl.jpg differ diff --git a/assets/objects/ball/Leather_Black_albedo.jpg b/assets/objects/ball/Leather_Black_albedo.jpg new file mode 100644 index 0000000..8b6cc0d Binary files /dev/null and b/assets/objects/ball/Leather_Black_albedo.jpg differ diff --git a/assets/objects/ball/Leather_Black_normal.jpg b/assets/objects/ball/Leather_Black_normal.jpg new file mode 100644 index 0000000..e772312 Binary files /dev/null and b/assets/objects/ball/Leather_Black_normal.jpg differ diff --git a/assets/objects/ball/Leather_White_albedo.jpg b/assets/objects/ball/Leather_White_albedo.jpg new file mode 100644 index 0000000..736f3f1 Binary files /dev/null and b/assets/objects/ball/Leather_White_albedo.jpg differ diff --git a/assets/objects/ball/Leather_White_normal.jpg b/assets/objects/ball/Leather_White_normal.jpg new file mode 100644 index 0000000..d9fc961 Binary files /dev/null and b/assets/objects/ball/Leather_White_normal.jpg differ diff --git a/assets/objects/ball/Soccer_Ball.mtl b/assets/objects/ball/Soccer_Ball.mtl new file mode 100644 index 0000000..aea2485 --- /dev/null +++ b/assets/objects/ball/Soccer_Ball.mtl @@ -0,0 +1,44 @@ +# 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware +# File Created: 13.03.2019 02:41:43 + +newmtl Black + Ns 50.0000 + Ni 1.5000 + d 1.0000 + Tr 0.0000 + Tf 1.0000 1.0000 1.0000 + illum 2 + Ka 0.5880 0.5880 0.5880 + Kd 0.5880 0.5880 0.5880 + Ks 0.6300 0.6300 0.6300 + Ke 0.0000 0.0000 0.0000 + map_Ka Leather_Black_albedo.jpg + map_Kd Leather_Black_albedo.jpg + map_bump Leather_Black_normal.jpg + bump Leather_Black_normal.jpg + +newmtl White + Ns 50.0000 + Ni 1.5000 + d 1.0000 + Tr 0.0000 + Tf 1.0000 1.0000 1.0000 + illum 2 + Ka 0.5880 0.5880 0.5880 + Kd 0.5880 0.5880 0.5880 + Ks 0.6300 0.6300 0.6300 + Ke 0.0000 0.0000 0.0000 + map_Ka Leather_White_albedo.jpg + map_Kd Leather_White_albedo.jpg + map_bump Leather_White_normal.jpg + bump Leather_White_normal.jpg + +newmtl wire_086086086 + Ns 32 + d 1 + Tr 0 + Tf 1 1 1 + illum 2 + Ka 0.3373 0.3373 0.3373 + Kd 0.3373 0.3373 0.3373 + Ks 0.3500 0.3500 0.3500 diff --git a/assets/objects/ball/license.txt b/assets/objects/ball/license.txt new file mode 100644 index 0000000..3838b44 --- /dev/null +++ b/assets/objects/ball/license.txt @@ -0,0 +1 @@ +Contains textures from Textures.com - Textures may not be redistributed. Check Textures.com for full licensing. \ No newline at end of file diff --git a/assets/objects/skybox/textures/simple/back.jpg b/assets/objects/skybox/textures/simple/back.jpg new file mode 100644 index 0000000..470a679 Binary files /dev/null and b/assets/objects/skybox/textures/simple/back.jpg differ diff --git a/assets/objects/skybox/textures/simple/bottom.jpg b/assets/objects/skybox/textures/simple/bottom.jpg new file mode 100644 index 0000000..893f394 Binary files /dev/null and b/assets/objects/skybox/textures/simple/bottom.jpg differ diff --git a/assets/objects/skybox/textures/simple/front.jpg b/assets/objects/skybox/textures/simple/front.jpg new file mode 100644 index 0000000..4e17b77 Binary files /dev/null and b/assets/objects/skybox/textures/simple/front.jpg differ diff --git a/assets/objects/skybox/textures/simple/left.jpg b/assets/objects/skybox/textures/simple/left.jpg new file mode 100644 index 0000000..5750b91 Binary files /dev/null and b/assets/objects/skybox/textures/simple/left.jpg differ diff --git a/assets/objects/skybox/textures/simple/right.jpg b/assets/objects/skybox/textures/simple/right.jpg new file mode 100644 index 0000000..8963037 Binary files /dev/null and b/assets/objects/skybox/textures/simple/right.jpg differ diff --git a/assets/objects/skybox/textures/simple/top.jpg b/assets/objects/skybox/textures/simple/top.jpg new file mode 100644 index 0000000..4db3c2a Binary files /dev/null and b/assets/objects/skybox/textures/simple/top.jpg differ diff --git a/assets/objects/torus/textures/grass_flower.jpg b/assets/objects/torus/textures/grass_flower.jpg new file mode 100644 index 0000000..5b7437e Binary files /dev/null and b/assets/objects/torus/textures/grass_flower.jpg differ diff --git a/assets/objects/torus/textures/grass_stone.jpg b/assets/objects/torus/textures/grass_stone.jpg new file mode 100644 index 0000000..d1c2b1b Binary files /dev/null and b/assets/objects/torus/textures/grass_stone.jpg differ diff --git a/assets/objects/torus/textures/height_map.jpg b/assets/objects/torus/textures/height_map.jpg new file mode 100644 index 0000000..2c517e0 Binary files /dev/null and b/assets/objects/torus/textures/height_map.jpg differ diff --git a/assets/objects/torus/textures/height_map_2.jpg b/assets/objects/torus/textures/height_map_2.jpg new file mode 100644 index 0000000..c719663 Binary files /dev/null and b/assets/objects/torus/textures/height_map_2.jpg differ diff --git a/assets/objects/torus/textures/plaster_wall.jpg b/assets/objects/torus/textures/plaster_wall.jpg new file mode 100644 index 0000000..43db652 Binary files /dev/null and b/assets/objects/torus/textures/plaster_wall.jpg differ diff --git a/assets/objects/torus/textures/soil.jpg b/assets/objects/torus/textures/soil.jpg new file mode 100644 index 0000000..32a79a5 Binary files /dev/null and b/assets/objects/torus/textures/soil.jpg differ diff --git a/assets/shaders/cubemap_fragment.glsl b/assets/shaders/cubemap_fragment.glsl new file mode 100644 index 0000000..957a1af --- /dev/null +++ b/assets/shaders/cubemap_fragment.glsl @@ -0,0 +1,11 @@ +#version 330 core + +out vec4 FragColor; + +in vec3 tex_coords; + +uniform samplerCube u_skybox; + +void main() { + FragColor = texture(u_skybox, tex_coords); +} diff --git a/assets/shaders/cubemap_vertex.glsl b/assets/shaders/cubemap_vertex.glsl new file mode 100644 index 0000000..52acb18 --- /dev/null +++ b/assets/shaders/cubemap_vertex.glsl @@ -0,0 +1,14 @@ +#version 330 core + +layout (location = 0) in vec3 in_pos; + +out vec3 tex_coords; + +uniform mat4 u_projection; +uniform mat4 u_view; + +void main() { + tex_coords = in_pos; + vec4 pos = u_projection * u_view * vec4(in_pos, 1.0); + gl_Position = pos.xyww; +} diff --git a/assets/shaders/light_fragment.glsl b/assets/shaders/light_fragment.glsl new file mode 100644 index 0000000..abd714e --- /dev/null +++ b/assets/shaders/light_fragment.glsl @@ -0,0 +1,5 @@ +#version 330 core + +void main() { + gl_FragDepth = gl_FragCoord.z; +} diff --git a/assets/shaders/light_vertex.glsl b/assets/shaders/light_vertex.glsl new file mode 100644 index 0000000..48c6bf1 --- /dev/null +++ b/assets/shaders/light_vertex.glsl @@ -0,0 +1,10 @@ +#version 330 core + +layout (location = 0) in vec3 in_pos; + +uniform mat4 u_projection; +uniform mat4 u_view; + +void main() { + gl_Position = u_projection * u_view * vec4(in_pos, 1.0); +} diff --git a/assets/shaders/object_fragment.glsl b/assets/shaders/object_fragment.glsl new file mode 100644 index 0000000..1e94103 --- /dev/null +++ b/assets/shaders/object_fragment.glsl @@ -0,0 +1,13 @@ +#version 330 core + +out vec4 FragColor; + +in vec3 vs_position; +in vec3 vs_normal; +in vec2 vs_texture_pos; + +uniform sampler2D u_texture_1; + +void main() { + FragColor = texture(u_texture_1, vs_texture_pos); +} diff --git a/assets/shaders/object_vertex.glsl b/assets/shaders/object_vertex.glsl new file mode 100644 index 0000000..1949a3a --- /dev/null +++ b/assets/shaders/object_vertex.glsl @@ -0,0 +1,21 @@ +#version 330 core + +layout (location = 0) in vec3 in_position; +layout (location = 1) in vec3 in_normal; +layout (location = 2) in vec2 in_texture_pos; + +out vec3 vs_position; +out vec3 vs_normal; +out vec2 vs_texture_pos; + +uniform mat4 u_model; +uniform mat4 u_view; +uniform mat4 u_projection; + +void main() { + vs_position = vec3(u_model * vec4(in_position, 1.0)); + vs_normal = mat3(transpose(inverse(u_model))) * in_normal; + vs_texture_pos = in_texture_pos; + + gl_Position = u_projection * u_view * u_model * vec4(in_position, 1.0); +} diff --git a/assets/shaders/torus_fragment.glsl b/assets/shaders/torus_fragment.glsl new file mode 100644 index 0000000..3ab0529 --- /dev/null +++ b/assets/shaders/torus_fragment.glsl @@ -0,0 +1,88 @@ +#version 330 core + +out vec4 FragColor; + +struct VS { + vec3 position; + vec3 normal; + vec3 texture_pos; +}; +in VS vs; + +uniform sampler2D u_texture1; +uniform sampler2D u_texture2; +uniform sampler2D u_texture3; +uniform sampler2D u_texture4; +uniform sampler2D u_texture_depth; + +uniform float u_height_min; +uniform float u_height_max; +uniform float u_height_shift; + +uniform mat4 u_light_view; +uniform mat4 u_light_projection; + +uniform mat4 u_spotlight_view; +uniform mat4 u_spotlight_projection; +uniform float u_spotlight_cone; + +// Light +float evaluate_light_coefficient(float diffuse) { + vec4 light_position = u_light_projection * u_light_view * vec4(vs.position, 1.0); + light_position = light_position / light_position.w; + light_position = light_position / 2 + 0.5f; + + float bias = 0.005f; + float shadow_coefficient = texture(u_texture_depth, light_position.xy).x < light_position.z - bias + ? 0.0f + : 1.0f; + float illumination_level = 0.15f; + float light_coefficient = min(shadow_coefficient, diffuse) + illumination_level; + + return light_coefficient; +} + +// Spotlight +float evaluate_spotlight_coefficient() { + vec4 spotlight_position = u_spotlight_projection * u_spotlight_view * vec4(vs.position, 1.0); + + spotlight_position = spotlight_position / spotlight_position.w; + spotlight_position = spotlight_position / 2 + 0.5f; + + float spotlight = 0.0f; + if ( + spotlight_position.x > 0 && spotlight_position.x < 1 && + spotlight_position.y > 0 && spotlight_position.y < 1 && + spotlight_position.z > 0 && spotlight_position.z < 1 + ) { + spotlight = 0.4f; + } + + return spotlight; +} + +void main() { + vec3 light_dir = normalize(vec3(1.0f, 1.0f, 1.0f)); + float light_diffuse = max(dot(vs.normal, light_dir), 0.0); + + vec3 color; + float height = (vs.texture_pos.z - u_height_min) / (u_height_max - u_height_min); + if (height > 0.9f) + color = mix( + texture(u_texture2, vs.texture_pos.xy).rgb, + texture(u_texture3, vs.texture_pos.xy).rgb, + 0.5f + ); + else if (height > 0.6f) + color = mix( + texture(u_texture3, vs.texture_pos.xy).rgb, + texture(u_texture4, vs.texture_pos.xy).rgb, + 0.25f + ); + else color = texture(u_texture4, vs.texture_pos.xy).rgb; + + float light_coefficient = evaluate_light_coefficient(light_diffuse); + float spotlight = evaluate_spotlight_coefficient(); + + FragColor = vec4(color * light_coefficient + spotlight, 1.0f); +} diff --git a/assets/shaders/torus_vertex.glsl b/assets/shaders/torus_vertex.glsl new file mode 100644 index 0000000..fbc0447 --- /dev/null +++ b/assets/shaders/torus_vertex.glsl @@ -0,0 +1,23 @@ +#version 330 core + +layout (location = 0) in vec3 in_position; +layout (location = 1) in vec3 in_normal; +layout (location = 2) in vec3 in_texture_pos; + +struct VS { + vec3 position; + vec3 normal; + vec3 texture_pos; +}; +out VS vs; + +uniform mat4 u_projection; +uniform mat4 u_view; + +void main() { + vs.position = in_position; + vs.normal = in_normal; + vs.texture_pos = in_texture_pos; + + gl_Position = u_projection * u_view * vec4(in_position, 1.0); +} diff --git a/build.bat b/build.bat new file mode 100644 index 0000000..3898417 --- /dev/null +++ b/build.bat @@ -0,0 +1,9 @@ +@ECHO ON + +RMDIR /Q /S build +MKDIR build +PUSHD build + +conan install .. +cmake .. -G "Visual Studio 15 2017 Win64" +cmake --build . --config Release diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..a09a066 --- /dev/null +++ b/build.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -e +set -x + +rm -rf build +mkdir build +pushd build + +export CONAN_SYSREQUIRES_MODE=enabled +conan install .. +cmake .. -DCMAKE_BUILD_TYPE=Release +cmake --build . diff --git a/conanfile.txt b/conanfile.txt new file mode 100644 index 0000000..8e1460f --- /dev/null +++ b/conanfile.txt @@ -0,0 +1,21 @@ +[requires] +imgui/1.74 +glfw/3.3.2 +glew/2.1.0 +fmt/7.0.3 +glm/0.9.9.8 +stb/20200203 +assimp/5.0.1 + +[generators] +cmake_find_package_multi + +[options] +glew:shared=False +fmt:header_only=True + +[imports] +./res/bindings, imgui_impl_glfw.cpp -> ../bindings +./res/bindings, imgui_impl_opengl3.cpp -> ../bindings +./res/bindings, imgui_impl_glfw.h -> ../bindings +./res/bindings, imgui_impl_opengl3.h -> ../bindings diff --git a/screenshots/HW3-2.png b/screenshots/HW3-2.png new file mode 100644 index 0000000..21321e4 Binary files /dev/null and b/screenshots/HW3-2.png differ diff --git a/screenshots/HW3.png b/screenshots/HW3.png new file mode 100644 index 0000000..b53aac8 Binary files /dev/null and b/screenshots/HW3.png differ diff --git a/src/elements/camera.h b/src/elements/camera.h new file mode 100644 index 0000000..b0f4033 --- /dev/null +++ b/src/elements/camera.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include "../utils/model.h" + + +class Camera { +public: + glm::vec3 position_; + glm::vec3 eye_{}; + glm::vec3 normal_{}; + + float angle_ = 90.0f; + +public: + explicit Camera(glm::vec3 position) : position_(position) {} + + [[nodiscard]] + glm::vec2 direction() const { + return glm::vec2(cos(glm::radians(angle_)), sin(glm::radians(angle_))); + } + + [[nodiscard]] + glm::mat4 view() const { + return glm::lookAt(eye_, position_, normal_); + } + + [[nodiscard]] + glm::vec3 position() const { + return position_; + } + + [[nodiscard]] + glm::vec3 normal() const { + return normal_; + } + + [[nodiscard]] + float angle() const { + return angle_; + } +}; diff --git a/src/elements/controller.h b/src/elements/controller.h new file mode 100644 index 0000000..60cf03e --- /dev/null +++ b/src/elements/controller.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include +#include "torus.h" +#include "camera.h" + +std::array keys; + +inline void key_callback(GLFWwindow *window, int key, int scancode, int action, int mode) { + if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) { + glfwSetWindowShouldClose(window, GL_TRUE); + } + + if (key >= 0 && key < 1024) { + if (action == GLFW_PRESS) keys[key] = true; + else if (action == GLFW_RELEASE) keys[key] = false; + } +} + +class Controller { +private: + using Table = std::vector>; + + TorusCoordinateTable table_; + Camera camera_ = Camera(glm::vec3(0, 0, 0)); + + glm::vec2 plane_ = {0.0f, 0.0f}; + const glm::ivec2 plane_max_ = {1024, 256}; + + float angle_shift_ = 1.0f; + +public: + explicit Controller(const Torus &torus) : table_(torus.table()) { + camera_ = Camera(get_position(glm::vec2(0.0f, 0.0f))); + } + + Camera camera() { + return camera_; + } + + void key_events() { + glm::vec2 direction = camera_.direction(); + + if (keys[GLFW_KEY_W]) plane_ += direction; + if (keys[GLFW_KEY_S]) plane_ -= direction; + + if (keys[GLFW_KEY_A]) camera_.angle_ += angle_shift_; + if (keys[GLFW_KEY_D]) camera_.angle_ -= angle_shift_; + + glm::vec2 point1 = plane_; + glm::vec2 point2 = plane_ - direction * 5.0f; + + camera_.position_ = get_position(point1) + 1.5f * get_normal(point1); + camera_.eye_ = get_position(point2) + 2.9f * get_normal(point2); + camera_.normal_ = get_normal(point1); + } + + [[nodiscard]] + glm::vec3 get_normal(glm::vec2 point) { + return barycentric_coordinates(point, table_.normal); + } + + [[nodiscard]] + glm::vec3 get_position(glm::vec2 point) { + return barycentric_coordinates(point, table_.position); + } + + [[nodiscard]] + glm::vec2 plane() const { + return plane_; + } + +private: + glm::vec3 barycentric_coordinates(glm::vec2 point, const Table &table) { + glm::ivec2 p1 = normalize(point); + glm::ivec2 p2 = normalize(p1 + glm::ivec2(0, 1)); + glm::ivec2 p3 = normalize(p1 + glm::ivec2(1, 1)); + glm::ivec2 p4 = normalize(p1 + glm::ivec2(1, 0)); + + glm::vec2 offset = point - glm::vec2(glm::ivec2(point)); + + if (offset.y > offset.x) { + return glm::vec3( + (1 - offset.y) * table[p1.x][p1.y] + + offset.x * table[p3.x][p3.y] + + (offset.y - offset.x) * table[p2.x][p2.y] + ); + } + + return glm::vec3( + (1 - offset.x) * table[p1.x][p1.y] + + offset.y * table[p3.x][p3.y] + + (offset.x - offset.y) * table[p4.x][p4.y] + ); + } + + [[nodiscard]] + glm::ivec2 normalize(const glm::ivec2 &vec) const { + return ((vec % plane_max_) + plane_max_) % plane_max_; + } +}; diff --git a/src/elements/cubemap.h b/src/elements/cubemap.h new file mode 100644 index 0000000..dc78bef --- /dev/null +++ b/src/elements/cubemap.h @@ -0,0 +1,128 @@ +#include +#include "../utils/shader.h" +#include "../utils/model.h" +#include "../settings.h" + + +class Cubemap { +private: + Shader shader_; + GLuint cubemap_texture_; + GLuint cubemap_vao; + GLuint cubemap_vbo; + +public: + explicit Cubemap(const Shader &shader, const std::vector &cube_textures) + : shader_(shader), + cubemap_vao(0), + cubemap_vbo(0) { + cubemap_texture_ = load_cubemap(cube_textures); + init_buffers(cubemap_vbo, cubemap_vao); + } + + ~Cubemap() { + glDeleteVertexArrays(1, &cubemap_vao); + glDeleteBuffers(1, &cubemap_vbo); + } + + void render(glm::mat4 &view, glm::mat4 &projection) { + glDepthFunc(GL_LEQUAL); + shader_.use(); + shader_.set_uniform("u_skybox", 0); + shader_.set_uniform("u_view", glm::value_ptr(view)); + shader_.set_uniform("u_projection", glm::value_ptr(projection)); + + glBindVertexArray(cubemap_vao); + glActiveTexture(Settings::GL_TEXTURE_CUBEMAP); + glBindTexture(GL_TEXTURE_CUBE_MAP, cubemap_texture_); + glDrawArrays(GL_TRIANGLES, 0, 36); + glBindVertexArray(0); + glDepthFunc(GL_LESS); + } + +private: + static GLuint load_cubemap(const std::vector &maps) { + GLuint textureID; + glGenTextures(1, &textureID); + glBindTexture(GL_TEXTURE_CUBE_MAP, textureID); + + int width, height, nrComponents; + for (GLuint i = 0; i < maps.size(); i++) { + GLubyte *data = stbi_load(maps[i].c_str(), &width, &height, &nrComponents, 0); + if (data) { + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, + 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data); + stbi_image_free(data); + } else { + std::cout << "Cubemap texture failed to load at path: " << maps[i] << std::endl; + stbi_image_free(data); + } + } + + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + + return textureID; + } + + void init_buffers(GLuint &skyboxVBO, GLuint &skyboxVAO) { + glGenVertexArrays(1, &skyboxVAO); + glGenBuffers(1, &skyboxVBO); + glBindVertexArray(skyboxVAO); + glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO); + glBufferData(GL_ARRAY_BUFFER, + skybox_vertices.size() * sizeof(skybox_vertices[0]), + skybox_vertices.data(), + GL_STATIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void *) nullptr); + } + +private: + const std::vector skybox_vertices = { + -20.0f, 20.0f, -20.0f, + -20.0f, -20.0f, -20.0f, + 20.0f, -20.0f, -20.0f, + 20.0f, -20.0f, -20.0f, + 20.0f, 20.0f, -20.0f, + -20.0f, 20.0f, -20.0f, + + -20.0f, -20.0f, 20.0f, + -20.0f, -20.0f, -20.0f, + -20.0f, 20.0f, -20.0f, + -20.0f, 20.0f, -20.0f, + -20.0f, 20.0f, 20.0f, + -20.0f, -20.0f, 20.0f, + + 20.0f, -20.0f, -20.0f, + 20.0f, -20.0f, 20.0f, + 20.0f, 20.0f, 20.0f, + 20.0f, 20.0f, 20.0f, + 20.0f, 20.0f, -20.0f, + 20.0f, -20.0f, -20.0f, + + -20.0f, -20.0f, 20.0f, + -20.0f, 20.0f, 20.0f, + 20.0f, 20.0f, 20.0f, + 20.0f, 20.0f, 20.0f, + 20.0f, -20.0f, 20.0f, + -20.0f, -20.0f, 20.0f, + + -20.0f, 20.0f, -20.0f, + 20.0f, 20.0f, -20.0f, + 20.0f, 20.0f, 20.0f, + 20.0f, 20.0f, 20.0f, + -20.0f, 20.0f, 20.0f, + -20.0f, 20.0f, -20.0f, + + -20.0f, -20.0f, -20.0f, + -20.0f, -20.0f, 20.0f, + 20.0f, -20.0f, -20.0f, + 20.0f, -20.0f, -20.0f, + -20.0f, -20.0f, 20.0f, + 20.0f, -20.0f, 20.0f + }; +}; diff --git a/src/elements/light.h b/src/elements/light.h new file mode 100644 index 0000000..c3c1e2b --- /dev/null +++ b/src/elements/light.h @@ -0,0 +1,81 @@ +#pragma once + + +#include +#include +#include +#include +#include "../utils/shader.h" +#include "../settings.h" + +class Light { +private: + Shader shader_; + + GLuint depth_map_FBO{}; + GLuint depth_map{}; + + glm::mat4 light_view = glm::lookAt( + Settings::LIGHT_POSITION, + glm::vec3(0.0f), + glm::vec3(0.0, 1.0, 0.0) + ); + glm::mat4 light_projection = glm::ortho( + -6.0f, 6.0f, + -6.0f, 6.0f, + Settings::LIGHT_Z_NEAR, Settings::LIGHT_Z_FAR + ); + +public: + explicit Light(const Shader &shader) : shader_(shader) { + create_depth_texture(); + } + + void render(glm::mat4 &model) { + glBindFramebuffer(GL_FRAMEBUFFER, depth_map_FBO); + glViewport(0, 0, Settings::SHADOW_WIDTH, Settings::SHADOW_HEIGHT); + glClear(GLuint(GL_DEPTH_BUFFER_BIT)); + + shader_.use(); + shader_.set_uniform("u_view", glm::value_ptr(light_view)); + shader_.set_uniform("u_projection", glm::value_ptr(light_projection)); + } + + void activate() const { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + glActiveTexture(Settings::GL_TEXTURE_DEPTH_MAP); + glBindTexture(GL_TEXTURE_2D, depth_map); + } + + [[nodiscard]] + glm::mat4 projection() const { + return light_projection; + } + + [[nodiscard]] + glm::mat4 view() const { + return light_view; + } + +private: + void create_depth_texture() { + glGenFramebuffers(1, &depth_map_FBO); + + glGenTextures(1, &depth_map); + glBindTexture(GL_TEXTURE_2D, depth_map); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, + Settings::SHADOW_WIDTH, Settings::SHADOW_HEIGHT, + 0, GL_DEPTH_COMPONENT, GL_FLOAT, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + + glBindFramebuffer(GL_FRAMEBUFFER, depth_map_FBO); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depth_map, 0); + glDrawBuffer(GL_NONE); + glReadBuffer(GL_NONE); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } +}; diff --git a/src/elements/object.h b/src/elements/object.h new file mode 100644 index 0000000..cc483e3 --- /dev/null +++ b/src/elements/object.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include +#include "../utils/shader.h" +#include "../utils/model.h" + +class Object { +private: + Shader shader_; + Model model_; + +public: + explicit Object(const Shader &shader, const std::string &path_to_model) + : shader_(shader), + model_(Model(path_to_model)) {} + +public: + void render(glm::mat4 &model, glm::mat4 &view, glm::mat4 &projection) { + shader_.use(); + + shader_.set_uniform("u_model", glm::value_ptr(model)); + shader_.set_uniform("u_view", glm::value_ptr(view)); + shader_.set_uniform("u_projection", glm::value_ptr(projection)); + + model_.draw(shader_); + } +}; diff --git a/src/elements/torus.h b/src/elements/torus.h new file mode 100644 index 0000000..57eefc1 --- /dev/null +++ b/src/elements/torus.h @@ -0,0 +1,276 @@ +#pragma once + +#include +#include "../utils/mesh.h" +#include "../utils/model.h" + +struct TorusCoordinateTable { + using POSITION = std::vector>; + using NORMAL = std::vector>; + + POSITION position; + NORMAL normal; + +private: + glm::ivec2 vec_size_{}; + +public: + TorusCoordinateTable() = default; + + explicit TorusCoordinateTable(glm::ivec2 vec_size) : vec_size_(vec_size) { + // Init table (rectangle pos -> torus pos) + resize_buffer(position); + resize_buffer(normal); + } + +private: + void resize_buffer(std::vector> &table) const { + table.resize(vec_size_.x); + for (auto &elem : table) { + elem.resize(vec_size_.y); + } + } +}; + + +class Torus { +// Const values +private: + // Torus settings + const float radius_major_ = 5.0f; + const float radius_minor_ = 1.0f; + + const glm::ivec2 plane = {1024, 256}; + +// Variables +private: + using Point = glm::ivec2; + + // Buffers + GLuint vao_; + std::vector vertices; + std::vector indices; + TorusCoordinateTable rectangle2torus; + + // Height map + GLubyte *height_map_{}; + GLint height_map_width_{}; + GLint height_map_height_{}; + GLint height_map_components_{}; + + // Shader + Shader shader_; + + float height_min_{}; + float height_max_{}; + +public: + explicit Torus(const Shader &shader) : shader_(shader), vao_(0), rectangle2torus(plane) { + load_textures(); + load_height_map(); + fill_buffers(); + bind_buffers(); + + std::vector heights; + for (auto &raw: rectangle2torus.position) for (auto &elem: raw) heights.push_back(elem.z); + + height_min_ = *std::min_element(heights.begin(), heights.end()); + height_max_ = *std::max_element(heights.begin(), heights.end()); + } + + void render( + glm::mat4 &view, glm::mat4 &projection, + glm::mat4 &light_view, glm::mat4 &light_projection, + glm::mat4 &spotlight_view, glm::mat4 &spotlight_projection + ) { + shader_.use(); + shader_.set_uniform("u_view", glm::value_ptr(view)); + shader_.set_uniform("u_projection", glm::value_ptr(projection)); + + shader_.set_uniform("u_light_view", glm::value_ptr(light_view)); + shader_.set_uniform("u_light_projection", glm::value_ptr(light_projection)); + + shader_.set_uniform("u_spotlight_view", glm::value_ptr(spotlight_view)); + shader_.set_uniform("u_spotlight_projection", glm::value_ptr(spotlight_projection)); + shader_.set_uniform("u_spotlight_cone", glm::cos(glm::radians(12.5f))); + + shader_.set_uniform("u_texture1", 1); + shader_.set_uniform("u_texture2", 2); + shader_.set_uniform("u_texture3", 3); + shader_.set_uniform("u_texture4", 4); + shader_.set_uniform("u_texture_depth", 5); + + shader_.set_uniform("u_height_min", 1.0f); + shader_.set_uniform("u_height_max", height_max_); + shader_.set_uniform("u_height_shift", (std::abs(height_max_) + std::abs(height_min_)) / 4); + + glBindVertexArray(vao_); + glDrawElements(GL_TRIANGLES, GLsizei(indices.size()), GL_UNSIGNED_INT, nullptr); + } + + [[nodiscard]] + auto table() const { + return rectangle2torus; + } + +private: + float evaluate_height(Point point) { + auto x = point.x * height_map_width_ * 6 / plane.x; + x %= height_map_width_; + + auto y = point.y * height_map_height_ / plane.y; + y %= height_map_height_; + + auto idx = x * height_map_height_ + y; + + return float(height_map_[3 * idx]) / 255.0f; + } + + glm::vec3 evaluate_point(Point point) { + auto phi = float((M_PI * 2.0 * point.x) / (plane.x)); + auto psi = float(M_PI * (2.0 * point.y / (plane.y))); + float height = evaluate_height(point) * 0.2f; + + return glm::vec3( + (radius_major_ + (radius_minor_ + height) * cos(psi)) * cos(phi), + (radius_major_ + (radius_minor_ + height) * cos(psi)) * sin(phi), + (radius_minor_ + height) * sin(psi) + ); + } + + [[nodiscard]] + glm::vec3 evaluate_normal(Point point) const { + auto phi = float((M_PI * 2.0 * point.x) / (plane.x - 1)); + auto psi = float(M_PI * (2.0 * point.y / (plane.y - 1))); + + return glm::vec3( + cos(psi) * cos(phi), + cos(psi) * sin(phi), + sin(psi) + ); + } + + glm::vec3 evaluate_texture_coordinate(Point point) { + auto phi = float((M_PI * 2.0 * point.x) / (plane.x)); + auto psi = float(M_PI * (2.0 * point.y / (plane.y))); + float height = evaluate_height(point) * 0.2f; + + return glm::vec3( + (radius_major_ + (radius_minor_ + height) * cos(psi)) * cos(phi), + (radius_major_ + (radius_minor_ + height) * cos(psi)) * sin(phi), + (radius_minor_ + height) + ); + } + + static GLuint load_texture_from_file(const std::string &filename) { + GLuint textureID; + glGenTextures(1, &textureID); + + GLint width, height, nrComponents; + GLubyte *data = stbi_load(filename.c_str(), &width, &height, &nrComponents, 0); + if (data) { + GLenum format; + if (nrComponents == 1) format = GL_RED; + else if (nrComponents == 3) format = GL_RGB; + else if (nrComponents == 4) format = GL_RGBA; + + glBindTexture(GL_TEXTURE_2D, textureID); + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); + glGenerateMipmap(GL_TEXTURE_2D); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } else { + throw std::runtime_error("textures failed to load at path: " + filename); + } + + stbi_image_free(data); + + return textureID; + } + + static void load_textures() { + glActiveTexture(Settings::GL_TEXTURE_TORUS_1); + glBindTexture(GL_TEXTURE_2D, load_texture_from_file(Settings::LANDSCAPE_TEXTURE_LEVEL_1)); + + glActiveTexture(Settings::GL_TEXTURE_TORUS_2); + glBindTexture(GL_TEXTURE_2D, load_texture_from_file(Settings::LANDSCAPE_TEXTURE_LEVEL_2)); + + glActiveTexture(Settings::GL_TEXTURE_TORUS_3); + glBindTexture(GL_TEXTURE_2D, load_texture_from_file(Settings::LANDSCAPE_TEXTURE_LEVEL_3)); + + glActiveTexture(Settings::GL_TEXTURE_TORUS_4); + glBindTexture(GL_TEXTURE_2D, load_texture_from_file(Settings::LANDSCAPE_TEXTURE_LEVEL_4)); + + glActiveTexture(GL_TEXTURE0); + } + + void load_height_map() { + std::string filename = std::string(Settings::LANDSCAPE_TEXTURE_HEIGHT_MAP); + + height_map_ = stbi_load(filename.c_str(), + &height_map_width_, + &height_map_height_, + &height_map_components_, + 0); + if (!height_map_) { + throw std::runtime_error("Error load image"); + } + } + + void fill_buffers() { + // Generate torus arrays + for (int i = 0; i < plane.x; ++i) { + for (int j = 0; j < plane.y; ++j) { + auto torus_point = evaluate_point({i, j}); + auto torus_normal = evaluate_normal({i, j}); + + rectangle2torus.position[i][j] = torus_point; + rectangle2torus.normal[i][j] = torus_normal; + + vertices.push_back(torus_point); + vertices.push_back(torus_normal); + vertices.push_back(evaluate_texture_coordinate({i, j})); + + // Triangle 1 + int ip1 = (i + 1) % plane.x; + int jp1 = (j + 1) % plane.y; + + indices.push_back(i * plane.y + j); + indices.push_back((i * plane.y + jp1)); + indices.push_back((ip1 * plane.y + jp1)); + + // Triangle 2 + indices.push_back((ip1 * plane.y + jp1)); + indices.push_back((ip1 * plane.y + j)); + indices.push_back(i * plane.y + j); + } + } + } + + void bind_buffers() { + GLuint vbo, ebo; + + glGenVertexArrays(1, &vao_); + glBindVertexArray(vao_); + + glGenBuffers(1, &vbo); + glBindBuffer(GL_ARRAY_BUFFER, vbo); + glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(vertices[0]), vertices.data(), GL_STATIC_DRAW); + + glGenBuffers(1, &ebo); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(int), indices.data(), GL_STATIC_DRAW); + + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(float), nullptr); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(float), (void *) (3 * sizeof(float))); + glEnableVertexAttribArray(2); + glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 9 * sizeof(float), (void *) (6 * sizeof(float))); + + glBindVertexArray(0); + } +}; diff --git a/src/elements/window.h b/src/elements/window.h new file mode 100644 index 0000000..a2cb2b5 --- /dev/null +++ b/src/elements/window.h @@ -0,0 +1,176 @@ +#pragma once + +#include +#include + +#include +#include "../utils/shader.h" +#include "controller.h" +#include "cubemap.h" +#include "torus.h" +#include "object.h" +#include "light.h" + + +class Window { + Cubemap cubemap_; + Torus torus_; + Object object_; + Controller controller_; + Light light_; + + GLFWwindow *window_; + + glm::mat4 default_matrix = glm::identity(); + +public: + Window(const Cubemap &cubemap, Torus torus, Object object, Controller controller, Light light, GLFWwindow *window) + : cubemap_(cubemap), + torus_(std::move(torus)), + object_(std::move(object)), + controller_(std::move(controller)), + light_(std::move(light)) { + window_ = window; + } + + void render() { + while (!glfwWindowShouldClose(window_)) { + check_events(); + init_display(); + check_error(); + + // Init Model Matrix + Camera camera = controller_.camera(); + auto model = create_aircraft_model_matrix(camera); + + // Create and activate Depth texture + light_.render(model); + auto light_view = light_.view(); + auto light_projection = light_.projection(); + render_elements( + model, + light_view, light_projection, + default_matrix, default_matrix, + default_matrix, default_matrix + ); + light_.activate(); + + init_display(); + + // Create View, Projection Matrix + glm::mat4 view = camera.view(); + glm::mat4 projection = glm::perspective( + glm::radians(45.0f), + Settings::RATIO, + Settings::Z_NEAR, + Settings::Z_FAR + ); + + // Spotlight + glm::mat4 spotlight_view = glm::lookAt( + controller_.get_position(controller_.plane() - camera.direction()), + controller_.get_position(controller_.plane()), + glm::vec3(0.0, 1.0, 0.0) + ); + glm::mat4 spotlight_projection = glm::perspective( + glm::radians(45.0f), + Settings::RATIO, + Settings::Z_NEAR, + 2.0f + ); + + // Render elements + render_elements( + model, view, projection, + light_view, light_projection, + spotlight_view, spotlight_projection + ); + + // Swap + glfwSwapBuffers(window_); + } + } + +private: + void render_elements( + glm::mat4 &model, glm::mat4 &view, glm::mat4 &projection, + glm::mat4 &light_view, glm::mat4 &light_projection, + glm::mat4 &spotlight_view, glm::mat4 &spotlight_projection + ) { + torus_.render(view, projection, light_view, light_projection, spotlight_view, spotlight_projection); + cubemap_.render(view, projection); + object_.render(model, view, projection); + } + + glm::mat4 create_aircraft_model_matrix(Camera camera) { + auto model = glm::identity(); + model = glm::translate(model, controller_.get_position(controller_.plane())); + model *= glm::orientation( + glm::normalize(controller_.get_normal(controller_.plane())), + glm::normalize(controller_.get_normal({0, 0})) + ); + + auto current_point = controller_.plane(); + auto p1 = controller_.get_position(current_point); + auto p2 = controller_.get_position(current_point + glm::vec2(0.0f, 1.0f)); + auto p3 = controller_.get_position(current_point + glm::vec2(1.0f, 0.0f)); + + auto vec1 = p2 - p1; + auto vec2 = p3 - p1; + + auto current_normal = controller_.get_normal(controller_.plane()); + auto cross_vec1 = glm::normalize(glm::cross(vec1, current_normal)); + auto cross_vec2 = glm::normalize(glm::cross(vec2, current_normal)); + + auto zero_point = glm::vec2(0.0f, 0.0f); + auto p4 = controller_.get_position(zero_point); + auto p5 = controller_.get_position(zero_point + glm::vec2(0.0f, 1.0f)); + auto p6 = controller_.get_position(zero_point + glm::vec2(1.0f, 0.0f)); + + auto vec3 = p5 - p4; + auto vec4 = p6 - p4; + + auto zero_normal = controller_.get_normal(zero_point); + auto cross_vec3 = glm::normalize(glm::cross(vec3, zero_normal)); + auto cross_vec4 = glm::normalize(glm::cross(vec4, zero_normal)); + + auto rotation1 = glm::identity(); + rotation1 *= glm::orientation(zero_normal, cross_vec3); + + auto rotation2 = glm::identity(); + rotation2 *= glm::orientation(current_normal, cross_vec1); + + model *= glm::rotate( + glm::radians(camera.angle() - 90.0f), + controller_.get_normal({0, 0}) + ); + model *= glm::orientation(glm::vec3(1, 0, 0), glm::vec3(0, 1, 0)); + + auto scale = 0.05f; + model = glm::scale(model, glm::vec3(scale, scale, scale)); + + return model; + } + + // Check and call events + void check_events() { + glfwPollEvents(); + controller_.key_events(); + } + + // Error + static void check_error() { + GLenum err; + while ((err = glGetError()) != GL_NO_ERROR) { + std::cerr << gluErrorString(err) << std::endl; + } + } + + // Display + void init_display() { + int display_width, display_height; + glfwGetFramebufferSize(window_, &display_width, &display_height); + glViewport(0, 0, display_width, display_height); + glClear(GLuint(GL_COLOR_BUFFER_BIT) | GLuint(GL_DEPTH_BUFFER_BIT)); + } +}; diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..4f5bf9d --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,33 @@ +#include "utils/shader.h" +#include "utils/model.h" + +#include "elements/torus.h" +#include "elements/object.h" +#include "elements/controller.h" +#include "elements/window.h" +#include "utils/opengl.h" + + +int main() { + // Window + auto open_gl = OpenGl(Settings::WINDOW_NAME); + + // Shaders + auto torus_shader = Shader(Settings::TORUS_SHADER_VERTEX, Settings::TORUS_SHADER_FRAGMENT); + auto cubemap_shader = Shader(Settings::CUBEMAP_SHADER_VERTEX, Settings::CUBEMAP_SHADER_FRAGMENT); + auto object_shader = Shader(Settings::OBJECT_SHADER_VERTEX, Settings::OBJECT_SHADER_FRAGMENT); + auto light_shader = Shader(Settings::LIGHT_SHADER_VERTEX, Settings::LIGHT_SHADER_FRAGMENT); + + // Elements + auto cubemap = Cubemap(cubemap_shader, Settings::CUBEMAP_TEXTURES); + auto object = Object(object_shader, Settings::PATH_TO_OBJECT); + auto torus = Torus(torus_shader); + auto light = Light(light_shader); + auto controller = Controller(torus); + + // Render + auto window = Window(cubemap, torus, object, controller, light, open_gl.window()); + window.render(); + + return 0; +} diff --git a/src/settings.h b/src/settings.h new file mode 100644 index 0000000..5ee6a3b --- /dev/null +++ b/src/settings.h @@ -0,0 +1,68 @@ +#pragma once + +#include + + +struct Settings { + // Shaders + inline static const std::string TORUS_SHADER_VERTEX = "assets/shaders/torus_vertex.glsl"; + inline static const std::string TORUS_SHADER_FRAGMENT = "assets/shaders/torus_fragment.glsl"; + + inline static const std::string CUBEMAP_SHADER_VERTEX = "assets/shaders/cubemap_vertex.glsl"; + inline static const std::string CUBEMAP_SHADER_FRAGMENT = "assets/shaders/cubemap_fragment.glsl"; + + inline static const std::string OBJECT_SHADER_VERTEX = "assets/shaders/object_vertex.glsl"; + inline static const std::string OBJECT_SHADER_FRAGMENT = "assets/shaders/object_fragment.glsl"; + + inline static const std::string LIGHT_SHADER_VERTEX = "assets/shaders/light_vertex.glsl"; + inline static const std::string LIGHT_SHADER_FRAGMENT = "assets/shaders/light_fragment.glsl"; + + // Textures + static const unsigned GL_TEXTURE_CUBEMAP = GL_TEXTURE0; + + static const unsigned GL_TEXTURE_TORUS_1 = GL_TEXTURE1; + static const unsigned GL_TEXTURE_TORUS_2 = GL_TEXTURE2; + static const unsigned GL_TEXTURE_TORUS_3 = GL_TEXTURE3; + static const unsigned GL_TEXTURE_TORUS_4 = GL_TEXTURE4; + + static const unsigned GL_TEXTURE_DEPTH_MAP = GL_TEXTURE5; + + static const unsigned GL_TEXTURE_MODEL = GL_TEXTURE6; + + // Window + inline static const std::string WINDOW_NAME = "Task-3-3D-scene"; + + static const GLuint WINDOW_WIDTH = 800; + static const GLuint WINDOW_HEIGHT = 600; + + static constexpr GLfloat RATIO = GLfloat(WINDOW_WIDTH) / GLfloat(WINDOW_HEIGHT); + static constexpr GLfloat Z_NEAR = 0.1f; + static constexpr GLfloat Z_FAR = 100.0f; + + // Shadow + static const GLuint SHADOW_WIDTH = 4096; + static const GLuint SHADOW_HEIGHT = 4096; + + static constexpr GLfloat LIGHT_Z_NEAR = 8.0f; + static constexpr GLfloat LIGHT_Z_FAR = 19.0f; + static constexpr glm::vec3 LIGHT_POSITION = glm::vec3(8.0f, 8.0f, 8.0f); + + // Paths +// inline static const std::string PATH_TO_OBJECT = "assets/objects/aircraft/piper_pa18.obj"; + inline static const std::string PATH_TO_OBJECT = "assets/objects/ball/Soccer_Ball.obj"; + + inline static const std::string LANDSCAPE_TEXTURE_LEVEL_1 = "assets/objects/torus/textures/soil.jpg"; + inline static const std::string LANDSCAPE_TEXTURE_LEVEL_2 = "assets/objects/torus/textures/plaster_wall.jpg"; + inline static const std::string LANDSCAPE_TEXTURE_LEVEL_3 = "assets/objects/torus/textures/grass_stone.jpg"; + inline static const std::string LANDSCAPE_TEXTURE_LEVEL_4 = "assets/objects/torus/textures/grass_flower.jpg"; + inline static const std::string LANDSCAPE_TEXTURE_HEIGHT_MAP = "assets/objects/torus/textures/height_map.jpg"; + + inline static const std::vector CUBEMAP_TEXTURES = { + "assets/objects/skybox/textures/simple/right.jpg", + "assets/objects/skybox/textures/simple/left.jpg", + "assets/objects/skybox/textures/simple/top.jpg", + "assets/objects/skybox/textures/simple/bottom.jpg", + "assets/objects/skybox/textures/simple/front.jpg", + "assets/objects/skybox/textures/simple/back.jpg" + }; +}; diff --git a/src/utils/mesh.h b/src/utils/mesh.h new file mode 100644 index 0000000..40b879c --- /dev/null +++ b/src/utils/mesh.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +#include "shader.h" +#include "../settings.h" + + +struct Vertex { + glm::vec3 position; + glm::vec3 normal; + glm::vec2 texture_coords; + glm::vec3 tangent; + glm::vec3 bitangent; +}; + +struct Texture { + GLuint id; + std::string type; + std::string path; +}; + +class Mesh { +private: + GLuint vbo_{}, vao_{}, ebo_{}; + std::vector vertices_; + std::vector indices_; + std::vector textures_; + +public: + Mesh(std::vector vertices, std::vector indices, std::vector textures) + : vertices_(std::move(vertices)), + indices_(std::move(indices)), + textures_(std::move(textures)) { + setup_mesh(); + } + + void draw(Shader &shader) { + for (GLint i = 0; i < textures_.size(); ++i) { + int texture_slot = int(Settings::GL_TEXTURE_MODEL) - GL_TEXTURE0 + i; + glActiveTexture(Settings::GL_TEXTURE_MODEL + i); + glBindTexture(GL_TEXTURE_2D, textures_[i].id); + shader.set_uniform("u_texture_" + std::to_string(i + 1), texture_slot); + } + + glBindVertexArray(vao_); + glDrawElements(GL_TRIANGLES, indices_.size(), GL_UNSIGNED_INT, nullptr); + glBindVertexArray(0); + + glActiveTexture(GL_TEXTURE0); + } + +private: + void setup_mesh() { + glGenVertexArrays(1, &vao_); + glGenBuffers(1, &vbo_); + glGenBuffers(1, &ebo_); + + glBindVertexArray(vao_); + glBindBuffer(GL_ARRAY_BUFFER, vbo_); + glBufferData(GL_ARRAY_BUFFER, vertices_.size() * sizeof(Vertex), vertices_.data(), GL_STATIC_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo_); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices_.size() * sizeof(GLuint), indices_.data(), GL_STATIC_DRAW); + + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), static_cast(nullptr)); + + glEnableVertexAttribArray(2); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (void *) (sizeof(float) * 6)); + + glBindVertexArray(0); + } +}; diff --git a/src/utils/model.h b/src/utils/model.h new file mode 100644 index 0000000..b093644 --- /dev/null +++ b/src/utils/model.h @@ -0,0 +1,191 @@ +#pragma once +#define STB_IMAGE_IMPLEMENTATION + +#include +#include +#include +#include +#include +#include + +#include "mesh.h" +#include "shader.h" + +#include +#include +#include +#include +#include +#include + + +class Model { +private: + std::vector textures_loaded_; + std::vector meshes_; + std::string directory_; + +public: + explicit Model(const std::string &path) { + load_model(path); + } + + void draw(Shader &shader) { + for (auto &mesh : meshes_) { + mesh.draw(shader); + } + } + +private: + void load_model(std::string const &path) { + Assimp::Importer importer; + const aiScene *scene = importer.ReadFile(path, aiProcess_Triangulate | + aiProcess_GenSmoothNormals | + aiProcess_FlipUVs | + aiProcess_CalcTangentSpace); + + if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) { + std::cout << "ERROR::ASSIMP:: " << importer.GetErrorString() << std::endl; + return; + } + + directory_ = path.substr(0, path.find_last_of('/')); + + process_node(scene->mRootNode, scene); + } + + void process_node(aiNode *node, const aiScene *scene) { + for (GLuint i = 0; i < node->mNumMeshes; ++i) { + aiMesh *mesh = scene->mMeshes[node->mMeshes[i]]; + meshes_.push_back(process_mesh(mesh, scene)); + } + + for (GLuint i = 0; i < node->mNumChildren; i++) { + process_node(node->mChildren[i], scene); + } + } + + Mesh process_mesh(aiMesh *mesh, const aiScene *scene) { + std::vector vertices; + std::vector indices; + std::vector textures; + + for (GLuint i = 0; i < mesh->mNumVertices; ++i) { + Vertex vertex{}; + glm::vec3 vector = glm::vec3( + mesh->mVertices[i].x, + mesh->mVertices[i].y, + mesh->mVertices[i].z + ); + + // Positions + vertex.position = vector; + + // Normals + if (mesh->HasNormals()) { + vector.x = mesh->mNormals[i].x; + vector.y = mesh->mNormals[i].y; + vector.z = mesh->mNormals[i].z; + vertex.normal = vector; + } + + // textures coordinates + if (mesh->mTextureCoords[0]) { + glm::vec2 vec; + vec.x = mesh->mTextureCoords[0][i].x; + vec.y = mesh->mTextureCoords[0][i].y; + vertex.texture_coords = vec; + + vector.x = mesh->mTangents[i].x; + vector.y = mesh->mTangents[i].y; + vector.z = mesh->mTangents[i].z; + vertex.tangent = vector; + + vector.x = mesh->mBitangents[i].x; + vector.y = mesh->mBitangents[i].y; + vector.z = mesh->mBitangents[i].z; + vertex.bitangent = vector; + } else { + vertex.texture_coords = glm::vec2(0.0f, 0.0f); + } + + vertices.push_back(vertex); + } + + for (GLuint i = 0; i < mesh->mNumFaces; ++i) { + aiFace face = mesh->mFaces[i]; + for (GLuint j = 0; j < face.mNumIndices; ++j) { + indices.push_back(face.mIndices[j]); + } + } + + aiMaterial *material = scene->mMaterials[mesh->mMaterialIndex]; + + // 1. Diffuse maps + std::vector diffuse_maps = load_material_textures(material, + aiTextureType_DIFFUSE, + "texture_diffuse"); + textures.insert(textures.end(), diffuse_maps.begin(), diffuse_maps.end()); + + return Mesh(vertices, indices, textures); + } + + std::vector load_material_textures(aiMaterial *material, + aiTextureType texture_type, + const std::string &type_name) { + std::vector textures; + for (GLuint i = 0; i < material->GetTextureCount(texture_type); ++i) { + aiString path; + material->GetTexture(texture_type, i, &path); + + bool is_loaded = false; + for (auto &texture_path : textures_loaded_) { + if (std::strcmp(texture_path.path.data(), path.C_Str()) == 0) { + textures.push_back(texture_path); + is_loaded = true; + break; + } + } + + if (!is_loaded) { + Texture texture; + texture.id = load_texture_from_file(std::string(directory_ + "/" + path.C_Str())); + texture.type = type_name; + texture.path = path.C_Str(); + textures.push_back(texture); + textures_loaded_.push_back(texture); + } + } + + return textures; + } + + static GLuint load_texture_from_file(const std::string &filename) { + GLuint textureID; + glGenTextures(1, &textureID); + + GLint width, height, nrComponents; + GLubyte *data = stbi_load(filename.c_str(), &width, &height, &nrComponents, 0); + if (data) { + GLenum format; + if (nrComponents == 1) format = GL_RED; + else if (nrComponents == 3) format = GL_RGB; + else if (nrComponents == 4) format = GL_RGBA; + + glBindTexture(GL_TEXTURE_2D, textureID); + glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data); + glGenerateMipmap(GL_TEXTURE_2D); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } else { + std::cout << "textures failed to load at path: " << filename << std::endl; + } + + stbi_image_free(data); + + return textureID; + } +}; diff --git a/src/utils/opengl.h b/src/utils/opengl.h new file mode 100644 index 0000000..55b3074 --- /dev/null +++ b/src/utils/opengl.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "../elements/controller.h" + + +class OpenGl { + std::string window_name_; + GLFWwindow *window_; + +public: + explicit OpenGl(std::string window_name) : window_name_(std::move(window_name)) { + window_ = init_opengl(); + } + + ~OpenGl() { + glfwDestroyWindow(window_); + glfwTerminate(); + } + + GLFWwindow *window() { + return window_; + } + +private: + GLFWwindow *init_opengl() { + glfwInit(); + + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); + glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); + glfwWindowHint(GLFW_SAMPLES, 4); + + GLFWwindow *window = glfwCreateWindow( + Settings::WINDOW_WIDTH, + Settings::WINDOW_HEIGHT, + window_name_.c_str(), + nullptr, + nullptr + ); + + if (window == nullptr) { + glfwTerminate(); + throw std::runtime_error("Failed to create GLFW window"); + } + + glfwMakeContextCurrent(window); + +// glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + glfwSetKeyCallback(window, key_callback); + + glewExperimental = GL_TRUE; + if (glewInit() != GLEW_OK) { + throw std::runtime_error("Failed to initialize GLEW"); + } + + int frame_width, frame_height; + glfwGetFramebufferSize(window, &frame_width, &frame_height); + glViewport(0, 0, frame_width, frame_height); + glEnable(GL_DEPTH_TEST); + + return window; + } +}; diff --git a/src/utils/shader.cpp b/src/utils/shader.cpp new file mode 100644 index 0000000..239dde8 --- /dev/null +++ b/src/utils/shader.cpp @@ -0,0 +1,125 @@ +#include "shader.h" + +#include +#include +#include + +namespace { + std::string read_shader_code(const std::string &filename) { + std::stringstream file_stream; + try { + std::ifstream file(filename.c_str()); + file_stream << file.rdbuf(); + file.close(); + } + catch (std::exception const &e) { + std::cerr << "Error reading shader file: " << e.what() << std::endl; + } + return file_stream.str(); + } +} + +Shader::Shader(const std::string &vertex_filepath, const std::string &fragment_filepath) { + const auto vertex_code = read_shader_code(vertex_filepath); + const auto fragment_code = read_shader_code(fragment_filepath); + compile(vertex_code, fragment_code); + link(); +} + +Shader::~Shader() = default; + +void Shader::compile(const std::string &vertex_code, const std::string &fragment_code) { + const char *vcode = vertex_code.c_str(); + vertex_id_ = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vertex_id_, 1, &vcode, nullptr); + glCompileShader(vertex_id_); + + const char *fcode = fragment_code.c_str(); + fragment_id_ = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fragment_id_, 1, &fcode, nullptr); + glCompileShader(fragment_id_); + check_compile_error(); +} + +void Shader::link() { + program_id_ = glCreateProgram(); + glAttachShader(program_id_, vertex_id_); + glAttachShader(program_id_, fragment_id_); + glLinkProgram(program_id_); + check_linking_error(); + glDeleteShader(vertex_id_); + glDeleteShader(fragment_id_); +} + +void Shader::use() const { + glUseProgram(program_id_); +} + +template<> +void Shader::set_uniform(const std::string &name, int val) { + glUniform1i(glGetUniformLocation(program_id_, name.c_str()), val); +} + +template<> +void Shader::set_uniform(const std::string &name, bool val) { + glUniform1i(glGetUniformLocation(program_id_, name.c_str()), val); +} + +template<> +void Shader::set_uniform(const std::string &name, float val) { + glUniform1f(glGetUniformLocation(program_id_, name.c_str()), val); +} + +template<> +void Shader::set_uniform(const std::string &name, float val1, float val2) { + glUniform2f(glGetUniformLocation(program_id_, name.c_str()), val1, val2); +} + +template<> +void Shader::set_uniform(const std::string &name, float val1, float val2, float val3) { + glUniform3f(glGetUniformLocation(program_id_, name.c_str()), val1, val2, val3); +} + +template<> +void Shader::set_uniform(const std::string &name, float *val) { + glUniformMatrix4fv(glGetUniformLocation(program_id_, name.c_str()), 1, GL_FALSE, val); +} + +template<> +void Shader::set_uniform(const std::string &name, glm::vec3 vec) { + set_uniform(name, vec.x, vec.y, vec.z); +} + +void Shader::check_compile_error() const { + int success; + char infoLog[1024]; + glGetShaderiv(vertex_id_, GL_COMPILE_STATUS, &success); + if (!success) { + glGetShaderInfoLog(vertex_id_, 1024, nullptr, infoLog); + std::cerr << "Error compiling Vertex Shader:\n" << infoLog << std::endl; + } + glGetShaderiv(fragment_id_, GL_COMPILE_STATUS, &success); + if (!success) { + glGetShaderInfoLog(fragment_id_, 1024, nullptr, infoLog); + std::cerr << "Error compiling Fragment Shader:\n" << infoLog << std::endl; + } +} + +void Shader::check_linking_error() const { + int success; + char infoLog[1024]; + glGetProgramiv(program_id_, GL_LINK_STATUS, &success); + if (!success) { + glGetProgramInfoLog(program_id_, 1024, nullptr, infoLog); + std::cerr << "Error Linking Shader Program:\n" << infoLog << std::endl; + } +} + +template +void Shader::set_uniform(const std::string &name, T val) {} + +template +void Shader::set_uniform(const std::string &name, T val1, T val2) {} + +template +void Shader::set_uniform(const std::string &name, T val1, T val2, T val3) {} diff --git a/src/utils/shader.h b/src/utils/shader.h new file mode 100644 index 0000000..e479c59 --- /dev/null +++ b/src/utils/shader.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +#include +#include + +class Shader { +public: + Shader(const std::string &vertex_filepath, const std::string &fragment_filepath); + ~Shader(); + + void use() const; + + template + void set_uniform(const std::string &name, T val); + + template + void set_uniform(const std::string &name, T val1, T val2); + + template + void set_uniform(const std::string &name, T val1, T val2, T val3); + +private: + void check_compile_error() const; + void check_linking_error() const; + void compile(const std::string &vertex_code, const std::string &fragment_code); + void link(); + +private: + GLuint vertex_id_{}; + GLuint fragment_id_{}; + GLuint program_id_{}; +};