diff --git a/SConstruct b/SConstruct index bca9884..cd63400 100644 --- a/SConstruct +++ b/SConstruct @@ -58,3 +58,4 @@ testEnv.Program('test/bin/vector-test.out', [catch_cpp, 'test/vector/vector-test testEnv.Program('test/bin/test-app.out', Glob('test/test-app/*.cpp')) testEnv.Program('test/bin/parallax-demo.out', Glob('test/parallax-demo/*.cpp')) testEnv.Program('test/bin/particle-demo.out', Glob('test/particle-demo/*.cpp')) +testEnv.Program('test/bin/shadow-demo.out', Glob('test/shadow-demo/*.cpp')) diff --git a/Shader/bump.fs b/Shader/bump.fs index f4aca4f..7ba6d4d 100644 --- a/Shader/bump.fs +++ b/Shader/bump.fs @@ -4,8 +4,9 @@ out vec4 FragColor; in VS_OUT { vec3 FragPos; + vec4 FragPosLightSpace; vec2 TexCoords; - vec3 TangentLightPos; + vec3 TangentLightDir; vec3 TangentViewPos; vec3 TangentFragPos; } fs_in; @@ -13,6 +14,7 @@ in VS_OUT { layout (binding = 0) uniform sampler2D albedoMap; layout (binding = 1) uniform sampler2D normalMap; layout (binding = 2) uniform sampler2D depthMap; +layout (binding = 3) uniform sampler2D shadowMap; uniform float bump_depth; uniform float number_of_steps; @@ -57,6 +59,28 @@ vec2 get_parallax_offset_uv(vec2 uv, vec3 view_direction) { return current_uv; } +float get_shadow(vec4 fragPosLightSpace, vec3 normal) { + // The bias varies depending on the angle to the light (the steeper the angle, the bigger the bias needs to be) + mediump float bias = max(0.005 * (1.0 - dot(normal, fs_in.TangentLightDir)), 0.0005); + + // perform perspective divide + mediump vec3 projCoords = fragPosLightSpace.xyz / fragPosLightSpace.w; + // transform to [0,1] range + projCoords = projCoords * 0.5 + 0.5; + + // If we're outside of [0.0, 1.0] in the coordinates, return 0 + if (projCoords.x < 0.0 || projCoords.y < 0.0 || projCoords.x > 1.0 || projCoords.y > 1.0) return 0.0; + + // get closest depth value from light's perspective (using [0,1] range fragPosLight as coords) + mediump float closestDepth = texture(shadowMap, projCoords.xy).r; + // get depth of current fragment from light's perspective + mediump float currentDepth = projCoords.z; + // check whether current frag pos is in shadow + mediump float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0; + + return shadow; +} + void main() { // Offset texture coordinates with Parallax Mapping vec3 view_direction = normalize(fs_in.TangentViewPos - fs_in.TangentFragPos); @@ -80,7 +104,7 @@ void main() { vec3 ambient = 0.1 * color; // Apply albedo with intensity based on the dot product between the light direction and the normal here - vec3 light_direction = normalize(fs_in.TangentLightPos - fs_in.TangentFragPos); + vec3 light_direction = fs_in.TangentLightDir; float light_normal_dot = max(dot(light_direction, normal), 0.0); vec3 albedo = light_normal_dot * color; @@ -90,7 +114,9 @@ void main() { vec3 specular = vec3(0.2) * spec; + float shadow = get_shadow(fs_in.FragPosLightSpace, normal); + // Apply - FragColor = vec4(ambient + albedo + specular, 1.0); + FragColor = vec4(ambient + albedo + specular - shadow * 4.0, 1.0); } diff --git a/Shader/bump.vs b/Shader/bump.vs index becd764..38dc13c 100644 --- a/Shader/bump.vs +++ b/Shader/bump.vs @@ -8,8 +8,9 @@ layout (location = 4) in vec3 aBitangent; out VS_OUT { vec3 FragPos; + vec4 FragPosLightSpace; vec2 TexCoords; - vec3 TangentLightPos; + vec3 TangentLightDir; vec3 TangentViewPos; vec3 TangentFragPos; } vs_out; @@ -18,20 +19,23 @@ uniform mat4 projection; uniform mat4 view; uniform mat4 model; -uniform vec3 lightPos; +uniform vec3 light_direction; +uniform mat4 light_space_matrix; + uniform vec3 viewPos; void main() { gl_Position = projection * view * model * vec4(aPos, 1.0); vs_out.FragPos = vec3(model * vec4(aPos, 1.0)); - vs_out.TexCoords = aTexCoords; + vs_out.TexCoords = aTexCoords; + vs_out.FragPosLightSpace = light_space_matrix * vec4(vs_out.FragPos, 1.0); vec3 T = normalize(mat3(model) * aTangent); vec3 B = normalize(mat3(model) * aBitangent); vec3 N = normalize(mat3(model) * aNormal); mat3 TBN = transpose(mat3(T, B, N)); - vs_out.TangentLightPos = TBN * lightPos; + vs_out.TangentLightDir = light_direction; vs_out.TangentViewPos = TBN * viewPos; vs_out.TangentFragPos = TBN * vs_out.FragPos; } diff --git a/Shader/depth-debug.fs b/Shader/depth-debug.fs new file mode 100644 index 0000000..9e1e129 --- /dev/null +++ b/Shader/depth-debug.fs @@ -0,0 +1,21 @@ +#version 430 +out vec4 FragColor; + +in vec2 TexCoords; + +layout (location = 0) uniform sampler2D depthMap; + +// required when using a perspective projection matrix +float LinearizeDepth(float depth) +{ + float z = depth * 2.0 - 1.0; // Back to NDC + return (2.0 * 0.1 * 100.0) / (100.0 + 0.1 - z * (100.0 - 0.1)); +} + +void main() +{ + float depthValue = texture(depthMap, TexCoords).r; + // FragColor = vec4(vec3(LinearizeDepth(depthValue) / 100.0), 1.0); // perspective + FragColor = vec4(vec3(depthValue) * 10.0, 1.0); // orthographic +} + diff --git a/Shader/depth-debug.vs b/Shader/depth-debug.vs new file mode 100644 index 0000000..7d32067 --- /dev/null +++ b/Shader/depth-debug.vs @@ -0,0 +1,13 @@ +#version 430 + +layout (location = 0) in vec3 aPos; +layout (location = 1) in vec2 aTexCoords; + +out vec2 TexCoords; + +void main() +{ + TexCoords = aTexCoords; + gl_Position = vec4(aPos, 1.0); +} + diff --git a/Shader/shadow.fs b/Shader/shadow.fs new file mode 100644 index 0000000..635e442 --- /dev/null +++ b/Shader/shadow.fs @@ -0,0 +1,7 @@ +#version 430 + +void main() +{ + // This happens implicitly anyways + // gl_FragDepth = gl_FragCoord.z; +} \ No newline at end of file diff --git a/Shader/shadow.vs b/Shader/shadow.vs new file mode 100644 index 0000000..9da0cb9 --- /dev/null +++ b/Shader/shadow.vs @@ -0,0 +1,10 @@ +#version 430 +layout (location = 0) in vec3 aPos; + +uniform mat4 lightSpaceMatrix; +uniform mat4 model; + +void main() +{ + gl_Position = lightSpaceMatrix * model * vec4(aPos, 1.0); +} \ No newline at end of file diff --git a/cpp/RenderBackend.cpp b/cpp/RenderBackend.cpp index ecc5a0f..5741e6d 100644 --- a/cpp/RenderBackend.cpp +++ b/cpp/RenderBackend.cpp @@ -17,7 +17,7 @@ void RenderBackend::initialize_window(unsigned int width, unsigned int height, S gladLoadGLLoader((GLADloadproc)glfwGetProcAddress); // FIXME: Disabled because of a bug with particles: they're discarded as if the floor moves with the camera - // glEnable(GL_DEPTH_TEST); + glEnable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } diff --git a/cpp/vendor/OBJ_Loader.h b/cpp/vendor/OBJ_Loader.h new file mode 100644 index 0000000..41accaa --- /dev/null +++ b/cpp/vendor/OBJ_Loader.h @@ -0,0 +1,977 @@ +// OBJ_Loader.h - A Single Header OBJ Model Loader + +#pragma once + +// Iostream - STD I/O Library +#include + +// Vector - STD Vector/Array Library +#include + +// String - STD String Library +#include + +// fStream - STD File I/O Library +#include + +// Math.h - STD math Library +#include + +// Print progress to console while loading (large models) +#define OBJL_CONSOLE_OUTPUT + +// Namespace: OBJL +// +// Description: The namespace that holds eveyrthing that +// is needed and used for the OBJ Model Loader +namespace objl { +// Structure: Vector2 +// +// Description: A 2D Vector that Holds Positional Data +struct Vector2 { + // Default Constructor + Vector2() { + X = 0.0f; + Y = 0.0f; + } + // Variable Set Constructor + Vector2(float X_, float Y_) { + X = X_; + Y = Y_; + } + // Bool Equals Operator Overload + bool operator==(const Vector2 &other) const { + return (this->X == other.X && this->Y == other.Y); + } + // Bool Not Equals Operator Overload + bool operator!=(const Vector2 &other) const { + return !(this->X == other.X && this->Y == other.Y); + } + // Addition Operator Overload + Vector2 operator+(const Vector2 &right) const { + return Vector2(this->X + right.X, this->Y + right.Y); + } + // Subtraction Operator Overload + Vector2 operator-(const Vector2 &right) const { + return Vector2(this->X - right.X, this->Y - right.Y); + } + // Float Multiplication Operator Overload + Vector2 operator*(const float &other) const { + return Vector2(this->X * other, this->Y * other); + } + + // Positional Variables + float X; + float Y; +}; + +// Structure: Vector3 +// +// Description: A 3D Vector that Holds Positional Data +struct Vector3 { + // Default Constructor + Vector3() { + X = 0.0f; + Y = 0.0f; + Z = 0.0f; + } + // Variable Set Constructor + Vector3(float X_, float Y_, float Z_) { + X = X_; + Y = Y_; + Z = Z_; + } + // Bool Equals Operator Overload + bool operator==(const Vector3 &other) const { + return (this->X == other.X && this->Y == other.Y && this->Z == other.Z); + } + // Bool Not Equals Operator Overload + bool operator!=(const Vector3 &other) const { + return !(this->X == other.X && this->Y == other.Y && this->Z == other.Z); + } + // Addition Operator Overload + Vector3 operator+(const Vector3 &right) const { + return Vector3(this->X + right.X, this->Y + right.Y, this->Z + right.Z); + } + // Subtraction Operator Overload + Vector3 operator-(const Vector3 &right) const { + return Vector3(this->X - right.X, this->Y - right.Y, this->Z - right.Z); + } + // Float Multiplication Operator Overload + Vector3 operator*(const float &other) const { + return Vector3(this->X * other, this->Y * other, this->Z * other); + } + // Float Division Operator Overload + Vector3 operator/(const float &other) const { + return Vector3(this->X / other, this->Y / other, this->Z / other); + } + + // Positional Variables + float X; + float Y; + float Z; +}; + +// Structure: Vertex +// +// Description: Model Vertex object that holds +// a Position, Normal, and Texture Coordinate +struct Vertex { + // Position Vector + Vector3 Position; + + // Normal Vector + Vector3 Normal; + + // Texture Coordinate Vector + Vector2 TextureCoordinate; +}; + +struct Material { + Material() { + name; + Ns = 0.0f; + Ni = 0.0f; + d = 0.0f; + illum = 0; + } + + // Material Name + std::string name; + // Ambient Color + Vector3 Ka; + // Diffuse Color + Vector3 Kd; + // Specular Color + Vector3 Ks; + // Specular Exponent + float Ns; + // Optical Density + float Ni; + // Dissolve + float d; + // Illumination + int illum; + // Ambient Texture Map + std::string map_Ka; + // Diffuse Texture Map + std::string map_Kd; + // Specular Texture Map + std::string map_Ks; + // Specular Hightlight Map + std::string map_Ns; + // Alpha Texture Map + std::string map_d; + // Bump Map + std::string map_bump; +}; + +// Structure: Mesh +// +// Description: A Simple Mesh Object that holds +// a name, a vertex list, and an index list +struct Mesh { + // Default Constructor + Mesh() {} + // Variable Set Constructor + Mesh(std::vector &_Vertices, std::vector &_Indices) { + Vertices = _Vertices; + Indices = _Indices; + } + // Mesh Name + std::string MeshName; + // Vertex List + std::vector Vertices; + // Index List + std::vector Indices; + + // Material + Material MeshMaterial; +}; + +// Namespace: Math +// +// Description: The namespace that holds all of the math +// functions need for OBJL +namespace math { +// Vector3 Cross Product +Vector3 CrossV3(const Vector3 a, const Vector3 b) { + return Vector3(a.Y * b.Z - a.Z * b.Y, a.Z * b.X - a.X * b.Z, a.X * b.Y - a.Y * b.X); +} + +// Vector3 Magnitude Calculation +float MagnitudeV3(const Vector3 in) { + return (sqrtf(powf(in.X, 2) + powf(in.Y, 2) + powf(in.Z, 2))); +} + +// Vector3 DotProduct +float DotV3(const Vector3 a, const Vector3 b) { + return (a.X * b.X) + (a.Y * b.Y) + (a.Z * b.Z); +} + +// Angle between 2 Vector3 Objects +float AngleBetweenV3(const Vector3 a, const Vector3 b) { + float angle = DotV3(a, b); + angle /= (MagnitudeV3(a) * MagnitudeV3(b)); + return angle = acosf(angle); +} + +// Projection Calculation of a onto b +Vector3 ProjV3(const Vector3 a, const Vector3 b) { + Vector3 bn = b / MagnitudeV3(b); + return bn * DotV3(a, bn); +} +} // namespace math + +// Namespace: Algorithm +// +// Description: The namespace that holds all of the +// Algorithms needed for OBJL +namespace algorithm { +// Vector3 Multiplication Opertor Overload +Vector3 operator*(const float &left, const Vector3 &right) { + return Vector3(right.X * left, right.Y * left, right.Z * left); +} + +// A test to see if P1 is on the same side as P2 of a line segment ab +bool SameSide(Vector3 p1, Vector3 p2, Vector3 a, Vector3 b) { + Vector3 cp1 = math::CrossV3(b - a, p1 - a); + Vector3 cp2 = math::CrossV3(b - a, p2 - a); + + if (math::DotV3(cp1, cp2) >= 0) + return true; + else + return false; +} + +// Generate a cross produect normal for a triangle +Vector3 GenTriNormal(Vector3 t1, Vector3 t2, Vector3 t3) { + Vector3 u = t2 - t1; + Vector3 v = t3 - t1; + + Vector3 normal = math::CrossV3(u, v); + + return normal; +} + +// Check to see if a Vector3 Point is within a 3 Vector3 Triangle +bool inTriangle(Vector3 point, Vector3 tri1, Vector3 tri2, Vector3 tri3) { + // Test to see if it is within an infinite prism that the triangle outlines. + bool within_tri_prisim = SameSide(point, tri1, tri2, tri3) && + SameSide(point, tri2, tri1, tri3) && SameSide(point, tri3, tri1, tri2); + + // If it isn't it will never be on the triangle + if (!within_tri_prisim) return false; + + // Calulate Triangle's Normal + Vector3 n = GenTriNormal(tri1, tri2, tri3); + + // Project the point onto this normal + Vector3 proj = math::ProjV3(point, n); + + // If the distance from the triangle to the point is 0 + // it lies on the triangle + if (math::MagnitudeV3(proj) == 0) + return true; + else + return false; +} + +// Split a String into a string array at a given token +inline void split(const std::string &in, std::vector &out, std::string token) { + out.clear(); + + std::string temp; + + for (int i = 0; i < int(in.size()); i++) { + std::string test = in.substr(i, token.size()); + + if (test == token) { + if (!temp.empty()) { + out.push_back(temp); + temp.clear(); + i += (int)token.size() - 1; + } else { + out.push_back(""); + } + } else if (i + token.size() >= in.size()) { + temp += in.substr(i, token.size()); + out.push_back(temp); + break; + } else { + temp += in[i]; + } + } +} + +// Get tail of string after first token and possibly following spaces +inline std::string tail(const std::string &in) { + size_t token_start = in.find_first_not_of(" \t"); + size_t space_start = in.find_first_of(" \t", token_start); + size_t tail_start = in.find_first_not_of(" \t", space_start); + size_t tail_end = in.find_last_not_of(" \t"); + if (tail_start != std::string::npos && tail_end != std::string::npos) { + return in.substr(tail_start, tail_end - tail_start + 1); + } else if (tail_start != std::string::npos) { + return in.substr(tail_start); + } + return ""; +} + +// Get first token of string +inline std::string firstToken(const std::string &in) { + if (!in.empty()) { + size_t token_start = in.find_first_not_of(" \t"); + size_t token_end = in.find_first_of(" \t", token_start); + if (token_start != std::string::npos && token_end != std::string::npos) { + return in.substr(token_start, token_end - token_start); + } else if (token_start != std::string::npos) { + return in.substr(token_start); + } + } + return ""; +} + +// Get element at given index position +template inline const T &getElement(const std::vector &elements, std::string &index) { + int idx = std::stoi(index); + if (idx < 0) + idx = int(elements.size()) + idx; + else + idx--; + return elements[idx]; +} +} // namespace algorithm + +// Class: Loader +// +// Description: The OBJ Model Loader +class Loader { + public: + // Default Constructor + Loader() {} + ~Loader() { LoadedMeshes.clear(); } + + // Load a file into the loader + // + // If file is loaded return true + // + // If the file is unable to be found + // or unable to be loaded return false + bool LoadFile(std::string Path) { + // If the file is not an .obj file return false + if (Path.substr(Path.size() - 4, 4) != ".obj") return false; + + std::ifstream file(Path); + + if (!file.is_open()) return false; + + LoadedMeshes.clear(); + LoadedVertices.clear(); + LoadedIndices.clear(); + + std::vector Positions; + std::vector TCoords; + std::vector Normals; + + std::vector Vertices; + std::vector Indices; + + std::vector MeshMatNames; + + bool listening = false; + std::string meshname; + + Mesh tempMesh; + +#ifdef OBJL_CONSOLE_OUTPUT + const unsigned int outputEveryNth = 1000; + unsigned int outputIndicator = outputEveryNth; +#endif + + std::string curline; + while (std::getline(file, curline)) { +#ifdef OBJL_CONSOLE_OUTPUT + if ((outputIndicator = ((outputIndicator + 1) % outputEveryNth)) == 1) { + if (!meshname.empty()) { + std::cout << "\r- " << meshname << "\t| vertices > " << Positions.size() + << "\t| texcoords > " << TCoords.size() << "\t| normals > " + << Normals.size() << "\t| triangles > " << (Vertices.size() / 3) + << (!MeshMatNames.empty() ? "\t| material: " + MeshMatNames.back() + : ""); + } + } +#endif + + // Generate a Mesh Object or Prepare for an object to be created + if (algorithm::firstToken(curline) == "o" || algorithm::firstToken(curline) == "g" || + curline[0] == 'g') { + if (!listening) { + listening = true; + + if (algorithm::firstToken(curline) == "o" || + algorithm::firstToken(curline) == "g") { + meshname = algorithm::tail(curline); + } else { + meshname = "unnamed"; + } + } else { + // Generate the mesh to put into the array + + if (!Indices.empty() && !Vertices.empty()) { + // Create Mesh + tempMesh = Mesh(Vertices, Indices); + tempMesh.MeshName = meshname; + + // Insert Mesh + LoadedMeshes.push_back(tempMesh); + + // Cleanup + Vertices.clear(); + Indices.clear(); + meshname.clear(); + + meshname = algorithm::tail(curline); + } else { + if (algorithm::firstToken(curline) == "o" || + algorithm::firstToken(curline) == "g") { + meshname = algorithm::tail(curline); + } else { + meshname = "unnamed"; + } + } + } +#ifdef OBJL_CONSOLE_OUTPUT + std::cout << std::endl; + outputIndicator = 0; +#endif + } + // Generate a Vertex Position + if (algorithm::firstToken(curline) == "v") { + std::vector spos; + Vector3 vpos; + algorithm::split(algorithm::tail(curline), spos, " "); + + vpos.X = std::stof(spos[0]); + vpos.Y = std::stof(spos[1]); + vpos.Z = std::stof(spos[2]); + + Positions.push_back(vpos); + } + // Generate a Vertex Texture Coordinate + if (algorithm::firstToken(curline) == "vt") { + std::vector stex; + Vector2 vtex; + algorithm::split(algorithm::tail(curline), stex, " "); + + vtex.X = std::stof(stex[0]); + vtex.Y = std::stof(stex[1]); + + TCoords.push_back(vtex); + } + // Generate a Vertex Normal; + if (algorithm::firstToken(curline) == "vn") { + std::vector snor; + Vector3 vnor; + algorithm::split(algorithm::tail(curline), snor, " "); + + vnor.X = std::stof(snor[0]); + vnor.Y = std::stof(snor[1]); + vnor.Z = std::stof(snor[2]); + + Normals.push_back(vnor); + } + // Generate a Face (vertices & indices) + if (algorithm::firstToken(curline) == "f") { + // Generate the vertices + std::vector vVerts; + GenVerticesFromRawOBJ(vVerts, Positions, TCoords, Normals, curline); + + // Add Vertices + for (int i = 0; i < int(vVerts.size()); i++) { + Vertices.push_back(vVerts[i]); + + LoadedVertices.push_back(vVerts[i]); + } + + std::vector iIndices; + + VertexTriangluation(iIndices, vVerts); + + // Add Indices + for (int i = 0; i < int(iIndices.size()); i++) { + unsigned int indnum = + (unsigned int)((Vertices.size()) - vVerts.size()) + iIndices[i]; + Indices.push_back(indnum); + + indnum = (unsigned int)((LoadedVertices.size()) - vVerts.size()) + iIndices[i]; + LoadedIndices.push_back(indnum); + } + } + // Get Mesh Material Name + if (algorithm::firstToken(curline) == "usemtl") { + MeshMatNames.push_back(algorithm::tail(curline)); + + // Create new Mesh, if Material changes within a group + if (!Indices.empty() && !Vertices.empty()) { + // Create Mesh + tempMesh = Mesh(Vertices, Indices); + tempMesh.MeshName = meshname; + int i = 2; + while (1) { + tempMesh.MeshName = meshname + "_" + std::to_string(i); + + for (auto &m : LoadedMeshes) + if (m.MeshName == tempMesh.MeshName) continue; + break; + } + + // Insert Mesh + LoadedMeshes.push_back(tempMesh); + + // Cleanup + Vertices.clear(); + Indices.clear(); + } + +#ifdef OBJL_CONSOLE_OUTPUT + outputIndicator = 0; +#endif + } + // Load Materials + if (algorithm::firstToken(curline) == "mtllib") { + // Generate LoadedMaterial + + // Generate a path to the material file + std::vector temp; + algorithm::split(Path, temp, "/"); + + std::string pathtomat = ""; + + if (temp.size() != 1) { + for (int i = 0; i < temp.size() - 1; i++) { + pathtomat += temp[i] + "/"; + } + } + + pathtomat += algorithm::tail(curline); + +#ifdef OBJL_CONSOLE_OUTPUT + std::cout << std::endl << "- find materials in: " << pathtomat << std::endl; +#endif + + // Load Materials + LoadMaterials(pathtomat); + } + } + +#ifdef OBJL_CONSOLE_OUTPUT + std::cout << std::endl; +#endif + + // Deal with last mesh + + if (!Indices.empty() && !Vertices.empty()) { + // Create Mesh + tempMesh = Mesh(Vertices, Indices); + tempMesh.MeshName = meshname; + + // Insert Mesh + LoadedMeshes.push_back(tempMesh); + } + + file.close(); + + // Set Materials for each Mesh + for (int i = 0; i < MeshMatNames.size(); i++) { + std::string matname = MeshMatNames[i]; + + // Find corresponding material name in loaded materials + // when found copy material variables into mesh material + for (int j = 0; j < LoadedMaterials.size(); j++) { + if (LoadedMaterials[j].name == matname) { + LoadedMeshes[i].MeshMaterial = LoadedMaterials[j]; + break; + } + } + } + + if (LoadedMeshes.empty() && LoadedVertices.empty() && LoadedIndices.empty()) { + return false; + } else { + return true; + } + } + + // Loaded Mesh Objects + std::vector LoadedMeshes; + // Loaded Vertex Objects + std::vector LoadedVertices; + // Loaded Index Positions + std::vector LoadedIndices; + // Loaded Material Objects + std::vector LoadedMaterials; + + private: + // Generate vertices from a list of positions, + // tcoords, normals and a face line + void GenVerticesFromRawOBJ(std::vector &oVerts, const std::vector &iPositions, + const std::vector &iTCoords, + const std::vector &iNormals, std::string icurline) { + std::vector sface, svert; + Vertex vVert; + algorithm::split(algorithm::tail(icurline), sface, " "); + + bool noNormal = false; + + // For every given vertex do this + for (int i = 0; i < int(sface.size()); i++) { + // See What type the vertex is. + int vtype; + + algorithm::split(sface[i], svert, "/"); + + // Check for just position - v1 + if (svert.size() == 1) { + // Only position + vtype = 1; + } + + // Check for position & texture - v1/vt1 + if (svert.size() == 2) { + // Position & Texture + vtype = 2; + } + + // Check for Position, Texture and Normal - v1/vt1/vn1 + // or if Position and Normal - v1//vn1 + if (svert.size() == 3) { + if (svert[1] != "") { + // Position, Texture, and Normal + vtype = 4; + } else { + // Position & Normal + vtype = 3; + } + } + + // Calculate and store the vertex + switch (vtype) { + case 1: // P + { + vVert.Position = algorithm::getElement(iPositions, svert[0]); + vVert.TextureCoordinate = Vector2(0, 0); + noNormal = true; + oVerts.push_back(vVert); + break; + } + case 2: // P/T + { + vVert.Position = algorithm::getElement(iPositions, svert[0]); + vVert.TextureCoordinate = algorithm::getElement(iTCoords, svert[1]); + noNormal = true; + oVerts.push_back(vVert); + break; + } + case 3: // P//N + { + vVert.Position = algorithm::getElement(iPositions, svert[0]); + vVert.TextureCoordinate = Vector2(0, 0); + vVert.Normal = algorithm::getElement(iNormals, svert[2]); + oVerts.push_back(vVert); + break; + } + case 4: // P/T/N + { + vVert.Position = algorithm::getElement(iPositions, svert[0]); + vVert.TextureCoordinate = algorithm::getElement(iTCoords, svert[1]); + vVert.Normal = algorithm::getElement(iNormals, svert[2]); + oVerts.push_back(vVert); + break; + } + default: { + break; + } + } + } + + // take care of missing normals + // these may not be truly acurate but it is the + // best they get for not compiling a mesh with normals + if (noNormal) { + Vector3 A = oVerts[0].Position - oVerts[1].Position; + Vector3 B = oVerts[2].Position - oVerts[1].Position; + + Vector3 normal = math::CrossV3(A, B); + + for (int i = 0; i < int(oVerts.size()); i++) { + oVerts[i].Normal = normal; + } + } + } + + // Triangulate a list of vertices into a face by printing + // inducies corresponding with triangles within it + void VertexTriangluation(std::vector &oIndices, + const std::vector &iVerts) { + // If there are 2 or less verts, + // no triangle can be created, + // so exit + if (iVerts.size() < 3) { return; } + // If it is a triangle no need to calculate it + if (iVerts.size() == 3) { + oIndices.push_back(0); + oIndices.push_back(1); + oIndices.push_back(2); + return; + } + + // Create a list of vertices + std::vector tVerts = iVerts; + + while (true) { + // For every vertex + for (int i = 0; i < int(tVerts.size()); i++) { + // pPrev = the previous vertex in the list + Vertex pPrev; + if (i == 0) { + pPrev = tVerts[tVerts.size() - 1]; + } else { + pPrev = tVerts[i - 1]; + } + + // pCur = the current vertex; + Vertex pCur = tVerts[i]; + + // pNext = the next vertex in the list + Vertex pNext; + if (i == tVerts.size() - 1) { + pNext = tVerts[0]; + } else { + pNext = tVerts[i + 1]; + } + + // Check to see if there are only 3 verts left + // if so this is the last triangle + if (tVerts.size() == 3) { + // Create a triangle from pCur, pPrev, pNext + for (int j = 0; j < int(tVerts.size()); j++) { + if (iVerts[j].Position == pCur.Position) oIndices.push_back(j); + if (iVerts[j].Position == pPrev.Position) oIndices.push_back(j); + if (iVerts[j].Position == pNext.Position) oIndices.push_back(j); + } + + tVerts.clear(); + break; + } + if (tVerts.size() == 4) { + // Create a triangle from pCur, pPrev, pNext + for (int j = 0; j < int(iVerts.size()); j++) { + if (iVerts[j].Position == pCur.Position) oIndices.push_back(j); + if (iVerts[j].Position == pPrev.Position) oIndices.push_back(j); + if (iVerts[j].Position == pNext.Position) oIndices.push_back(j); + } + + Vector3 tempVec; + for (int j = 0; j < int(tVerts.size()); j++) { + if (tVerts[j].Position != pCur.Position && + tVerts[j].Position != pPrev.Position && + tVerts[j].Position != pNext.Position) { + tempVec = tVerts[j].Position; + break; + } + } + + // Create a triangle from pCur, pPrev, pNext + for (int j = 0; j < int(iVerts.size()); j++) { + if (iVerts[j].Position == pPrev.Position) oIndices.push_back(j); + if (iVerts[j].Position == pNext.Position) oIndices.push_back(j); + if (iVerts[j].Position == tempVec) oIndices.push_back(j); + } + + tVerts.clear(); + break; + } + + // If Vertex is not an interior vertex + float angle = math::AngleBetweenV3(pPrev.Position - pCur.Position, + pNext.Position - pCur.Position) * + (180 / 3.14159265359); + if (angle <= 0 && angle >= 180) continue; + + // If any vertices are within this triangle + bool inTri = false; + for (int j = 0; j < int(iVerts.size()); j++) { + if (algorithm::inTriangle(iVerts[j].Position, pPrev.Position, pCur.Position, + pNext.Position) && + iVerts[j].Position != pPrev.Position && + iVerts[j].Position != pCur.Position && + iVerts[j].Position != pNext.Position) { + inTri = true; + break; + } + } + if (inTri) continue; + + // Create a triangle from pCur, pPrev, pNext + for (int j = 0; j < int(iVerts.size()); j++) { + if (iVerts[j].Position == pCur.Position) oIndices.push_back(j); + if (iVerts[j].Position == pPrev.Position) oIndices.push_back(j); + if (iVerts[j].Position == pNext.Position) oIndices.push_back(j); + } + + // Delete pCur from the list + for (int j = 0; j < int(tVerts.size()); j++) { + if (tVerts[j].Position == pCur.Position) { + tVerts.erase(tVerts.begin() + j); + break; + } + } + + // reset i to the start + // -1 since loop will add 1 to it + i = -1; + } + + // if no triangles were created + if (oIndices.size() == 0) break; + + // if no more vertices + if (tVerts.size() == 0) break; + } + } + + // Load Materials from .mtl file + bool LoadMaterials(std::string path) { + // If the file is not a material file return false + if (path.substr(path.size() - 4, path.size()) != ".mtl") return false; + + std::ifstream file(path); + + // If the file is not found return false + if (!file.is_open()) return false; + + Material tempMaterial; + + bool listening = false; + + // Go through each line looking for material variables + std::string curline; + while (std::getline(file, curline)) { + // new material and material name + if (algorithm::firstToken(curline) == "newmtl") { + if (!listening) { + listening = true; + + if (curline.size() > 7) { + tempMaterial.name = algorithm::tail(curline); + } else { + tempMaterial.name = "none"; + } + } else { + // Generate the material + + // Push Back loaded Material + LoadedMaterials.push_back(tempMaterial); + + // Clear Loaded Material + tempMaterial = Material(); + + if (curline.size() > 7) { + tempMaterial.name = algorithm::tail(curline); + } else { + tempMaterial.name = "none"; + } + } + } + // Ambient Color + if (algorithm::firstToken(curline) == "Ka") { + std::vector temp; + algorithm::split(algorithm::tail(curline), temp, " "); + + if (temp.size() != 3) continue; + + tempMaterial.Ka.X = std::stof(temp[0]); + tempMaterial.Ka.Y = std::stof(temp[1]); + tempMaterial.Ka.Z = std::stof(temp[2]); + } + // Diffuse Color + if (algorithm::firstToken(curline) == "Kd") { + std::vector temp; + algorithm::split(algorithm::tail(curline), temp, " "); + + if (temp.size() != 3) continue; + + tempMaterial.Kd.X = std::stof(temp[0]); + tempMaterial.Kd.Y = std::stof(temp[1]); + tempMaterial.Kd.Z = std::stof(temp[2]); + } + // Specular Color + if (algorithm::firstToken(curline) == "Ks") { + std::vector temp; + algorithm::split(algorithm::tail(curline), temp, " "); + + if (temp.size() != 3) continue; + + tempMaterial.Ks.X = std::stof(temp[0]); + tempMaterial.Ks.Y = std::stof(temp[1]); + tempMaterial.Ks.Z = std::stof(temp[2]); + } + // Specular Exponent + if (algorithm::firstToken(curline) == "Ns") { + tempMaterial.Ns = std::stof(algorithm::tail(curline)); + } + // Optical Density + if (algorithm::firstToken(curline) == "Ni") { + tempMaterial.Ni = std::stof(algorithm::tail(curline)); + } + // Dissolve + if (algorithm::firstToken(curline) == "d") { + tempMaterial.d = std::stof(algorithm::tail(curline)); + } + // Illumination + if (algorithm::firstToken(curline) == "illum") { + tempMaterial.illum = std::stoi(algorithm::tail(curline)); + } + // Ambient Texture Map + if (algorithm::firstToken(curline) == "map_Ka") { + tempMaterial.map_Ka = algorithm::tail(curline); + } + // Diffuse Texture Map + if (algorithm::firstToken(curline) == "map_Kd") { + tempMaterial.map_Kd = algorithm::tail(curline); + } + // Specular Texture Map + if (algorithm::firstToken(curline) == "map_Ks") { + tempMaterial.map_Ks = algorithm::tail(curline); + } + // Specular Hightlight Map + if (algorithm::firstToken(curline) == "map_Ns") { + tempMaterial.map_Ns = algorithm::tail(curline); + } + // Alpha Texture Map + if (algorithm::firstToken(curline) == "map_d") { + tempMaterial.map_d = algorithm::tail(curline); + } + // Bump Map + if (algorithm::firstToken(curline) == "map_Bump" || + algorithm::firstToken(curline) == "map_bump" || + algorithm::firstToken(curline) == "bump") { + tempMaterial.map_bump = algorithm::tail(curline); + } + } + + // Deal with last material + + // Push Back loaded Material + LoadedMaterials.push_back(tempMaterial); + + // Test to see if anything was loaded + // If not return false + if (LoadedMaterials.empty()) return false; + // If so return true + else + return true; + } +}; +} // namespace objl diff --git a/include/Gedeng/DirectionalLight.h b/include/Gedeng/DirectionalLight.h new file mode 100644 index 0000000..3fffc7f --- /dev/null +++ b/include/Gedeng/DirectionalLight.h @@ -0,0 +1,105 @@ +#pragma once + +#include "Gedeng/Mesh.h" +#include "Gedeng/Shader.h" +#include + +namespace Gedeng { + +class DirectionalLight { + public: + glm::vec3 direction; + + DirectionalLight() : shadow_shader(Gedeng::Shader("Shader/shadow.vs", "Shader/shadow.fs")) { + // Configure depth map + glGenFramebuffers(1, &depth_map_fbo); + + // Create depth texture + glGenTextures(1, &depth_map); + glBindTexture(GL_TEXTURE_2D, depth_map); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SHADOW_WIDTH, SHADOW_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_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + + // Attach depth texture as FBO's depth buffer + 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); + } + + explicit DirectionalLight(glm::vec3 direction) + // TODO: Avoid all this duplication + : direction(direction), shadow_shader(Gedeng::Shader("Shader/shadow.vs", "Shader/shadow.fs")) { + // Configure depth map + glGenFramebuffers(1, &depth_map_fbo); + + // Create depth texture + glGenTextures(1, &depth_map); + glBindTexture(GL_TEXTURE_2D, depth_map); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SHADOW_WIDTH, SHADOW_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_REPEAT); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); + + // Attach depth texture as FBO's depth buffer + 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); + } + + void set_in_shader(Shader &shader) { + shader.setVec3("light_direction", direction); + shader.setMat4("light_space_matrix", get_light_space_matrix()); + } + + void render_shadow(Mesh &mesh) { + shadow_shader.use(); + + shadow_shader.setMat4("lightSpaceMatrix", get_light_space_matrix()); + + glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT); + glBindFramebuffer(GL_FRAMEBUFFER, depth_map_fbo); + + mesh.render(shadow_shader); + + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + + glm::mat4 get_light_space_matrix() { + // TODO: Make the values here configurable + float near_plane = 1.0f, far_plane = 100.0f; + glm::mat4 lightProjection = glm::ortho(-40.0f, 40.0f, -40.0f, 40.0f, near_plane, far_plane); + glm::mat4 lightView = glm::lookAt(-direction * 40.0f, direction, glm::vec3(0.0, 1.0, 0.0)); + return lightProjection * lightView; + } + + void clear_shadows() { + shadow_shader.use(); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } + + void bind_depth_map_to(int unit) { + glActiveTexture(GL_TEXTURE0 + unit); + glBindTexture(GL_TEXTURE_2D, depth_map); + } + + private: + unsigned int depth_map; + unsigned int depth_map_fbo; + + const unsigned int SHADOW_WIDTH = 1024; + const unsigned int SHADOW_HEIGHT = 1024; + + Gedeng::Shader shadow_shader; +}; + +} // namespace Gedeng \ No newline at end of file diff --git a/include/Gedeng/Material.h b/include/Gedeng/Material.h new file mode 100644 index 0000000..a4202a0 --- /dev/null +++ b/include/Gedeng/Material.h @@ -0,0 +1,19 @@ +#pragma once + +namespace Gedeng { + +class Material { + public: + Material() = default; + + Material(float diffuse, float specular) : diffuse(diffuse), specular(specular) { + } + + float diffuse = 0.8; + + float specular = 0.2; + + float normal_scale = 3.0; +}; + +} // namespace Gedeng \ No newline at end of file diff --git a/include/Gedeng/Mesh.h b/include/Gedeng/Mesh.h new file mode 100644 index 0000000..1f74b01 --- /dev/null +++ b/include/Gedeng/Mesh.h @@ -0,0 +1,105 @@ +#pragma once + +// Must be the first include +#include + +// Other includes +#include + +#include "Gedeng/Shader.h" +#include "Gedeng/Spatial.h" +#include "Material.h" +#include + +namespace Gedeng { + +struct Mesh : public Spatial { + Mesh() = default; + + explicit Mesh(const std::vector &_vertices, const std::vector &_indices) + : vertex_count(_indices.size()), vertices(_vertices), indices(_indices) { + // Copy the vertices into a local classic float array. Nothing was displayed without this, + // maybe + // due to weird hidden type incompatibility or out of scope issues? + float vertices[_vertices.size()]; + std::copy(_vertices.begin(), _vertices.end(), vertices); + + unsigned int indices[_indices.size()]; + std::copy(_indices.begin(), _indices.end(), indices); + + glGenVertexArrays(1, &VAO); + glGenBuffers(1, &VBO); + glGenBuffers(1, &EBO); + + // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then + // configure vertex attributes(s). + glBindVertexArray(VAO); + + glBindBuffer(GL_ARRAY_BUFFER, VBO); + glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); + + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO); + glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); + + // position attribute + glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(float), (void *)0); + glEnableVertexAttribArray(0); + + // Normal attribute + glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(float), (void *)(3 * sizeof(float))); + glEnableVertexAttribArray(1); + + // texture coord attribute + glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 14 * sizeof(float), (void *)(6 * sizeof(float))); + glEnableVertexAttribArray(2); + + // Tangent attribute + glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(float), (void *)(8 * sizeof(float))); + glEnableVertexAttribArray(3); + + // Bitangent attribute + glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(float), (void *)(11 * sizeof(float))); + glEnableVertexAttribArray(4); + + glBindVertexArray(0); + } + + virtual void render(Shader &shader) const { + glm::mat4 model_matrix = get_matrix(); + // model_matrix[3] = glm::vec4(get_origin(), 1.0); + + shader.setMat4("model", model_matrix); + + /* // 0 can't be a valid texture name, so we use it for meshes without textures here + if (texture_id != 0) { + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_2D, texture_id); + } + if (normal_id != 0) { + glActiveTexture(GL_TEXTURE2); + glBindTexture(GL_TEXTURE_2D, normal_id); + } */ + + // TODO: Not always required (not when rendering shadows) - make functions separate? + // shader.setFloat("diffuseStrength", material.diffuse); + // shader.setFloat("specularStrength", material.specular); + // shader.setFloat("normalScale", material.normal_scale); + + glBindVertexArray(VAO); + glBindBuffer(GL_ARRAY_BUFFER, VBO); + glDrawElements(GL_TRIANGLES, vertex_count, GL_UNSIGNED_INT, 0); + } + + unsigned int vertex_count; + std::vector vertices; + std::vector indices; + + private: + unsigned int EBO; + unsigned int VBO; + unsigned int VAO; + + Material material; +}; + +} // namespace Gedeng diff --git a/include/Gedeng/ObjMesh.h b/include/Gedeng/ObjMesh.h new file mode 100644 index 0000000..b37ee4b --- /dev/null +++ b/include/Gedeng/ObjMesh.h @@ -0,0 +1,173 @@ +#pragma once + +#include "Mesh.h" +#include "vendor/OBJ_Loader.h" +#include + +namespace Gedeng { + +class ObjMesh : public Mesh { + public: + struct Settings { + Settings() = default; + + Settings(float minDistanceForRender, float maxDistanceForRender, bool colliding) + : minDistanceForRender(minDistanceForRender), maxDistanceForRender(maxDistanceForRender), + colliding(colliding) { + } + + float minDistanceForRender = 0.0; + float maxDistanceForRender = 1000.0; + bool colliding = true; + }; + + explicit ObjMesh(const std::string &path, const Settings &settings) + : Mesh(getVerticesFromFile(path), getIndicesFromFile(path)), minDistance(settings.minDistanceForRender), + maxDistance(settings.maxDistanceForRender), colliding(settings.colliding) { + } + + float minDistance; + + float maxDistance; + + bool colliding; + + private: + static std::vector getVerticesFromFile(const std::string &path) { + objl::Loader loader; + + bool loadout = loader.LoadFile(path); + + if (loadout) { + // TODO: Currently only the first mesh is used + objl::Mesh curMesh = loader.LoadedMeshes[0]; + + std::vector vertexData = std::vector(); + + auto tangents = getTangentsFromVertices(curMesh.Vertices, getIndicesFromFile(path)); + + for (int i = 0; i <= curMesh.Vertices.size(); i++) { + auto vertex = curMesh.Vertices[i]; + auto tangent = tangents.first[i]; + auto bitangent = tangents.second[i]; + + vertexData.emplace_back(vertex.Position.X); + vertexData.emplace_back(vertex.Position.Y); + vertexData.emplace_back(vertex.Position.Z); + vertexData.emplace_back(vertex.Normal.X); + vertexData.emplace_back(vertex.Normal.Y); + vertexData.emplace_back(vertex.Normal.Z); + vertexData.emplace_back(vertex.TextureCoordinate.X); + vertexData.emplace_back(vertex.TextureCoordinate.Y); + vertexData.emplace_back(tangent.x); + vertexData.emplace_back(tangent.y); + vertexData.emplace_back(tangent.z); + vertexData.emplace_back(bitangent.x); + vertexData.emplace_back(bitangent.y); + vertexData.emplace_back(bitangent.z); + } + + return vertexData; + } else { + // Output Error + std::cout << "Failed to Load File. May have failed to find it or it was not an .obj file.\n"; + } + } + + static std::pair, std::vector> + getTangentsFromVertices(const std::vector &vertices, const std::vector indices) { + // These vectors hold tangents and bitangents in the same way in which the vertices vectors + // holds vertices: Their index in the vector corresponds to indices in the indices vector. + std::vector tangents(vertices.size()); + std::vector bitangents(vertices.size()); + + // Iterate over all triangles + for (uint index = 0; index < indices.size(); index += 3) { + // Index in vertex buffer + uint i0 = indices[index]; + uint i1 = indices[index + 1]; + uint i2 = indices[index + 2]; + + // Inputs + glm::vec3 vertex0 = glm::vec3(vertices[i0].Position.X, vertices[i0].Position.Y, vertices[i0].Position.Z); + glm::vec3 vertex1 = glm::vec3(vertices[i1].Position.X, vertices[i1].Position.Y, vertices[i1].Position.Z); + glm::vec3 vertex2 = glm::vec3(vertices[i2].Position.X, vertices[i2].Position.Y, vertices[i2].Position.Z); + + glm::vec2 uv0 = glm::vec2(vertices[i0].TextureCoordinate.X, vertices[i0].TextureCoordinate.Y); + glm::vec2 uv1 = glm::vec2(vertices[i1].TextureCoordinate.X, vertices[i1].TextureCoordinate.Y); + glm::vec2 uv2 = glm::vec2(vertices[i2].TextureCoordinate.X, vertices[i2].TextureCoordinate.Y); + + glm::vec3 normal0 = glm::vec3(vertices[i0].Normal.X, vertices[i0].Normal.Y, vertices[i0].Normal.Z); + glm::vec3 normal1 = glm::vec3(vertices[i1].Normal.X, vertices[i1].Normal.Y, vertices[i1].Normal.Z); + glm::vec3 normal2 = glm::vec3(vertices[i2].Normal.X, vertices[i2].Normal.Y, vertices[i2].Normal.Z); + + // Edges of the triangle : position delta + glm::vec3 deltaPos1 = vertex1 - vertex0; + glm::vec3 deltaPos2 = vertex2 - vertex0; + + // UV delta + glm::vec2 deltaUV1 = uv1 - uv0; + glm::vec2 deltaUV2 = uv2 - uv0; + + float r = 1.0f / (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x); + glm::vec3 tangent = (deltaPos1 * deltaUV2.y - deltaPos2 * deltaUV1.y) * r; + glm::vec3 bitangent = (deltaPos2 * deltaUV1.x - deltaPos1 * deltaUV2.x) * r; + + // Add the tangent and bitangent to the current value in the array. + // This is done to get an average in the end. + tangents[i0] += tangent; + tangents[i1] += tangent; + tangents[i2] += tangent; + + bitangents[i0] += bitangent; + bitangents[i1] += bitangent; + bitangents[i2] += bitangent; + } + + return std::pair, std::vector>(tangents, bitangents); + } + + static std::vector getIndicesFromFile(const std::string &path) { + objl::Loader loader; + + bool loadout = loader.LoadFile(path); + + if (loadout) { + // TODO: Currently only the first mesh is used + objl::Mesh curMesh = loader.LoadedMeshes[0]; + + std::vector indices = std::vector(); + + // Emplace indices into the float vector + for (int j = 0; j < curMesh.Indices.size(); j++) { + indices.emplace_back(curMesh.Indices[j]); + } + + return indices; + } else { + // Output Error + std::cout << "Failed to Load File. May have failed to find it or it was not an .obj file.\n"; + } + } + + // TODO: Only prints at the moment + static std::vector getMaterialFromFile(const std::string &path) { + objl::Loader loader; + + bool loadout = loader.LoadFile(path); + + if (loadout) { + // TODO: Currently only the first mesh is used + objl::Mesh curMesh = loader.LoadedMeshes[0]; + + std::vector vertexData = std::vector(); + + return vertexData; + } else { + // Output Error + std::cout << "Failed to Load File. May have failed to find it or it was not an .obj file.\n"; + } + } +}; + +} // namespace Gedeng \ No newline at end of file diff --git a/include/Gedeng/QuadMesh.h b/include/Gedeng/QuadMesh.h index 55e1d36..71cc795 100644 --- a/include/Gedeng/QuadMesh.h +++ b/include/Gedeng/QuadMesh.h @@ -4,14 +4,14 @@ #include // Other includes +#include "Mesh.h" #include "Shader.h" -#include "Spatial.h" #include namespace Gedeng { // A simple 2x2 quad mesh consisting of two triangles. Oriented upwards by default. -class QuadMesh : public Spatial { +class QuadMesh : public Mesh { public: QuadMesh(float scale = 1.0f) { // Positions @@ -102,7 +102,7 @@ class QuadMesh : public Spatial { glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, 14 * sizeof(float), (void *)(11 * sizeof(float))); } - void render(Shader &shader) { + void render(Shader &shader) const override { shader.setMat4("model", get_matrix()); glBindVertexArray(quadVAO); diff --git a/test/shadow-demo/main.cpp b/test/shadow-demo/main.cpp new file mode 100644 index 0000000..862050e --- /dev/null +++ b/test/shadow-demo/main.cpp @@ -0,0 +1,167 @@ +#include "Gedeng/DirectionalLight.h" +#include "Gedeng/Input.h" +#include "Gedeng/Logger.h" +#include "Gedeng/ObjMesh.h" +#include "Gedeng/ParticleSystem.h" +#include "Gedeng/QuadMesh.h" +#include "Gedeng/Shader.h" +#include "Gedeng/TextLabel.h" +#include "Gedeng/Vector3.h" +#include +#define GEDENG_MAIN +#include + +// For Debugging +unsigned int quadVAO = 0; +unsigned int quadVBO; +void renderQuad() { + if (quadVAO == 0) { + float quadVertices[] = { + // positions // texture Coords + -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, + }; + // setup plane VAO + 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); +} + +class ShadowApp : public Gedeng::Application { + public: + ShadowApp(unsigned long ms_per_update, unsigned int window_size_x, unsigned int window_size_y, + Gedeng::String window_name) + : Application(ms_per_update, window_size_x, window_size_y, window_name), particle_interval(0.2), + number_of_steps(10.0), number_of_refinement_steps(10.0), bump_depth(0.1), + render_shader(Gedeng::Shader("Shader/bump.vs", "Shader/bump.fs")), + debug_shader(Gedeng::Shader("Shader/depth-debug.vs", "Shader/depth-debug.fs")), + camera(Gedeng::FPSCamera(90, 1920, 1080, 0.1, 1000.0)), + albedo("Resources/Textures/PavingStones/PavingStones070_2K_Color.jpg", Gedeng::Texture::Settings()), + bump("Resources/Textures/PavingStones/PavingStones070_2K_Displacement.jpg", Gedeng::Texture::Settings()), + normal("Resources/Textures/PavingStones/PavingStones070_2K_Normal.jpg", Gedeng::Texture::Settings()), + particle_tex1("Resources/Textures/Particles/circle.png", Gedeng::Texture::Settings()), + particle_tex2("Resources/Textures/Particles/magic.png", Gedeng::Texture::Settings()), + particle_tex3("Resources/Textures/Particles/smoke.png", Gedeng::Texture::Settings()), + quad_mesh(Gedeng::QuadMesh(10.0)), light(glm::vec3(0.57735, -0.57735, 0.57735)), + monkey_mesh("Resources/Meshes/Monkey.obj", Gedeng::ObjMesh::Settings()) { + particles.set_position(glm::vec3(0.0f, 0.0f, -10.0f)); + particles.set_velocity(glm::vec3(-2, 4, -2), glm::vec3(2, 6, 2)); + particles.set_gravity(glm::vec3(0, -4, 0)); + particles.set_color(glm::vec3(0.0f, 0.5f, 1.0f)); + particles.set_lifetime(1.0f, 1.5f); + particles.set_size(0.1); + particles.set_interval(particle_interval); + particles.set_number_of_particles(1); + particles.set_textures(&particle_tex1, &particle_tex2, &particle_tex3); + + camera.translate(glm::vec3(0.0, 2.0, 1.0)); + // camera.rotate(30, glm::vec3(1.0, 0.0, 0.0)); + + monkey_mesh.translate(glm::vec3(2.0, 3.0, -2.0)); + } + + ~ShadowApp() = default; + + void fixed_update(double delta) override { + camera.update(delta); + + if (Gedeng::Input::is_mouse_down(GLFW_MOUSE_BUTTON_LEFT)) + particles.set_position(camera.get_view_ray().get_plane_collision().collision_position); + + if (Gedeng::Input::is_key_down(GLFW_KEY_2)) particle_interval = fmax(particle_interval + 0.1 * delta, 0.02); + if (Gedeng::Input::is_key_down(GLFW_KEY_1)) particle_interval = fmax(particle_interval - 0.1 * delta, 0.02); + + particles.set_interval(particle_interval); + } + + void dynamic_update(double delta) override { + // Shadows + light.clear_shadows(); + light.render_shadow(monkey_mesh); + light.render_shadow(quad_mesh); + + glClearColor(0.1, 0.1f, 0.1, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + render_shader.use(); + + // Camera + render_shader.setMat4("projection", camera.get_projection()); + render_shader.setMat4("view", camera.get_view()); + render_shader.setVec3("viewPos", camera.get_translation()); + + // Lighting + light.set_in_shader(render_shader); + + render_shader.setFloat("number_of_steps", glm::max(0.0f, number_of_steps)); + render_shader.setFloat("number_of_refinement_steps", glm::max(0.0f, number_of_refinement_steps)); + render_shader.setFloat("bump_depth", glm::max(0.0f, bump_depth)); + + // Textures + albedo.bind_to(0); + normal.bind_to(1); + bump.bind_to(2); + light.bind_depth_map_to(3); + + // Quad which is rendered onto + quad_mesh.render(render_shader); + + // Particles + particles.set_camera(camera); + particles.update(delta); + particles.render(); + + // Props + render_shader.use(); + monkey_mesh.render(render_shader); + + /* // Render the light's depth map to a quad for debugging + debug_shader.use(); + light.bind_depth_map_to(0); + renderQuad(); */ + } + + private: + float particle_interval; + + float number_of_steps; + float number_of_refinement_steps; + float bump_depth; + + Gedeng::Shader render_shader; + Gedeng::Shader debug_shader; + + Gedeng::VertexBuffer vertex_rectangle; + + Gedeng::FPSCamera camera; + + Gedeng::Texture albedo; + Gedeng::Texture bump; + Gedeng::Texture normal; + + Gedeng::Texture particle_tex1; + Gedeng::Texture particle_tex2; + Gedeng::Texture particle_tex3; + + Gedeng::QuadMesh quad_mesh; + + Gedeng::ParticleSystem particles; + + Gedeng::DirectionalLight light; + Gedeng::ObjMesh monkey_mesh; +}; + +Gedeng::Application *Gedeng::create_application() { + GG_CLIENT_INFO("Creating Application"); + return new ShadowApp(20, 1920, 1080, String("Parallax Demo")); +}