diff --git a/.gitignore b/.gitignore index 5c1693c..ff07a40 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ [Bb]uild/ +.vs/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 3abff83..555e232 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,7 @@ project(OpenGLPrj) option(GLFW_BUILD_DOCS OFF) option(GLFW_BUILD_EXAMPLES OFF) option(GLFW_BUILD_TESTS OFF) + add_subdirectory(vendor/glfw) if(MSVC) @@ -15,6 +16,9 @@ else() endif() endif() +set(SHADERS_RELATIVE_SRC_PATH "res/shaders") +set(TEXTURES_RELATIVE_SRC_PATH "res/textures") + include_directories(include/ vendor/glad/include/ vendor/glfw/include/ @@ -24,13 +28,19 @@ include_directories(include/ file(GLOB VENDORS_SOURCES vendor/glad/src/glad.c) file(GLOB PROJECT_HEADERS include/*.hpp) file(GLOB PROJECT_SOURCES src/*.cpp) -file(GLOB PROJECT_SHADERS shaders/*.comp - shaders/*.frag - shaders/*.geom - shaders/*.vert - shaders/*.vs - shaders/*.fs +file(GLOB PROJECT_SHADERS ${SHADERS_RELATIVE_SRC_PATH}/*.comp + ${SHADERS_RELATIVE_SRC_PATH}/*.frag + ${SHADERS_RELATIVE_SRC_PATH}/*.geom + ${SHADERS_RELATIVE_SRC_PATH}/*.vert + ${SHADERS_RELATIVE_SRC_PATH}/*.vs + ${SHADERS_RELATIVE_SRC_PATH}/*.fs + ${SHADERS_RELATIVE_SRC_PATH}/*.glsl + ) + +file(GLOB PROJECT_TEXTURES ${TEXTURES_RELATIVE_SRC_PATH}/*.png + ${TEXTURES_RELATIVE_SRC_PATH}/*.jpg ) + file(GLOB PROJECT_CONFIGS CMakeLists.txt Readme.md .gitattributes @@ -39,17 +49,34 @@ file(GLOB PROJECT_CONFIGS CMakeLists.txt source_group("include" FILES ${PROJECT_HEADERS}) source_group("shaders" FILES ${PROJECT_SHADERS}) +source_group("textures" FILES ${PROJECT_TEXTURES}) source_group("src" FILES ${PROJECT_SOURCES}) source_group("vendors" FILES ${VENDORS_SOURCES}) add_definitions(-DGLFW_INCLUDE_NONE -DPROJECT_SOURCE_DIR=\"${PROJECT_SOURCE_DIR}\") + add_executable(${PROJECT_NAME} ${PROJECT_SOURCES} ${PROJECT_HEADERS} - ${PROJECT_SHADERS} ${PROJECT_CONFIGS} - ${VENDORS_SOURCES}) + ${PROJECT_SHADERS} ${PROJECT_TEXTURES} ${PROJECT_CONFIGS} + ${VENDORS_SOURCES} "src/Mesh.cpp" "src/Skybox.cpp") target_link_libraries(${PROJECT_NAME} glfw ${GLFW_LIBRARIES} ${GLAD_LIBRARIES} ) -set_target_properties(${PROJECT_NAME} PROPERTIES - RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/${PROJECT_NAME}) + + +#set (source "${CMAKE_SOURCE_DIR}/res") +#set (destination "${CMAKE_CURRENT_BINARY_DIR}/res") + +add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_SOURCE_DIR}/res + $/../res) + + +set_target_properties(${PROJECT_NAME} + PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/lib" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/lib" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${PROJECT_NAME}/bin" +) diff --git a/include/Camera.hpp b/include/Camera.hpp new file mode 100644 index 0000000..06b1bc1 --- /dev/null +++ b/include/Camera.hpp @@ -0,0 +1,68 @@ +#ifndef CAMERA_H +#define CAMERA_H + +#include +#include +#include + +#include + +// Defines several possible options for camera movement. Used as abstraction to +// stay away from window-system specific input methods +enum Camera_Movement { FORWARD, BACKWARD, LEFT, RIGHT }; + +// Default camera values +const float YAW = -90.0f; +const float PITCH = 0.0f; +const float SPEED = 2.5f; +const float SENSITIVITY = 0.1f; +const float ZOOM = 45.0f; + +// An abstract camera class that processes input and calculates the +// corresponding Euler Angles, Vectors and Matrices for use in OpenGL +class Camera { +public: + // Camera Attributes + glm::vec3 Position; + glm::vec3 Front; + glm::vec3 Up; + glm::vec3 Right; + glm::vec3 WorldUp; + // Euler Angles + float Yaw; + float Pitch; + // Camera options + float MovementSpeed; + float MouseSensitivity; + float Zoom; + + // Constructor with vectors + Camera(glm::vec3 position = glm::vec3(0.0f, 0.0f, 0.0f), + glm::vec3 up = glm::vec3(0.0f, 1.0f, 0.0f), float yaw = YAW, + float pitch = PITCH); + // Constructor with scalar values + Camera(float posX, float posY, float posZ, float upX, float upY, float upZ, + float yaw, float pitch); + + // Returns the view matrix calculated using Euler Angles and the LookAt Matrix + glm::mat4 GetViewMatrix(); + + // Processes input received from any keyboard-like input system. Accepts input + // parameter in the form of camera defined ENUM (to abstract it from windowing + // systems) + void ProcessKeyboard(Camera_Movement direction, float deltaTime); + + // Processes input received from a mouse input system. Expects the offset + // value in both the x and y direction. + void ProcessMouseMovement(float xoffset, float yoffset, + GLboolean constrainPitch = true); + + // Processes input received from a mouse scroll-wheel event. Only requires + // input on the vertical wheel-axis + void ProcessMouseScroll(float yoffset); + +private: + // Calculates the front vector from the Camera's (updated) Euler Angles + void updateCameraVectors(); +}; +#endif diff --git a/include/Mesh.hpp b/include/Mesh.hpp new file mode 100644 index 0000000..9bd1bbd --- /dev/null +++ b/include/Mesh.hpp @@ -0,0 +1,18 @@ +#ifndef mMesh +#define mMesh +#pragma once + +#include +#include + +class Mesh { +public: + std::vector vertices; // interleaved: pos(x,y,z), normal(x,y,z), uv(u,v) + std::vector indices; + int vertexCount; + + static Mesh generateGrid(float width, float depth, int m, int n, int erosionIterations, float hydraulicFactor, float talusAngle); + static Mesh generateWaterPlane(float width, float depth, unsigned int divisions); +}; + +#endif \ No newline at end of file diff --git a/include/OpenGLPrj.hpp b/include/OpenGLPrj.hpp index 5357255..b2c7d5f 100644 --- a/include/OpenGLPrj.hpp +++ b/include/OpenGLPrj.hpp @@ -10,11 +10,5 @@ // Reference: https://github.com/nothings/stb/blob/master/stb_image.h#L4 // To use stb_image, add this in *one* C++ source file. -// #define STB_IMAGE_IMPLEMENTATION -#include -// Define Some Constants -const int mWidth = 1280; -const int mHeight = 800; - -#endif //~ Glitter Header +#endif //~ OpenGLPrj Header diff --git a/include/Shader.hpp b/include/Shader.hpp new file mode 100644 index 0000000..4d818f9 --- /dev/null +++ b/include/Shader.hpp @@ -0,0 +1,77 @@ +#ifndef SHADER_HPP +#define SHADER_HPP + +#include // include glad to get all the required OpenGL headers + +#include +#include +#include +#include +#include +#include +#include + +class Shader { +public: + enum SHADER_TYPE { VERTEX, FRAGMENT, GEOMETRY }; + + unsigned int ID; + // constructor generates the shader on the fly + // ------------------------------------------------------------------------ + Shader(const char *vertexPath, const char *fragmentPath); + + // constructor using std::string + // ------------------------------------------------------------------------ + Shader(const std::string vertexPath, const std::string fragmentPath); + + // activate the shader + // ------------------------------------------------------------------------ + void use(); + + // utility uniform functions + // ------------------------------------------------------------------------ + void setBool(const std::string &name, bool value) const; + + // ------------------------------------------------------------------------ + void setInt(const std::string &name, int value) const; + + // ------------------------------------------------------------------------ + void setFloat(const std::string &name, float value) const; + + // ------------------------------------------------------------------------ + void setVec2(const std::string &name, const glm::vec2 &value) const; + void setVec2(const std::string &name, float x, float y) const; + + // ------------------------------------------------------------------------ + void setVec3(const std::string &name, const glm::vec3 &value) const; + + void setVec3(const std::string &name, float x, float y, float z) const; + // ------------------------------------------------------------------------ + void setVec4(const std::string &name, const glm::vec4 &value) const; + + void setVec4(const std::string &name, float x, float y, float z, + float w) const; + + // ------------------------------------------------------------------------ + void setMat2(const std::string &name, const glm::mat2 &mat) const; + + // ------------------------------------------------------------------------ + void setMat3(const std::string &name, const glm::mat3 &mat) const; + + // ------------------------------------------------------------------------ + void setMat4(const std::string &name, const glm::mat4 &mat) const; + +private: + // utility function for checking shader compilation/linking errors. + // ------------------------------------------------------------------------ + void checkCompileErrors(unsigned int shader, std::string type); + + void readShader(char const *const, Shader::SHADER_TYPE); + + void compileShader(); + + std::string vertexShader; + std::string fragmentShader; +}; + +#endif // SHADER_HPP diff --git a/include/Skybox.hpp b/include/Skybox.hpp new file mode 100644 index 0000000..3c47fc7 --- /dev/null +++ b/include/Skybox.hpp @@ -0,0 +1,20 @@ +#pragma once +#include +#include +#include // or GLEW + +class SkyBox { + int width, height, channels; + std::string imagePath; + unsigned int textureID; // OpenGL texture handle + +public: + + // Constructor: takes path to the cross image + SkyBox(const std::string& path); + + unsigned char* extractFace(unsigned char* src, int faceSize, int xOffset, int yOffset, int width, int channels); + unsigned int loadCubemapFromCross(const std::string& path); + + unsigned int getTextureID() const { return textureID; } +}; diff --git a/include/main.hpp b/include/main.hpp new file mode 100644 index 0000000..7997097 --- /dev/null +++ b/include/main.hpp @@ -0,0 +1,87 @@ +#ifndef mMain +#define mMain +#pragma once + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +const unsigned int SCR_WIDTH = 1920; +const unsigned int SCR_HEIGHT = 1080; + +Camera camera(glm::vec3(0.0f, 5.0f, 10.0f)); + +float lastX = SCR_WIDTH / 2.0f; +float lastY = SCR_HEIGHT / 2.0f; +bool firstMouse = true; + +float deltaTime = 0.0f; +float lastFrame = 0.0f; + +unsigned int quadVAO = 0; +unsigned int quadVBO; + +const float DAY_LENGTH = 300.0f; // seconds per full day (adjust) + +std::unordered_map shaders; + +struct BloomBuffers { + unsigned int hdrFBO; + unsigned int colorBuffers[2]; + unsigned int pingpongFBO[2]; + unsigned int pingpongColorbuffers[2]; +}; + +struct Vertex { + glm::vec3 Position; + glm::vec3 Normal; + glm::vec2 TexCoords; + glm::vec3 Tangent; + glm::vec3 Bitangent; +}; + + +// Function declarations +// Main +int main(); + +// GLFW / OpenGL init +GLFWwindow* initGLFW(); +void framebuffer_size_callback(GLFWwindow* window, int width, int height); +void mouse_callback(GLFWwindow* window, double xpos, double ypos); +void scroll_callback(GLFWwindow* window, double xoffset, double yoffset); + +// Mesh & Shadow setup +void setupMesh(const Mesh& terrain, unsigned int& VAO, unsigned int& VBO, unsigned int& EBO); +void setupShadowMap(unsigned int& depthMapFBO, unsigned int& depthMap, const unsigned int SHADOW_WIDTH, const unsigned int SHADOW_HEIGHT); +unsigned int setupSun(); + +// Render helpers +void setupBloomBuffers(BloomBuffers& bloom, unsigned int width, unsigned int height); +void renderSun(Shader* sunShader, unsigned int sunVAO, glm::vec3 lightDir, glm::mat4 projection, glm::mat4 view); + +// Render loop +void renderLoop(GLFWwindow* window, + std::unordered_map>& shaders, + const Mesh& terrain, unsigned int terrainVAO, + const Mesh& waterMesh, unsigned int waterVAO, + unsigned int depthMapFBO, unsigned int depthMap); + +void renderQuad(); + +unsigned int loadWaterNormalMap(const char* path); +unsigned int createWaterPlaneVAO(std::vector& outVertices, std::vector& outIndices, unsigned int& VAO, unsigned int& VBO, unsigned int& EBO); +// Input +void processInput(GLFWwindow* window); +#endif \ No newline at end of file diff --git a/res/shaders/blur.frag b/res/shaders/blur.frag new file mode 100644 index 0000000..b141759 --- /dev/null +++ b/res/shaders/blur.frag @@ -0,0 +1,36 @@ +#version 330 core +out vec4 FragColor; + +in vec2 TexCoords; + +uniform sampler2D image; +uniform bool horizontal; + +const float weights[5] = float[](0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216); + +void main() +{ + vec2 tex_offset = 1.0 / textureSize(image, 0); // size of 1 texel + vec3 result = texture(image, TexCoords).rgb * weights[0]; + + if(horizontal) + { + for(int i = 1; i < 5; ++i) + { + result += texture(image, TexCoords + vec2(tex_offset.x * i, 0.0)).rgb * weights[i]; + result += texture(image, TexCoords - vec2(tex_offset.x * i, 0.0)).rgb * weights[i]; + } + } + else + { + for(int i = 1; i < 5; ++i) + { + result += texture(image, TexCoords + vec2(0.0, tex_offset.y * i)).rgb * weights[i]; + result += texture(image, TexCoords - vec2(0.0, tex_offset.y * i)).rgb * weights[i]; + } + } + + FragColor = vec4(result, 1.0); + + +} diff --git a/res/shaders/blur.vert b/res/shaders/blur.vert new file mode 100644 index 0000000..1310ff8 --- /dev/null +++ b/res/shaders/blur.vert @@ -0,0 +1,11 @@ +#version 330 core +layout (location = 0) in vec2 aPos; +layout (location = 1) in vec2 aTexCoords; + +out vec2 TexCoords; + +void main() +{ + TexCoords = aTexCoords; + gl_Position = vec4(aPos.xy, 0.0, 1.0); +} diff --git a/res/shaders/bright_pass.frag b/res/shaders/bright_pass.frag new file mode 100644 index 0000000..49bf97a --- /dev/null +++ b/res/shaders/bright_pass.frag @@ -0,0 +1,17 @@ +#version 330 core +out vec4 FragColor; + +in vec2 TexCoords; + +uniform sampler2D scene; +uniform float threshold; // brightness cutoff + +void main() +{ + vec3 color = texture(scene, TexCoords).rgb; + float brightness = dot(color, vec3(0.2126, 0.7152, 0.0722)); + if (brightness > threshold) + FragColor = vec4(color, 1.0); + else + FragColor = vec4(0.0); +} diff --git a/res/shaders/bright_pass.vert b/res/shaders/bright_pass.vert new file mode 100644 index 0000000..1310ff8 --- /dev/null +++ b/res/shaders/bright_pass.vert @@ -0,0 +1,11 @@ +#version 330 core +layout (location = 0) in vec2 aPos; +layout (location = 1) in vec2 aTexCoords; + +out vec2 TexCoords; + +void main() +{ + TexCoords = aTexCoords; + gl_Position = vec4(aPos.xy, 0.0, 1.0); +} diff --git a/res/shaders/depth_shader.frag b/res/shaders/depth_shader.frag new file mode 100644 index 0000000..3858769 --- /dev/null +++ b/res/shaders/depth_shader.frag @@ -0,0 +1,6 @@ +#version 330 core +void main() +{ + // Fragment shader is empty because we only need depth + // The depth is automatically written to the depth buffer +} diff --git a/res/shaders/depth_shader.vert b/res/shaders/depth_shader.vert new file mode 100644 index 0000000..0d3ac83 --- /dev/null +++ b/res/shaders/depth_shader.vert @@ -0,0 +1,8 @@ +#version 330 core +layout(location=0) in vec3 aPos; +uniform mat4 model; +uniform mat4 lightSpaceMatrix; + +void main(){ + gl_Position = lightSpaceMatrix * model * vec4(aPos,1.0); +} diff --git a/res/shaders/final.frag b/res/shaders/final.frag new file mode 100644 index 0000000..7ffda45 --- /dev/null +++ b/res/shaders/final.frag @@ -0,0 +1,22 @@ +#version 330 core +out vec4 FragColor; +in vec2 TexCoords; + +uniform sampler2D scene; +uniform sampler2D bloomBlur; +uniform float exposure; + +void main() +{ + vec3 hdrColor = texture(scene, TexCoords).rgb; + vec3 bloomColor = texture(bloomBlur, TexCoords).rgb; + hdrColor += bloomColor; // add bloom + + // Reinhard tone mapping — keeps bright stuff visible + vec3 mapped = vec3(1.0) - exp(-hdrColor * exposure); + + // Apply gamma correction (AFTER tone mapping!) + mapped = pow(mapped, vec3(1.0 / 2.2)); + + FragColor = vec4(mapped, 1.0); +} diff --git a/res/shaders/final.vert b/res/shaders/final.vert new file mode 100644 index 0000000..1310ff8 --- /dev/null +++ b/res/shaders/final.vert @@ -0,0 +1,11 @@ +#version 330 core +layout (location = 0) in vec2 aPos; +layout (location = 1) in vec2 aTexCoords; + +out vec2 TexCoords; + +void main() +{ + TexCoords = aTexCoords; + gl_Position = vec4(aPos.xy, 0.0, 1.0); +} diff --git a/res/shaders/shader.frag b/res/shaders/shader.frag new file mode 100644 index 0000000..61e3c05 --- /dev/null +++ b/res/shaders/shader.frag @@ -0,0 +1,88 @@ +#version 330 core + +in vec3 FragPos; +in vec3 Normal; +in float clipDist; // <-- comes from vertex shader + +out vec4 FragColor; + +uniform vec3 viewPos; +uniform vec3 lightDir; +uniform sampler2D shadowMap; +uniform mat4 lightSpaceMatrix; + +uniform int clipAbove; + +uniform float seaLevel; + +// Shadow calculation unchanged +float ShadowCalculation(vec4 fragPosLightSpace, vec3 norm) +{ + vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; + projCoords = projCoords * 0.5 + 0.5; + + if (projCoords.z > 1.0) + return 0.0; + + float shadow = 0.0; + float bias = max(0.002 * (1.0 - dot(norm, -lightDir)), 0.01); + float texelSize = 1.0 / textureSize(shadowMap, 0).x; + + for (int x = -1; x <= 1; ++x) + for (int y = -1; y <= 1; ++y) + { + float pcfDepth = texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r; + shadow += projCoords.z - bias > pcfDepth ? 1.0 : 0.0; + } + + return shadow / 9.0; +} + +void main() +{ + // ---------- CLIPPING ---------- + if (clipAbove != -1 && clipDist < 0.0) + discard; + + + // ---------- HEIGHT FADE ---------- + float fadeStart = seaLevel + 0.01; + float fadeEnd = seaLevel + 0.05; + + if (FragPos.y <= fadeStart) + discard; + + float alpha = smoothstep(fadeStart, fadeEnd, FragPos.y); + + // ---------- TERRAIN COLOR ---------- + vec3 colorYellow = vec3(0.875, 0.859, 0.710); + vec3 colorBlue = vec3(0.0, 0.35, 0.5); + vec3 colorGreen = vec3(0.263, 0.706, 0.424); + vec3 colorGray = vec3(0.447, 0.329, 0.157); + vec3 colorWhite = vec3(0.898, 0.851, 0.761); + + float h = FragPos.y; + vec3 baseColor = mix(colorBlue, colorYellow, smoothstep(0.0, 0.05, h)); + baseColor = mix(baseColor, colorGreen, smoothstep(0.05, 0.1, h)); + baseColor = mix(baseColor, colorGray, smoothstep(0.2, 0.7, h)); + + // ---------- LIGHTING ---------- + vec3 norm = normalize(Normal); + vec3 lightDirection = normalize(-lightDir); + + vec3 ambient = 0.1 * baseColor; + float diff = max(dot(norm, lightDirection), 0.0); + vec3 diffuse = diff * baseColor; + + vec3 viewDir = normalize(viewPos - FragPos); + vec3 reflectDir = reflect(-lightDirection, norm); + float spec = pow(max(dot(viewDir, reflectDir), 0.0), 16.0); + vec3 specular = 0.05 * spec * vec3(1.0); + + vec4 fragPosLightSpace = lightSpaceMatrix * vec4(FragPos, 1.0); + float shadow = ShadowCalculation(fragPosLightSpace, norm); + + vec3 result = ambient + (1.0 - shadow) * (diffuse + specular); + + FragColor = vec4(result, alpha); +} diff --git a/res/shaders/shader.vert b/res/shaders/shader.vert new file mode 100644 index 0000000..96f93a0 --- /dev/null +++ b/res/shaders/shader.vert @@ -0,0 +1,27 @@ +#version 330 core +layout(location=0) in vec3 aPos; +layout(location=1) in vec3 aNormal; +layout(location=2) in vec2 aTexCoords; + +out vec3 FragPos; +out vec3 Normal; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; + +uniform float clipHeight; +uniform int clipAbove; +out float clipDist; + +void main() +{ + vec4 worldPos = model * vec4(aPos, 1.0); + Normal = mat3(transpose(inverse(model))) * aNormal; + gl_Position = projection * view * worldPos; + FragPos = vec3(model * vec4(aPos,1.0)); + + clipDist = clipAbove == 1 + ? worldPos.y - clipHeight + : clipHeight - worldPos.y; +} diff --git a/res/shaders/skybox.frag b/res/shaders/skybox.frag new file mode 100644 index 0000000..8fd6b09 --- /dev/null +++ b/res/shaders/skybox.frag @@ -0,0 +1,14 @@ +#version 330 core +out vec4 FragColor; + +in vec3 TexCoords; +uniform samplerCube skybox; +uniform float exposure = 0.5; // tweak for brightness + +void main() +{ + vec3 color = texture(skybox, TexCoords).rgb; + color = pow(color, vec3(2.2)); // gamma correction + color *= exposure; // scale brightness + FragColor = vec4(color, 1.0); +} diff --git a/res/shaders/skybox.vert b/res/shaders/skybox.vert new file mode 100644 index 0000000..4c86409 --- /dev/null +++ b/res/shaders/skybox.vert @@ -0,0 +1,14 @@ +#version 330 core +layout(location = 0) in vec3 aPos; + +out vec3 TexCoords; + +uniform mat4 view; +uniform mat4 projection; + +void main() +{ + TexCoords = aPos; + vec4 pos = projection * view * vec4(aPos, 1.0); + gl_Position = pos.xyww; // w = depth, keep cube at infinity +} diff --git a/res/shaders/sun_shader.frag b/res/shaders/sun_shader.frag new file mode 100644 index 0000000..1d31eda --- /dev/null +++ b/res/shaders/sun_shader.frag @@ -0,0 +1,10 @@ +#version 330 core +out vec4 FragColor; + +uniform vec3 color; // bright sun color + +void main() +{ + // Make it very bright so HDR + bloom picks it up + FragColor = vec4(color, 1.0); +} diff --git a/res/shaders/sun_shader.vert b/res/shaders/sun_shader.vert new file mode 100644 index 0000000..048f11b --- /dev/null +++ b/res/shaders/sun_shader.vert @@ -0,0 +1,11 @@ +#version 330 core +layout(location = 0) in vec3 aPos; // quad vertices + +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; + +void main() +{ + gl_Position = projection * view * model * vec4(aPos, 1.0); +} diff --git a/res/shaders/water.frag b/res/shaders/water.frag new file mode 100644 index 0000000..058e6e6 --- /dev/null +++ b/res/shaders/water.frag @@ -0,0 +1,120 @@ +#version 330 core +out vec4 FragColor; + +in vec3 FragPos; +in vec2 TexCoords; +in vec4 ReflectedClipPos; +in mat3 TBN; + +uniform vec3 viewPos; +uniform vec3 islandPos; +uniform float time; + +uniform sampler2D reflectionTex; +uniform sampler2D shadowMap; +uniform samplerCube skyCubemap; +uniform sampler2D normalMap; + +uniform mat4 lightSpaceMatrix; +uniform float normalStrength; + + + +float ShadowCalculation(vec4 fragPosLightSpace) +{ + vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; + projCoords = projCoords * 0.5 + 0.5; + + if (projCoords.x < 0.0 || projCoords.x > 1.0 || + projCoords.y < 0.0 || projCoords.y > 1.0) + return 0.0; + + float bias = 0.005; + float shadow = 0.0; + vec2 texelSize = 1.0 / textureSize(shadowMap, 0); + + for (int x = -1; x <= 1; ++x) + for (int y = -1; y <= 1; ++y) + shadow += projCoords.z - bias > + texture(shadowMap, projCoords.xy + vec2(x, y) * texelSize).r + ? 1.0 : 0.0; + + return shadow / 9.0; +} + +void main() +{ + vec3 viewDir = normalize(viewPos - FragPos); + + + // ===== ISLAND INFLUENCE (LIMITED) ===== + float dist = length(FragPos.xz - islandPos.xz); + float islandInfluence = 1.0 - smoothstep(0.0, 30.0, dist); + + // ===== ORIGINAL NORMAL ANIMATION ===== + vec2 uv = FragPos.xz * 0.05; // world-space tiling (infinite-safe) + + vec2 distortion = + vec2(sin(time + dist), cos(time + dist)) * 0.01 * islandInfluence; + + vec3 normalTex = + texture(normalMap, uv + distortion).xyz * 2.0 - 1.0; + + normalTex.xy *= normalStrength; + + vec3 waterNormal = normalize(TBN * normalTex); + + // ===== FRESNEL ===== + float ndotv = max(dot(waterNormal, viewDir), 0.0); + + // Fresnel with base reflectivity (F0) + float F0 = 0.02; // water ≈ 2% + float fresnel = F0 + (1.0 - F0) * pow(1.0 - ndotv, 3.0); + + // artistic dampening + fresnel *= 0.65; + + + // ===== REFLECTION ===== + float distToCam = distance(FragPos, viewPos); + + // planar reflections only near camera + float planarFade = clamp(1.0 - distToCam / 150.0, 0.0, 1.0); + + vec2 ndc = ReflectedClipPos.xy / ReflectedClipPos.w; + vec2 reflUV = ndc * 0.5 + 0.5 + waterNormal.xz * 0.05; + + reflUV = clamp(reflUV, 0.001, 0.999); + + vec3 planarReflection = texture(reflectionTex, reflUV).rgb; + vec3 skyReflection = texture( + skyCubemap, + reflect(-viewDir, waterNormal) + ).rgb; + + + + // fade planar → sky + vec3 reflection = mix(skyReflection, planarReflection, planarFade); + + + // ===== SHADOW ===== + float shadow = + ShadowCalculation(lightSpaceMatrix * vec4(FragPos, 1.0)); + + shadow *= islandInfluence; // you already computed this + + + // ===== FINAL COLOR ===== + vec3 waterColor = vec3(0.0, 0.3, 0.5); + vec3 color = mix(waterColor, reflection, fresnel); + color *= 1.0 - shadow * 0.5; + + // Tune these numbers based on your far plane + float horizonFade = smoothstep(1000.0, 5500.0, distToCam); + + // Blend water into sky reflection near horizon + color = mix(color, skyReflection, horizonFade); + + FragColor = vec4(color, 0.7); +} diff --git a/res/shaders/water.vert b/res/shaders/water.vert new file mode 100644 index 0000000..35fac5d --- /dev/null +++ b/res/shaders/water.vert @@ -0,0 +1,55 @@ +#version 330 core +layout(location = 0) in vec3 aPos; +layout(location = 1) in vec3 aNormal; +layout(location = 2) in vec2 aTexCoords; +layout(location = 3) in vec3 aTangent; +layout(location = 4) in vec3 aBitangent; + +out vec3 FragPos; +out vec3 Normal; +out vec2 TexCoords; +out vec4 ReflectedClipPos; +out mat3 TBN; + +uniform mat4 model; +uniform mat4 view; +uniform mat4 projection; +uniform mat4 reflectionVP; + +uniform float time; +uniform float minHeight; +uniform float maxHeight; + +void main() +{ + // --- WORLD POSITION FIRST --- + vec4 worldPos = model * vec4(aPos, 1.0); + vec3 worldXZ = worldPos.xyz; + + // --- SAME WAVES, BUT WORLD-SPACE --- + float wave1 = sin(worldXZ.x * 0.5 + worldXZ.z * 0.4 + time * 0.8) * 0.03; + float wave2 = cos(worldXZ.x * 0.45 - worldXZ.z * 0.55 + time * 0.9) * 0.025; + float wave3 = sin((worldXZ.x + worldXZ.z) * 0.48 + time * 1.0) * 0.02; + float wave4 = cos((worldXZ.x - worldXZ.z) * 0.35 + time * 1.2) * 0.015; + float wave5 = sin((worldXZ.x * 0.6 + worldXZ.z * 0.5) + time * 0.7) * 0.01; + + float totalWave = wave1 + wave2 + wave3 + wave4 + wave5; + + worldPos.y += totalWave; + worldPos.y = clamp(worldPos.y, minHeight, maxHeight); + + FragPos = worldPos.xyz; + + // --- NORMALS --- + Normal = normalize(mat3(transpose(inverse(model))) * aNormal); + + vec3 T = normalize(mat3(model) * aTangent); + vec3 B = normalize(mat3(model) * aBitangent); + vec3 N = normalize(mat3(model) * aNormal); + TBN = mat3(T, B, N); + + TexCoords = aTexCoords; + + ReflectedClipPos = reflectionVP * worldPos; + gl_Position = projection * view * worldPos; +} diff --git a/res/textures/Daylight Box UV.png b/res/textures/Daylight Box UV.png new file mode 100644 index 0000000..6a28559 Binary files /dev/null and b/res/textures/Daylight Box UV.png differ diff --git a/res/textures/WaterNormalMap.jpeg b/res/textures/WaterNormalMap.jpeg new file mode 100644 index 0000000..aec3658 Binary files /dev/null and b/res/textures/WaterNormalMap.jpeg differ diff --git a/res/textures/WaterNormalMap.jpg b/res/textures/WaterNormalMap.jpg new file mode 100644 index 0000000..e8e0b3e Binary files /dev/null and b/res/textures/WaterNormalMap.jpg differ diff --git a/res/textures/awesomeface.png b/res/textures/awesomeface.png new file mode 100755 index 0000000..2ca937e Binary files /dev/null and b/res/textures/awesomeface.png differ diff --git a/res/textures/container.jpg b/res/textures/container.jpg new file mode 100755 index 0000000..50aa4c9 Binary files /dev/null and b/res/textures/container.jpg differ diff --git a/src/Camera.cpp b/src/Camera.cpp new file mode 100644 index 0000000..66de6f8 --- /dev/null +++ b/src/Camera.cpp @@ -0,0 +1,93 @@ +#include + +Camera::Camera(glm::vec3 position, glm::vec3 up, float yaw, float pitch) + : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), + MouseSensitivity(SENSITIVITY), Zoom(ZOOM) { + Position = position; + WorldUp = up; + Yaw = yaw; + Pitch = pitch; + updateCameraVectors(); +} +// Constructor with scalar values +Camera::Camera(float posX, float posY, float posZ, float upX, float upY, + float upZ, float yaw, float pitch) + : Front(glm::vec3(0.0f, 0.0f, -1.0f)), MovementSpeed(SPEED), + MouseSensitivity(SENSITIVITY), Zoom(ZOOM) { + Position = glm::vec3(posX, posY, posZ); + WorldUp = glm::vec3(upX, upY, upZ); + Yaw = yaw; + Pitch = pitch; + updateCameraVectors(); +} + +// Returns the view matrix calculated using Euler Angles and the LookAt Matrix +glm::mat4 Camera::GetViewMatrix() { + return glm::lookAt(Position, Position + Front, Up); +} + +// Processes input received from any keyboard-like input system. Accepts input +// parameter in the form of camera defined ENUM (to abstract it from windowing +// systems) +void Camera::ProcessKeyboard(Camera_Movement direction, float deltaTime) { + float velocity = MovementSpeed * deltaTime; + if (direction == FORWARD) + Position += Front * velocity; + if (direction == BACKWARD) + Position -= Front * velocity; + if (direction == LEFT) + Position -= Right * velocity; + if (direction == RIGHT) + Position += Right * velocity; +} + +// Processes input received from a mouse input system. Expects the offset +// value in both the x and y direction. +void Camera::ProcessMouseMovement(float xoffset, float yoffset, + GLboolean constrainPitch) { + xoffset *= MouseSensitivity; + yoffset *= MouseSensitivity; + + Yaw += xoffset; + Pitch += yoffset; + + // Make sure that when pitch is out of bounds, screen doesn't get flipped + if (constrainPitch) { + if (Pitch > 89.0f) + Pitch = 89.0f; + if (Pitch < -89.0f) + Pitch = -89.0f; + } + + // Update Front, Right and Up Vectors using the updated Euler angles + updateCameraVectors(); +} + +// Processes input received from a mouse scroll-wheel event. Only requires +// input on the vertical wheel-axis +void Camera::ProcessMouseScroll(float yoffset) { + if (Zoom >= 1.0f && Zoom <= 45.0f) + Zoom -= yoffset; + if (Zoom <= 1.0f) + Zoom = 1.0f; + if (Zoom >= 45.0f) + Zoom = 45.0f; +} + +// Calculates the front vector from the Camera's (updated) Euler Angles +void Camera::updateCameraVectors() { + // Calculate the new Front vector + glm::vec3 front; + front.x = static_cast(cos(glm::radians(static_cast(Yaw))) * + cos(glm::radians(static_cast(Pitch)))); + front.y = static_cast(sin(glm::radians(static_cast(Pitch)))); + front.z = static_cast(sin(glm::radians(static_cast(Yaw))) * + cos(glm::radians(static_cast(Pitch)))); + Front = glm::normalize(front); + // Also re-calculate the Right and Up vector + Right = glm::normalize(glm::cross( + Front, WorldUp)); // Normalize the vectors, because their length gets + // closer to 0 the more you look up or down which + // results in slower movement. + Up = glm::normalize(glm::cross(Right, Front)); +} diff --git a/src/Mesh.cpp b/src/Mesh.cpp new file mode 100644 index 0000000..194512e --- /dev/null +++ b/src/Mesh.cpp @@ -0,0 +1,301 @@ +#include +#define STB_PERLIN_IMPLEMENTATION +#include +#include + +static const float SEA_LEVEL = 0.0f; +static const float SHORE_WIDTH = 0.0f; + +Mesh Mesh::generateGrid(float width, float depth, int m, int n, + int erosionIterations = 20, float hydraulicFactor = 0.5f, + float talusAngle = 0.2f) +{ + Mesh mesh; + mesh.vertices.reserve(m * n * 8); + mesh.indices.reserve((m - 1) * (n - 1) * 6); + + float dx = width / (m - 1); + float dz = depth / (n - 1); + float startX = -width * 0.5f; + float startZ = -depth * 0.5f; + + std::vector positions(m * n); + std::vector uvs(m * n); + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution dis(0.0f, 1000.0f); + float offsetX = dis(gen); + float offsetZ = dis(gen); + + float islandRadius = 0.4f * std::max(width, depth); + + // --- Generate initial heightmap --- + for (int i = 0; i < m; ++i) { + for (int j = 0; j < n; ++j) { + float x = startX + i * dx; + float z = startZ + j * dz; + + float scale = 0.5f; + float amplitude = 3.0f; + float dist = glm::length(glm::vec2(x, z)); + float falloff = glm::clamp(1.0f - (dist / islandRadius) * (dist / islandRadius), 0.0f, 1.0f); + + float noise = 0.0f; + float freq = 1.0f; + float amp = 1.0f; + for (int o = 0; o < 3; ++o) { + noise += stb_perlin_noise3( + (x + offsetX) * scale * freq, + 0.0f, + (z + offsetZ) * scale * freq, + 0, 0, 0 + ) * amp; + freq *= 2.0f; + amp *= 0.5f; + } + noise = glm::clamp(noise, -1.0f, 1.0f); + float rawHeight = noise * amplitude * falloff; + + float t = glm::smoothstep( + SEA_LEVEL - SHORE_WIDTH, + SEA_LEVEL + SHORE_WIDTH, + rawHeight + ); + + float y = glm::mix(SEA_LEVEL, rawHeight, t); + + int idx = i * n + j; + positions[idx] = glm::vec3(x, y, z); + uvs[idx] = glm::vec2((float)i / (m - 1), (float)j / (n - 1)); + } + } + + std::vector newHeights(m * n); + + for (int iter = 0; iter < erosionIterations; ++iter) { + // Copy current heights + for (int k = 0; k < m * n; ++k) + newHeights[k] = positions[k].y; + + for (int i = 1; i < m - 1; ++i) { + for (int j = 1; j < n - 1; ++j) { + int idx = i * n + j; + float centerY = positions[idx].y; + + float totalDelta = 0.0f; + std::vector deltas(8, 0.0f); + + // Check 8 neighbors + int nIdx = 0; + for (int ni = -1; ni <= 1; ++ni) { + for (int nj = -1; nj <= 1; ++nj) { + if (ni == 0 && nj == 0) continue; + int neighborIdx = (i + ni) * n + (j + nj); + float neighborY = positions[neighborIdx].y; + float delta = centerY - neighborY; + if (delta > 0.01f) { + delta *= hydraulicFactor * 0.5f; + delta = glm::min(delta, 0.05f); // max move + deltas[nIdx++] = delta; + totalDelta += delta; + } + else { + deltas[nIdx++] = 0.0f; + } + } + } + + // Subtract total from center + newHeights[idx] -= totalDelta; + + // Distribute to neighbors proportionally + nIdx = 0; + for (int ni = -1; ni <= 1; ++ni) { + for (int nj = -1; nj <= 1; ++nj) { + if (ni == 0 && nj == 0) continue; + int neighborIdx = (i + ni) * n + (j + nj); + if (totalDelta > 0.0f) + newHeights[neighborIdx] += deltas[nIdx++] * (deltas[nIdx - 1] / totalDelta); + } + } + } + } + + // Copy new heights back + for (int k = 0; k < m * n; ++k) + positions[k].y = newHeights[k]; + } + + // --- Thermal erosion (slope-based smoothing) --- + for (int iter = 0; iter < 3; ++iter) { + std::vector newPositions = positions; + for (int i = 1; i < m - 1; ++i) { + for (int j = 1; j < n - 1; ++j) { + int idx = i * n + j; + float centerY = positions[idx].y; + + for (int ni = -1; ni <= 1; ++ni) { + for (int nj = -1; nj <= 1; ++nj) { + if (ni == 0 && nj == 0) continue; + int nIdx = (i + ni) * n + (j + nj); + float neighborY = positions[nIdx].y; + float slope = centerY - neighborY; + + if (slope > talusAngle) { + float move = (slope - talusAngle) * 0.5f; + newPositions[idx].y -= move; + newPositions[nIdx].y += move; + } + } + } + } + } + positions = newPositions; + } + + // --- Generate indices and normals (same as before) --- + for (int i = 0; i < m - 1; ++i) { + for (int j = 0; j < n - 1; ++j) { + unsigned int a = i * n + j; + unsigned int b = (i + 1) * n + j; + unsigned int c = (i + 1) * n + (j + 1); + unsigned int d = i * n + (j + 1); + + auto inside = [&](unsigned int idx) { + glm::vec2 posXZ(positions[idx].x, positions[idx].z); + return glm::length(posXZ) <= islandRadius; + }; + + if (inside(a) || inside(b) || inside(c)) { + mesh.indices.push_back(a); + mesh.indices.push_back(d); + mesh.indices.push_back(b); + } + + if (inside(b) || inside(c) || inside(d)) { + mesh.indices.push_back(d); + mesh.indices.push_back(c); + mesh.indices.push_back(b); + } + } + } + + std::vector normals(m * n, glm::vec3(0.0f)); + for (size_t k = 0; k < mesh.indices.size(); k += 3) { + unsigned int ia = mesh.indices[k + 0]; + unsigned int ib = mesh.indices[k + 1]; + unsigned int ic = mesh.indices[k + 2]; + + glm::vec3 A = positions[ia]; + glm::vec3 B = positions[ib]; + glm::vec3 C = positions[ic]; + + glm::vec3 faceN = glm::normalize(glm::cross(B - A, C - A)); + normals[ia] += faceN; + normals[ib] += faceN; + normals[ic] += faceN; + } + + for (size_t i = 0; i < normals.size(); ++i) + normals[i] = glm::normalize(normals[i]); + + for (int i = 0; i < m; ++i) { + for (int j = 0; j < n; ++j) { + int idx = i * n + j; + glm::vec3 p = positions[idx]; + glm::vec3 no = normals[idx]; + glm::vec2 uv = uvs[idx]; + + mesh.vertices.push_back(p.x); + mesh.vertices.push_back(p.y); + mesh.vertices.push_back(p.z); + + mesh.vertices.push_back(no.x); + mesh.vertices.push_back(no.y); + mesh.vertices.push_back(no.z); + + mesh.vertices.push_back(uv.x); + mesh.vertices.push_back(uv.y); + } + } + + mesh.vertexCount = m * n; + return mesh; +} + + +Mesh Mesh::generateWaterPlane(float width, float depth, unsigned int divisions = 1) +{ + Mesh mesh; + mesh.vertices.clear(); + mesh.indices.clear(); + + const float WATER_OFFSET = 0.02f; + float y = SEA_LEVEL - WATER_OFFSET; + + float halfW = width * 0.5f; + float halfD = depth * 0.5f; + + float dx = width / divisions; + float dz = depth / divisions; + + glm::vec3 upNormal(0.0f, 1.0f, 0.0f); + glm::vec3 tangent(1.0f, 0.0f, 0.0f); + glm::vec3 bitangent(0.0f, 0.0f, 1.0f); + + // Create vertices + for (unsigned int z = 0; z <= divisions; ++z) + { + for (unsigned int x = 0; x <= divisions; ++x) + { + glm::vec3 pos(-halfW + x * dx, y, -halfD + z * dz); + glm::vec2 uv((float)x / divisions, (float)z / divisions); + + // Push vertex attributes: pos, normal, uv, tangent, bitangent + mesh.vertices.push_back(pos.x); + mesh.vertices.push_back(pos.y); + mesh.vertices.push_back(pos.z); + + mesh.vertices.push_back(upNormal.x); + mesh.vertices.push_back(upNormal.y); + mesh.vertices.push_back(upNormal.z); + + mesh.vertices.push_back(uv.x); + mesh.vertices.push_back(uv.y); + + mesh.vertices.push_back(tangent.x); + mesh.vertices.push_back(tangent.y); + mesh.vertices.push_back(tangent.z); + + mesh.vertices.push_back(bitangent.x); + mesh.vertices.push_back(bitangent.y); + mesh.vertices.push_back(bitangent.z); + } + } + + // Create indices + for (unsigned int z = 0; z < divisions; ++z) + { + for (unsigned int x = 0; x < divisions; ++x) + { + unsigned int topLeft = z * (divisions + 1) + x; + unsigned int bottomLeft = (z + 1) * (divisions + 1) + x; + unsigned int topRight = topLeft + 1; + unsigned int bottomRight = bottomLeft + 1; + + mesh.indices.push_back(topLeft); + mesh.indices.push_back(bottomLeft); + mesh.indices.push_back(topRight); + + mesh.indices.push_back(topRight); + mesh.indices.push_back(bottomLeft); + mesh.indices.push_back(bottomRight); + } + } + + mesh.vertexCount = (divisions + 1) * (divisions + 1); + + return mesh; +} + diff --git a/src/Shader.cpp b/src/Shader.cpp new file mode 100644 index 0000000..8e7a4a3 --- /dev/null +++ b/src/Shader.cpp @@ -0,0 +1,180 @@ +#include + +Shader::Shader(const char *vertexPath, const char *fragmentPath) { + + readShader(vertexPath, SHADER_TYPE::VERTEX); + readShader(fragmentPath, SHADER_TYPE::FRAGMENT); + + compileShader(); +} + +// constructor using std::string +// ------------------------------------------------------------------------ +Shader::Shader(const std::string vertexPath, const std::string fragmentPath) { + + readShader(vertexPath.c_str(), SHADER_TYPE::VERTEX); + readShader(fragmentPath.c_str(), SHADER_TYPE::FRAGMENT); + + compileShader(); +} + +void Shader::readShader(char const *const shaderPath, + Shader::SHADER_TYPE type) { + + // 1. retrieve the shader source code from shaderPath + std::string shaderCode; + std::ifstream shaderFile; + + std::string *shaderCodePtr = nullptr; + + std::string shdrTypename; + + switch (type) { + case Shader::SHADER_TYPE::VERTEX: + shdrTypename = "VERTEX"; + shaderCodePtr = &(this->vertexShader); + break; + case Shader::SHADER_TYPE::FRAGMENT: + shdrTypename = "FRAGMENT"; + shaderCodePtr = &(this->fragmentShader); + break; + case Shader::SHADER_TYPE::GEOMETRY: + shdrTypename = "GEOMETRY"; + break; + } + + // ensure ifstream objects can throw exceptions: + shaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit); + try { + // open files + shaderFile.open(shaderPath); + std::stringstream shaderStream; + // read file's buffer contents into streams + shaderStream << shaderFile.rdbuf(); + // close file handlers + shaderFile.close(); + // convert stream into string + shaderCode = shaderStream.str(); + } catch (std::ifstream::failure e) { + std::cout << "ERROR::" << shdrTypename << "::FILE_NOT_SUCCESFULLY_READ" + << std::endl; + } + + *shaderCodePtr = shaderCode; + + return; +} + +void Shader::compileShader() { + // 2. compile shaders + unsigned int vertex, fragment; + + // vertex shader + GLchar const *vShdCode = this->vertexShader.c_str(); + + vertex = glCreateShader(GL_VERTEX_SHADER); + glShaderSource(vertex, 1, &vShdCode, nullptr); + glCompileShader(vertex); + checkCompileErrors(vertex, "VERTEX"); + + // fragment Shader + GLchar const *fShdCode = this->fragmentShader.c_str(); + fragment = glCreateShader(GL_FRAGMENT_SHADER); + glShaderSource(fragment, 1, &fShdCode, nullptr); + glCompileShader(fragment); + checkCompileErrors(fragment, "FRAGMENT"); + + // shader Program + ID = glCreateProgram(); + glAttachShader(ID, vertex); + glAttachShader(ID, fragment); + glLinkProgram(ID); + checkCompileErrors(ID, "PROGRAM"); + // delete the shaders as they're linked into our program now and no longer + // necessary + glDeleteShader(vertex); + glDeleteShader(fragment); +} + +// activate the shader +// ------------------------------------------------------------------------ +void Shader::use() { glUseProgram(ID); } +// utility uniform functions +// ------------------------------------------------------------------------ +void Shader::setBool(const std::string &name, bool value) const { + glUniform1i(glGetUniformLocation(ID, name.c_str()), static_cast(value)); +} +// ------------------------------------------------------------------------ +void Shader::setInt(const std::string &name, int value) const { + glUniform1i(glGetUniformLocation(ID, name.c_str()), value); +} +// ------------------------------------------------------------------------ +void Shader::setFloat(const std::string &name, float value) const { + glUniform1f(glGetUniformLocation(ID, name.c_str()), value); +} + +// ------------------------------------------------------------------------ +void Shader::setVec2(const std::string &name, const glm::vec2 &value) const { + glUniform2fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]); +} +void Shader::setVec2(const std::string &name, float x, float y) const { + glUniform2f(glGetUniformLocation(ID, name.c_str()), x, y); +} +// ------------------------------------------------------------------------ +void Shader::setVec3(const std::string &name, const glm::vec3 &value) const { + glUniform3fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]); +} +void Shader::setVec3(const std::string &name, float x, float y, float z) const { + glUniform3f(glGetUniformLocation(ID, name.c_str()), x, y, z); +} +// ------------------------------------------------------------------------ +void Shader::setVec4(const std::string &name, const glm::vec4 &value) const { + glUniform4fv(glGetUniformLocation(ID, name.c_str()), 1, &value[0]); +} +void Shader::setVec4(const std::string &name, float x, float y, float z, + float w) const { + glUniform4f(glGetUniformLocation(ID, name.c_str()), x, y, z, w); +} +// ------------------------------------------------------------------------ +void Shader::setMat2(const std::string &name, const glm::mat2 &mat) const { + glUniformMatrix2fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, + &mat[0][0]); +} +// ------------------------------------------------------------------------ +void Shader::setMat3(const std::string &name, const glm::mat3 &mat) const { + glUniformMatrix3fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, + &mat[0][0]); +} +// ------------------------------------------------------------------------ +void Shader::setMat4(const std::string &name, const glm::mat4 &mat) const { + glUniformMatrix4fv(glGetUniformLocation(ID, name.c_str()), 1, GL_FALSE, + &mat[0][0]); +} + +// utility function for checking shader compilation/linking errors. +// ------------------------------------------------------------------------ +void Shader::checkCompileErrors(unsigned int shader, std::string type) { + int success; + char infoLog[1024]; + if (type != "PROGRAM") { + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + if (!success) { + glGetShaderInfoLog(shader, 1024, nullptr, infoLog); + std::cout + << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" + << infoLog + << "\n -- --------------------------------------------------- -- " + << std::endl; + } + } else { + glGetProgramiv(shader, GL_LINK_STATUS, &success); + if (!success) { + glGetProgramInfoLog(shader, 1024, nullptr, infoLog); + std::cout + << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" + << infoLog + << "\n -- --------------------------------------------------- -- " + << std::endl; + } + } +} diff --git a/src/Skybox.cpp b/src/Skybox.cpp new file mode 100644 index 0000000..7edf9e6 --- /dev/null +++ b/src/Skybox.cpp @@ -0,0 +1,77 @@ +#include "Skybox.hpp" +#include +#include + +SkyBox::SkyBox(const std::string& path) + : width(0), height(0), channels(0), textureID(0), imagePath(path) +{ + textureID = loadCubemapFromCross(path); +} + +unsigned int SkyBox::loadCubemapFromCross(const std::string& path) +{ + int w, h, c; + unsigned char* data = stbi_load(path.c_str(), &w, &h, &c, 0); + if (!data) { + std::cout << "Failed to load skybox image: " << path << "\n"; + return 0; + } + + int faceSize = h / 3; // 3 rows in your cross + glGenTextures(1, &textureID); + glBindTexture(GL_TEXTURE_CUBE_MAP, textureID); + + // Map your layout: row 0 = top, row1 = middle, row2 = bottom + // columns 0..3 + std::vector> offsets = { + {2, 1}, // POSITIVE_X + {0, 1}, // NEGATIVE_X + {1, 0}, // POSITIVE_Y (top) + {1, 2}, // NEGATIVE_Y (bottom) + {1, 1}, // POSITIVE_Z (front) + {3, 1} // NEGATIVE_Z (back) + }; + + for (unsigned int i = 0; i < 6; ++i) + { + int col = offsets[i].first; + int row = offsets[i].second; + + unsigned char* faceData = extractFace(data, faceSize, col * faceSize, row * faceSize, w, c); + if (!faceData) { + std::cout << "Failed to extract face " << i << "\n"; + continue; + } + + GLenum format = (c == 3) ? GL_RGB : GL_RGBA; + + glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, format, + faceSize, faceSize, 0, format, GL_UNSIGNED_BYTE, faceData); + delete[] faceData; + } + + 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; +} + +unsigned char* SkyBox::extractFace(unsigned char* src, int faceSize, int xOffset, int yOffset, int imgWidth, int channels) +{ + unsigned char* face = new unsigned char[faceSize * faceSize * channels]; + for (int y = 0; y < faceSize; ++y) { + for (int x = 0; x < faceSize; ++x) { + for (int c = 0; c < channels; ++c) { + int srcIndex = ((y + yOffset) * imgWidth + (x + xOffset)) * channels + c; + int dstIndex = (y * faceSize + x) * channels + c; + face[dstIndex] = src[srcIndex]; + } + } + } + return face; +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index b7f1ee8..4fd290f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,48 +1,744 @@ -// Local Headers -#include "OpenGLPrj.hpp" +#include +#define STB_IMAGE_IMPLEMENTATION +#include +#include +#include "Skybox.hpp" -// System Headers -#include -#include -// Standard Headers -#include -#include +// World dimensions +const float worldWidth = 20000.0f; +const float worldDepth = 20000.0f; -int main(int argc, char * argv[]) { +int main() { + GLFWwindow* window = initGLFW(); - // Load GLFW and Create a Window + std::string shaderPath = "../res/shaders/"; + + std::unordered_map> shaders; + shaders["terrain"] = std::make_unique(shaderPath + "shader.vert", shaderPath + "shader.frag"); + shaders["depth"] = std::make_unique(shaderPath + "depth_shader.vert", shaderPath + "depth_shader.frag"); + shaders["sun"] = std::make_unique(shaderPath + "sun_shader.vert", shaderPath + "sun_shader.frag"); + shaders["blur"] = std::make_unique(shaderPath + "blur.vert", shaderPath + "blur.frag"); + shaders["final"] = std::make_unique(shaderPath + "final.vert", shaderPath + "final.frag"); + shaders["brightpass"] = std::make_unique(shaderPath + "bright_pass.vert", shaderPath + "bright_pass.frag"); + shaders["skybox"] = std::make_unique(shaderPath + "skybox.vert", shaderPath + "skybox.frag"); + shaders["water"] = std::make_unique(shaderPath + "water.vert",shaderPath + "water.frag"); + + + Mesh terrain = Mesh::generateGrid(10.0f, 10.0f, 1000, 1000, 20, 0.25f, 0.1f); + Mesh waterMesh = Mesh::generateWaterPlane(10000, 10000, worldWidth/10); + + glm::vec3 minPos(FLT_MAX, FLT_MAX, FLT_MAX); + glm::vec3 maxPos(-FLT_MAX, -FLT_MAX, -FLT_MAX); + + // Ensure terrain.vertices.size() is a multiple of 3 + size_t vertexCount = terrain.vertices.size() / 3; + + for (size_t i = 0; i < vertexCount; ++i) { + float x = terrain.vertices[i * 3 + 0]; + float y = terrain.vertices[i * 3 + 1]; + float z = terrain.vertices[i * 3 + 2]; + + if (x < minPos.x) minPos.x = x; + if (y < minPos.y) minPos.y = y; + if (z < minPos.z) minPos.z = z; + + if (x > maxPos.x) maxPos.x = x; + if (y > maxPos.y) maxPos.y = y; + if (z > maxPos.z) maxPos.z = z; + } + + glm::vec3 islandCenter = (minPos + maxPos) * 0.5f; + + unsigned int terrainVAO, terrainVBO, terrainEBO; + unsigned int waterVBO, waterEBO, waterVAO; + + createWaterPlaneVAO(waterMesh.vertices, waterMesh.indices, waterVAO, waterVBO, waterEBO); + + setupMesh(terrain, terrainVAO, terrainVBO, terrainEBO); + + const unsigned int SHADOW_WIDTH = 4096, SHADOW_HEIGHT = 4096; + unsigned int depthMapFBO, depthMap; + setupShadowMap(depthMapFBO, depthMap, SHADOW_WIDTH, SHADOW_HEIGHT); + + renderLoop(window, shaders, terrain, terrainVAO, + waterMesh, waterVAO, + depthMapFBO, depthMap); + + glDeleteVertexArrays(1, &terrainVAO); + glDeleteBuffers(1, &terrainVBO); + glDeleteBuffers(1, &terrainEBO); + + glDeleteVertexArrays(1, &waterVAO); + glDeleteBuffers(1, &waterVBO); + glDeleteBuffers(1, &waterEBO); + + glfwTerminate(); + return 0; +} + + +unsigned int createWaterPlaneVAO(std::vector& outVertices, std::vector& outIndices, + unsigned int& VAO, unsigned int& VBO, unsigned int& EBO) +{ + if (outVertices.empty() || outIndices.empty()) { + std::cerr << "Error: Empty vertex or index buffer!" << std::endl; + return 0; + } + + glGenVertexArrays(1, &VAO); + glGenBuffers(1, &VBO); + glGenBuffers(1, &EBO); + + glBindVertexArray(VAO); + + // Vertex buffer + glBindBuffer(GL_ARRAY_BUFFER, VBO); + glBufferData(GL_ARRAY_BUFFER, outVertices.size() * sizeof(float), outVertices.data(), GL_STATIC_DRAW); + + // Index buffer + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, outIndices.size() * sizeof(unsigned int), outIndices.data(), GL_STATIC_DRAW); + + // Stride: 3+3+2+3+3 = 14 floats + GLsizei stride = 14 * sizeof(float); + + glEnableVertexAttribArray(0); // Position + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, stride, (void*)0); + + glEnableVertexAttribArray(1); // Normal + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, stride, (void*)(3 * sizeof(float))); + + glEnableVertexAttribArray(2); // TexCoords + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, stride, (void*)(6 * sizeof(float))); + + glEnableVertexAttribArray(3); // Tangent + glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, stride, (void*)(8 * sizeof(float))); + + glEnableVertexAttribArray(4); // Bitangent + glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, stride, (void*)(11 * sizeof(float))); + + glBindVertexArray(0); + + return VAO; +} + + +// ------------------- INIT --------------------- +GLFWwindow* initGLFW() { glfwInit(); - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); - glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); - auto mWindow = glfwCreateWindow(mWidth, mHeight, "OpenGL", nullptr, nullptr); - // Check for Valid Context - if (mWindow == nullptr) { - fprintf(stderr, "Failed to Create OpenGL Context"); - return EXIT_FAILURE; + GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "Procedural Terrain with HDR Sun", nullptr, nullptr); + if (!window) { std::cout << "Failed to create window\n"; glfwTerminate(); exit(-1); } + + glfwMakeContextCurrent(window); + glfwSetFramebufferSizeCallback(window, framebuffer_size_callback); + glfwSetCursorPosCallback(window, mouse_callback); + glfwSetScrollCallback(window, scroll_callback); + glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); + + if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) { + std::cout << "Failed to initialize GLAD\n"; + exit(-1); } - // Create Context and Load OpenGL Functions - glfwMakeContextCurrent(mWindow); - gladLoadGL(); - fprintf(stderr, "OpenGL %s\n", glGetString(GL_VERSION)); + glEnable(GL_DEPTH_TEST); + glEnable(GL_CULL_FACE); + glCullFace(GL_BACK); + return window; +} + +// ------------------- MESH --------------------- +void setupMesh(const Mesh& terrain, unsigned int& VAO, unsigned int& VBO, unsigned int& EBO) { + glGenVertexArrays(1, &VAO); + glGenBuffers(1, &VBO); + glGenBuffers(1, &EBO); + + glBindVertexArray(VAO); + glBindBuffer(GL_ARRAY_BUFFER, VBO); + glBufferData(GL_ARRAY_BUFFER, terrain.vertices.size() * sizeof(float), terrain.vertices.data(), GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, terrain.indices.size() * sizeof(unsigned int), terrain.indices.data(), GL_STATIC_DRAW); + + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0); + glEnableVertexAttribArray(0); + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3 * sizeof(float))); + glEnableVertexAttribArray(1); + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float))); + glEnableVertexAttribArray(2); +} - // Rendering Loop - while (glfwWindowShouldClose(mWindow) == false) { - if (glfwGetKey(mWindow, GLFW_KEY_ESCAPE) == GLFW_PRESS) - glfwSetWindowShouldClose(mWindow, true); +// ------------------- SHADOW MAP --------------------- +void setupShadowMap(unsigned int& depthMapFBO, unsigned int& depthMap, unsigned int width, unsigned int height) { + glGenFramebuffers(1, &depthMapFBO); + glGenTextures(1, &depthMap); + glBindTexture(GL_TEXTURE_2D, depthMap); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, width, height, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); + 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_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + float borderColor[] = { 1.0, 1.0, 1.0, 1.0 }; + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); + + glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0); + glDrawBuffer(GL_NONE); + glReadBuffer(GL_NONE); + glBindFramebuffer(GL_FRAMEBUFFER, 0); +} + +// ------------------- BLOOM BUFFERS --------------------- +void setupBloomBuffers(BloomBuffers& bloom, unsigned int width, unsigned int height) { + glGenFramebuffers(1, &bloom.hdrFBO); + glBindFramebuffer(GL_FRAMEBUFFER, bloom.hdrFBO); + + glGenTextures(2, bloom.colorBuffers); + for (unsigned int i = 0; i < 2; i++) { + glBindTexture(GL_TEXTURE_2D, bloom.colorBuffers[i]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i, GL_TEXTURE_2D, bloom.colorBuffers[i], 0); + } + + unsigned int rboDepth; + glGenRenderbuffers(1, &rboDepth); + glBindRenderbuffer(GL_RENDERBUFFER, rboDepth); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, width, height); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboDepth); + + unsigned int attachments[2] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTACHMENT1 }; + glDrawBuffers(2, attachments); + + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) + std::cout << "HDR Framebuffer not complete!\n"; + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + glGenFramebuffers(2, bloom.pingpongFBO); + glGenTextures(2, bloom.pingpongColorbuffers); + for (unsigned int i = 0; i < 2; i++) { + glBindFramebuffer(GL_FRAMEBUFFER, bloom.pingpongFBO[i]); + glBindTexture(GL_TEXTURE_2D, bloom.pingpongColorbuffers[i]); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, width, height, 0, GL_RGBA, GL_FLOAT, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, bloom.pingpongColorbuffers[i], 0); + } +} + +// ------------------- SUN SETUP --------------------- +unsigned int setupSun() { + unsigned int VAO, VBO, EBO; + std::vector vertices; + std::vector indices; + const unsigned int X_SEGMENTS = 32; + const unsigned int Y_SEGMENTS = 32; + + for (unsigned int y = 0; y <= Y_SEGMENTS; ++y) { + for (unsigned int x = 0; x <= X_SEGMENTS; ++x) { + float xSegment = (float)x / X_SEGMENTS; + float ySegment = (float)y / Y_SEGMENTS; + float xPos = cos(xSegment * 2.0f * M_PI) * sin(ySegment * M_PI); + float yPos = cos(ySegment * M_PI); + float zPos = sin(xSegment * 2.0f * M_PI) * sin(ySegment * M_PI); + vertices.push_back(xPos); + vertices.push_back(yPos); + vertices.push_back(zPos); + } + } + for (unsigned int y = 0; y < Y_SEGMENTS; ++y) { + for (unsigned int x = 0; x < X_SEGMENTS; ++x) { + indices.push_back(y * (X_SEGMENTS + 1) + x); + indices.push_back((y + 1) * (X_SEGMENTS + 1) + x); + indices.push_back((y + 1) * (X_SEGMENTS + 1) + x + 1); + indices.push_back(y * (X_SEGMENTS + 1) + x); + indices.push_back((y + 1) * (X_SEGMENTS + 1) + x + 1); + indices.push_back(y * (X_SEGMENTS + 1) + x + 1); + } + } + + glGenVertexArrays(1, &VAO); + glGenBuffers(1, &VBO); + glGenBuffers(1, &EBO); + + glBindVertexArray(VAO); + glBindBuffer(GL_ARRAY_BUFFER, VBO); + glBufferData(GL_ARRAY_BUFFER, vertices.size() * sizeof(float), vertices.data(), GL_STATIC_DRAW); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(unsigned int), indices.data(), GL_STATIC_DRAW); + + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); + glEnableVertexAttribArray(0); + + return VAO; +} + +// ------------------- SKYBOX SETUP --------------------- +unsigned int createSkyboxVAO() +{ + float skyboxVertices[] = { + -1.0f, 1.0f, -1.0f, + -1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + 1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + + -1.0f, -1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, -1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f, -1.0f, 1.0f, + + 1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + + -1.0f, -1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, + -1.0f, -1.0f, 1.0f, + + -1.0f, 1.0f, -1.0f, + 1.0f, 1.0f, -1.0f, + 1.0f, 1.0f, 1.0f, + 1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, 1.0f, + -1.0f, 1.0f, -1.0f, + + -1.0f, -1.0f, -1.0f, + -1.0f, -1.0f, 1.0f, + 1.0f, -1.0f, -1.0f, + 1.0f, -1.0f, -1.0f, + -1.0f, -1.0f, 1.0f, + 1.0f, -1.0f, 1.0f + }; + + unsigned int skyboxVAO, skyboxVBO; + glGenVertexArrays(1, &skyboxVAO); + glGenBuffers(1, &skyboxVBO); + glBindVertexArray(skyboxVAO); + + glBindBuffer(GL_ARRAY_BUFFER, skyboxVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(skyboxVertices), skyboxVertices, GL_STATIC_DRAW); + + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0); + + glBindVertexArray(0); + + return skyboxVAO; +} + + +void renderSkyBox(Shader *shader, unsigned int skyboxVAO, unsigned int cubemapTexture) { + glDepthFunc(GL_LEQUAL); + shader->use(); + + glm::mat4 view = glm::mat4(glm::mat3(camera.GetViewMatrix())); + glm::mat4 projection = glm::perspective(glm::radians(camera.Zoom), + (float)SCR_WIDTH / (float)SCR_HEIGHT, 0.1f, 5000.0f); + + shader->setMat4("view", view); + shader->setMat4("projection", projection); + + glBindVertexArray(skyboxVAO); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture); + glDrawArrays(GL_TRIANGLES, 0, 36); + glBindVertexArray(0); + glDepthFunc(GL_LESS); +} + +void renderReflectiveWater(Shader* shader, unsigned int waterVAO, glm::mat4 model, glm::mat4 projection, glm::mat4 view, glm::vec3 pos) { + shader->use(); + shader->setMat4("projection", projection); + shader->setMat4("view", view); + shader->setMat4("model", model); + shader->setVec3("cameraPos", pos); + glBindVertexArray(waterVAO); + glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); +} - // Background Fill Color - glClearColor(0.25f, 0.25f, 0.25f, 1.0f); - glClear(GL_COLOR_BUFFER_BIT); - // Flip Buffers and Draw - glfwSwapBuffers(mWindow); +// ------------------- SUN RENDER --------------------- +void renderSun(Shader* shader, unsigned int sunVAO, glm::vec3 lightDir, glm::mat4 projection, glm::mat4 view) { + glm::vec3 sunPos = -100.0f * lightDir; + glm::mat4 model = glm::translate(glm::mat4(1.0f), sunPos); + model = glm::scale(model, glm::vec3(5.0f)); + shader->use(); + shader->setMat4("projection", projection); + shader->setMat4("view", view); + shader->setMat4("model", model); + shader->setVec3("color", glm::vec3(10.0f, 8.0f, 6.0f)); + glBindVertexArray(sunVAO); + glDrawElements(GL_TRIANGLE_STRIP, 32 * 32 * 6, GL_UNSIGNED_INT, 0); +} +void renderLoop( + GLFWwindow* window, + std::unordered_map>& shaders, + const Mesh& terrain, unsigned int terrainVAO, + const Mesh& waterMesh, unsigned int waterVAO, + unsigned int depthMapFBO, unsigned int depthMap) +{ + // ---------------- INITIAL SETUP ---------------- + BloomBuffers bloom; + setupBloomBuffers(bloom, SCR_WIDTH, SCR_HEIGHT); + + unsigned int sunVAO = setupSun(); + unsigned int skyboxVAO = createSkyboxVAO(); + + SkyBox skybox("../res/textures/Daylight Box UV.png"); + unsigned int cubemapTexture = skybox.getTextureID(); + + shaders["skybox"]->use(); + shaders["skybox"]->setInt("skybox", 0); + + unsigned int waterNormalMap = loadWaterNormalMap("../res/textures/WaterNormalMap.jpg"); + + shaders["water"]->use(); + shaders["water"]->setInt("reflectionTex", 0); + shaders["water"]->setInt("shadowMap", 1); + shaders["water"]->setFloat("minHeight", 0.005f); + shaders["water"]->setFloat("maxHeight", 0.05f); + + // ---------------- REFLECTION FBO ---------------- + unsigned int reflectionFBO, reflectionTex, reflectionRBO; + glGenFramebuffers(1, &reflectionFBO); + glBindFramebuffer(GL_FRAMEBUFFER, reflectionFBO); + + glGenTextures(1, &reflectionTex); + glBindTexture(GL_TEXTURE_2D, reflectionTex); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F, SCR_WIDTH, SCR_HEIGHT, 0, GL_RGBA, GL_FLOAT, nullptr); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, reflectionTex, 0); + + glGenRenderbuffers(1, &reflectionRBO); + glBindRenderbuffer(GL_RENDERBUFFER, reflectionRBO); + glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, SCR_WIDTH, SCR_HEIGHT); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, reflectionRBO); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + + glm::mat4 terrainModel = glm::mat4(1.0f); + float waterHeight = 0.01f; + float tileSize = 10.0f; + + // ==================== MAIN LOOP ==================== + while (!glfwWindowShouldClose(window)) + { + float time = (float)glfwGetTime(); + deltaTime = time - lastFrame; + lastFrame = time; + processInput(window); + + // ---------------- LIGHT SETUP ---------------- + glm::vec3 lightDir = glm::normalize(glm::vec3( + sin(time * 0.1f), + -1.0f, + -1.0f + )); + glm::vec3 lightPos = -10.0f * lightDir; + + glm::mat4 lightProjection = glm::ortho(-20.0f, 20.0f, -20.0f, 20.0f, 1.0f, 30.0f); + glm::mat4 lightView = glm::lookAt(lightPos, glm::vec3(0.0f), glm::vec3(0, 1, 0)); + glm::mat4 lightSpaceMatrix = lightProjection * lightView; + + // ================= SHADOW PASS ================= + glViewport(0, 0, 4096, 4096); + glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO); + glClear(GL_DEPTH_BUFFER_BIT); + + shaders["depth"]->use(); + shaders["depth"]->setMat4("lightSpaceMatrix", lightSpaceMatrix); + + shaders["depth"]->setMat4("model", terrainModel); + glBindVertexArray(terrainVAO); + glDrawElements(GL_TRIANGLES, terrain.indices.size(), GL_UNSIGNED_INT, 0); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // ================= REFLECTION CAMERA ================= + glm::vec3 reflCamPos = camera.Position; + reflCamPos.y = 2.0f * waterHeight - camera.Position.y; + + float reflPitch = -camera.Pitch; + float yaw = camera.Yaw; + + glm::vec3 reflFront; + reflFront.x = cos(glm::radians(yaw)) * cos(glm::radians(reflPitch)); + reflFront.y = sin(glm::radians(reflPitch)); + reflFront.z = sin(glm::radians(yaw)) * cos(glm::radians(reflPitch)); + reflFront = glm::normalize(reflFront); + + glm::mat4 reflView = glm::lookAt( + reflCamPos, + reflCamPos + reflFront, + glm::vec3(0, 1, 0) + ); + + glm::mat4 reflProjection = glm::perspective( + glm::radians(camera.Zoom), + (float)SCR_WIDTH / (float)SCR_HEIGHT, + 0.1f, + 5000.0f + ); + + // ================= REFLECTION PASS ================= + glBindFramebuffer(GL_FRAMEBUFFER, reflectionFBO); + glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glDisable(GL_CULL_FACE); + glEnable(GL_DEPTH_TEST); + + glm::mat4 reflectionVP = reflProjection * reflView; + shaders["water"]->use(); + shaders["water"]->setMat4("reflectionVP", reflectionVP); + + glActiveTexture(GL_TEXTURE3); + glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture); + shaders["water"]->setInt("skyCubemap", 3); + shaders["water"]->setFloat("normalStrength", 0.2f); + + glActiveTexture(GL_TEXTURE4); + glBindTexture(GL_TEXTURE_2D, waterNormalMap); + shaders["water"]->setInt("normalMap", 4); + shaders["water"]->setFloat("normalStrength", 0.1f); + shaders["water"]->setFloat("time", time); + shaders["water"]->setVec3("viewPos", camera.Position); + + // Terrain + shaders["terrain"]->use(); + shaders["terrain"]->setMat4("projection", reflProjection); + shaders["terrain"]->setMat4("view", reflView); + shaders["terrain"]->setMat4("model", terrainModel); + shaders["terrain"]->setVec3("viewPos", reflCamPos); + shaders["terrain"]->setVec3("lightDir", lightDir); + shaders["terrain"]->setMat4("lightSpaceMatrix", lightSpaceMatrix); + shaders["terrain"]->setFloat("clipHeight", waterHeight); + shaders["terrain"]->setInt("clipAbove", -1); + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, depthMap); + shaders["terrain"]->setInt("shadowMap", 1); + + glBindVertexArray(terrainVAO); + glDrawElements(GL_TRIANGLES, terrain.indices.size(), GL_UNSIGNED_INT, 0); + + // Sun (important!) + renderSun(shaders["sun"].get(), sunVAO, lightDir, reflProjection, reflView); + + glEnable(GL_CULL_FACE); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + + // ================= SCENE PASS ================= + glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT); + glBindFramebuffer(GL_FRAMEBUFFER, bloom.hdrFBO); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + glEnable(GL_DEPTH_TEST); + + glm::mat4 view = camera.GetViewMatrix(); + glm::mat4 projection = glm::perspective( + glm::radians(camera.Zoom), + (float)SCR_WIDTH / (float)SCR_HEIGHT, + 0.1f, + 5000.0f + ); + + renderSkyBox(shaders["skybox"].get(), skyboxVAO, cubemapTexture); + + shaders["terrain"]->use(); + shaders["terrain"]->setMat4("projection", projection); + shaders["terrain"]->setMat4("view", view); + shaders["terrain"]->setMat4("model", terrainModel); + shaders["terrain"]->setVec3("viewPos", camera.Position); + shaders["terrain"]->setVec3("lightDir", lightDir); + shaders["terrain"]->setMat4("lightSpaceMatrix", lightSpaceMatrix); + shaders["terrain"]->setFloat("clipHeight", waterHeight); + shaders["terrain"]->setInt("clipAbove", -1); // disable clipping + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, depthMap); + shaders["terrain"]->setInt("shadowMap", 1); + + glBindVertexArray(terrainVAO); + glDrawElements(GL_TRIANGLES, terrain.indices.size(), GL_UNSIGNED_INT, 0); + + // ================= WATER PASS ================= + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + + shaders["water"]->use(); + shaders["water"]->setMat4("projection", projection); + shaders["water"]->setMat4("view", view); + + glm::vec3 waterPos = camera.Position; + waterPos.y = waterHeight; + + // snap to avoid swimming artifacts + waterPos.x = floor(waterPos.x / tileSize) * tileSize; + waterPos.z = floor(waterPos.z / tileSize) * tileSize; + + glm::mat4 waterModel = glm::translate(glm::mat4(1.0f), waterPos); + + shaders["water"]->setMat4("model", waterModel); + shaders["water"]->setVec3("viewPos", camera.Position); + shaders["water"]->setVec3("lightDir", lightDir); + shaders["water"]->setFloat("time", time); + shaders["water"]->setMat4("lightSpaceMatrix", lightSpaceMatrix); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, reflectionTex); + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, depthMap); + + glBindVertexArray(waterVAO); + glDrawElements(GL_TRIANGLES, waterMesh.indices.size(), GL_UNSIGNED_INT, 0); + + glDepthMask(GL_TRUE); + glDisable(GL_BLEND); + glEnable(GL_CULL_FACE); + + // ================= SUN ================= + renderSun(shaders["sun"].get(), sunVAO, lightDir, projection, view); + + // ================= BLOOM ================= + glBindFramebuffer(GL_FRAMEBUFFER, bloom.pingpongFBO[0]); + shaders["brightpass"]->use(); + shaders["brightpass"]->setFloat("threshold", 0.6f); + glBindTexture(GL_TEXTURE_2D, bloom.colorBuffers[0]); + renderQuad(); + + bool horizontal = true, first = true; + shaders["blur"]->use(); + for (int i = 0; i < 5; i++) { + glBindFramebuffer(GL_FRAMEBUFFER, bloom.pingpongFBO[horizontal]); + shaders["blur"]->setInt("horizontal", horizontal); + glBindTexture(GL_TEXTURE_2D, + first ? bloom.pingpongColorbuffers[0] + : bloom.pingpongColorbuffers[!horizontal]); + renderQuad(); + horizontal = !horizontal; + if (first) first = false; + } + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + shaders["final"]->use(); + shaders["final"]->setBool("bloom", true); + shaders["final"]->setFloat("exposure", 1.3f); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, bloom.colorBuffers[0]); + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, bloom.pingpongColorbuffers[!horizontal]); + + renderQuad(); + + glfwSwapBuffers(window); glfwPollEvents(); - } glfwTerminate(); - return EXIT_SUCCESS; + } +} + + + +void renderQuad() { + if (quadVAO == 0) + { + float quadVertices[] = { + // positions // texCoords + -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, + -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, + 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, + }; + + glGenVertexArrays(1, &quadVAO); + glGenBuffers(1, &quadVBO); + glBindVertexArray(quadVAO); + glBindBuffer(GL_ARRAY_BUFFER, quadVBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(quadVertices), &quadVertices, GL_STATIC_DRAW); + glEnableVertexAttribArray(0); + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0); + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float))); + } + + glBindVertexArray(quadVAO); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + glBindVertexArray(0); +} + +unsigned int loadWaterNormalMap(const char* path) +{ + unsigned int textureID; + glGenTextures(1, &textureID); + + int width, height, nrChannels; + stbi_set_flip_vertically_on_load(true); + unsigned char* data = stbi_load(path, &width, &height, &nrChannels, 0); + + if (data) + { + GLenum format = (nrChannels == 3) ? GL_RGB : 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); + + stbi_image_free(data); + } + else + { + std::cerr << "Failed to load normal map: " << path << std::endl; + stbi_image_free(data); + } + + return textureID; +} + + +void processInput(GLFWwindow* window) { + if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) glfwSetWindowShouldClose(window, true); + if (glfwGetKey(window, GLFW_KEY_W) == GLFW_PRESS) camera.ProcessKeyboard(FORWARD, deltaTime); + if (glfwGetKey(window, GLFW_KEY_S) == GLFW_PRESS) camera.ProcessKeyboard(BACKWARD, deltaTime); + if (glfwGetKey(window, GLFW_KEY_A) == GLFW_PRESS) camera.ProcessKeyboard(LEFT, deltaTime); + if (glfwGetKey(window, GLFW_KEY_D) == GLFW_PRESS) camera.ProcessKeyboard(RIGHT, deltaTime); +} +void framebuffer_size_callback(GLFWwindow* window, int width, int height) { + glViewport(0, 0, width, height); +} +void mouse_callback(GLFWwindow* window, double xposd, double yposd) { + float xpos = static_cast(xposd); + float ypos = static_cast(yposd); + if (firstMouse) { lastX = xpos; lastY = ypos; firstMouse = false; } + float xoffset = xpos - lastX; + float yoffset = lastY - ypos; + lastX = xpos; + lastY = ypos; + camera.ProcessMouseMovement(xoffset, yoffset); +} +void scroll_callback(GLFWwindow* window, double xoffset, double yoffset) { + camera.ProcessMouseScroll(static_cast(yoffset)); }