diff --git a/project/CMakeLists.txt b/project/CMakeLists.txt index 1a4bbbb..f75d05a 100644 --- a/project/CMakeLists.txt +++ b/project/CMakeLists.txt @@ -21,8 +21,6 @@ set(IMGUI_SOURCES "imgui/backends/imgui_impl_sdlrenderer2.cpp" ) - - # Create the executable add_executable(${PROJECT_NAME} ${SOURCES}) #add_executable(${PROJECT_NAME} ${SOURCES} ${IMGUI_SOURCES}) diff --git a/project/resources/cat.png b/project/resources/cat.png new file mode 100644 index 0000000..9878788 Binary files /dev/null and b/project/resources/cat.png differ diff --git a/project/src/Camera.h b/project/src/Camera.h index bd2e1e7..ae259fb 100644 --- a/project/src/Camera.h +++ b/project/src/Camera.h @@ -40,9 +40,10 @@ namespace dae { float zNear{.1f}; float zFar{100.f}; - void Initialize(float _fovAngle = 90.f, Vector3 _origin = {0.f, 0.f, 0.f}) { + void Initialize(float _fovAngle = 90.f, Vector3 _origin = {0.f, 0.f, 0.f}, float _aspect = 1.f) { fovAngle = _fovAngle; fov = tanf((fovAngle * TO_RADIANS) / 2.f); + aspect = _aspect; origin = _origin; } @@ -54,15 +55,14 @@ namespace dae { void CalculateViewMatrix() { - viewMatrix = Matrix::CreateLookAtLH(origin, forward, up); - invViewMatrix = Matrix::Inverse(viewMatrix); + viewMatrix = Matrix::CreateLookAtLH(origin, origin + forward, Vector3::UnitY); + Matrix temp = viewMatrix; - //TODO W1 - //ONB => invViewMatrix - //Inverse(ONB) => ViewMatrix + invViewMatrix = temp.Inverse(); - //ViewMatrix => Matrix::CreateLookAtLH(...) [not implemented yet] - //DirectX Implementation => https://learn.microsoft.com/en-us/windows/win32/direct3d9/d3dxmatrixlookatlh + right = invViewMatrix.GetAxisX(); + up = invViewMatrix.GetAxisY(); + forward = invViewMatrix.GetAxisZ(); } void CalculateProjectionMatrix() { @@ -77,43 +77,50 @@ namespace dae { const float deltaTime = pTimer->GetElapsed(); //Keyboard Input - const uint8_t *pKeyboardState = SDL_GetKeyboardState(nullptr); - Vector3 zDirection{0.f, 0.f, 0.f}; - const Vector3 xDirection{0.f, 0.f, 0.f}; - const Vector3 yDirection{0.f, 0.f, 0.f}; + const uint8_t* pKeyboardState = SDL_GetKeyboardState(nullptr); + Vector3 zDirection{ 0.f,0.f,0.f }; + const Vector3 xDirection{ 0.f,0.f,0.f }; + const Vector3 yDirection{ 0.f,0.f,0.f }; - float MovementSpeed{10.f}; + float MovementSpeed{ 10.f }; - if (pKeyboardState[SDL_SCANCODE_LSHIFT]) { + if (pKeyboardState[SDL_SCANCODE_LSHIFT]) + { MovementSpeed *= 2; } - if (pKeyboardState[SDL_SCANCODE_W]) { + if (pKeyboardState[SDL_SCANCODE_W]) + { origin += forward * deltaTime * MovementSpeed; } - if (pKeyboardState[SDL_SCANCODE_A]) { + if (pKeyboardState[SDL_SCANCODE_A]) + { origin -= right * deltaTime * MovementSpeed; } - if (pKeyboardState[SDL_SCANCODE_S]) { + if (pKeyboardState[SDL_SCANCODE_S]) + { origin -= forward * deltaTime * MovementSpeed; } - if (pKeyboardState[SDL_SCANCODE_D]) { + if (pKeyboardState[SDL_SCANCODE_D]) + { origin += right * deltaTime * MovementSpeed; } - if (pKeyboardState[SDL_SCANCODE_E]) { + if (pKeyboardState[SDL_SCANCODE_E]) + { origin += up * deltaTime * MovementSpeed; } - if (pKeyboardState[SDL_SCANCODE_Q]) { + if (pKeyboardState[SDL_SCANCODE_Q]) + { origin -= up * deltaTime * MovementSpeed; } //Mouse Input - bool mousePosChange{false}; + bool mousePosChange{ false }; int mouseX{}, mouseY{}; const uint32_t mouseState = SDL_GetRelativeMouseState(&mouseX, &mouseY); mouseY *= -1; @@ -125,13 +132,16 @@ namespace dae { totalPitch += (static_cast(mouseY) * mouseSens) * TO_RADIANS; mousePosChange = true; - } else if (mouseState == SDL_BUTTON_LEFT) { + } + else if (mouseState == SDL_BUTTON_LEFT) + { zDirection = forward.Normalized() * static_cast(-mouseY); totalYaw += static_cast(mouseX) * mouseSens * TO_RADIANS; mousePosChange = true; - } else if (mouseState == SDL_BUTTON_X2) //lmb + rmb + } + else if (mouseState == SDL_BUTTON_X2) //lmb + rmb { origin.y += static_cast(mouseY) / 2; @@ -140,11 +150,12 @@ namespace dae { origin += ((zDirection + xDirection + yDirection) * cameraSpeed * deltaTime); - if (mousePosChange) { - const Matrix yawMatrix{Matrix::CreateRotationY(totalYaw)}; - const Matrix pitchMatrix{Matrix::CreateRotationX(totalPitch)}; + if (mousePosChange) + { + const Matrix yawMatrix{ Matrix::CreateRotationY(totalYaw) }; + const Matrix pitchMatrix{ Matrix::CreateRotationX(totalPitch) }; - const Matrix finalRotation{pitchMatrix * yawMatrix}; + const Matrix finalRotation{ pitchMatrix * yawMatrix }; forward = finalRotation.TransformVector(Vector3::UnitZ); forward.Normalize(); } @@ -152,6 +163,7 @@ namespace dae { //Update Matrices CalculateViewMatrix(); CalculateProjectionMatrix(); //Try to optimize this - should only be called once or when fov/aspectRatio changes + } }; } diff --git a/project/src/ColorRGB.h b/project/src/ColorRGB.h index be4a15c..f71f395 100644 --- a/project/src/ColorRGB.h +++ b/project/src/ColorRGB.h @@ -1,5 +1,6 @@ #pragma once #include "MathHelpers.h" +#include namespace dae { diff --git a/project/src/DataTypes.h b/project/src/DataTypes.h index be6d5a2..00ca474 100644 --- a/project/src/DataTypes.h +++ b/project/src/DataTypes.h @@ -8,23 +8,24 @@ namespace dae { struct Vertex { - Vector3 position{}; - ColorRGB color{colors::White}; - Vector2 uv{}; //W2 - //Vector3 normal{}; //W4 - //Vector3 tangent{}; //W4 - //Vector3 viewDirection{}; //W4 - }; + Vector4 position{}; + ColorRGB color{colors::White}; + Vector2 uv{}; + bool valid{ true }; + Vector3 normal{}; + Vector3 tangent{}; + Vector3 viewDir{}; + }; - struct Vertex_Out + struct Sample { - Vector4 position{}; - ColorRGB color{ colors::White }; - Vector2 uv{}; - //Vector3 normal{}; - //Vector3 tangent{}; - //Vector3 viewDirection{}; - }; + Vector2 uv{}; + Vector3 normal{}; + Vector3 tangent{}; + Vector3 viewDirection{}; + float depth{}; + Vector3 weight{}; + }; enum class PrimitiveTopology { @@ -36,9 +37,9 @@ namespace dae { std::vector vertices{}; std::vector indices{}; - PrimitiveTopology primitiveTopology{ PrimitiveTopology::TriangleStrip }; + PrimitiveTopology primitiveTopology{ PrimitiveTopology::TriangleList }; - std::vector vertices_out{}; + std::vector vertices_out{}; Matrix worldMatrix{}; }; } diff --git a/project/src/HitTest.cpp b/project/src/HitTest.cpp index ea42f92..48e190b 100644 --- a/project/src/HitTest.cpp +++ b/project/src/HitTest.cpp @@ -15,11 +15,84 @@ float max(float a, float b, float c) { float CrossZ(const Vector3& p0, const Vector3& p1, const Vector3& point) { - return (point.x - p0.x) * (p1.y - p0.y) - - (point.y - p0.y) * (p1.x - p0.x); + return (p1.x - p0.x) * (point.y - p0.y) + - (p1.y - p0.y) * (point.x - p0.x); } +template +Type interpolate(const Type& val0, const Type& val1, const Type& val2, float depth, float weight) +{ + return { + val0 * depth * weight + + val1 * depth * weight + + val2 * depth * weight + }; +} + +std::optional HitTest::TriangleHitTest(const Vector3& fragPos, const Vertex& v0, const Vertex& v1, const Vertex& v2) +{ + Vector3 weights; + + weights.x = CrossZ(v2.position, v1.position, fragPos); + if ( weights.x > 0 ) + return std::nullopt; + + weights.y = CrossZ(v0.position, v2.position, fragPos); + if ( weights.y > 0 ) + return std::nullopt; + + weights.z = CrossZ(v1.position, v0.position, fragPos); + if ( weights.z > 0 ) + return std::nullopt; + + const float totalWeight{ weights.x + weights.y + weights.z }; + + const float invTotalWeight{ 1.0f / totalWeight }; + const Vector3 normWeights{ + weights.x * invTotalWeight, + weights.y * invTotalWeight, + weights.z * invTotalWeight + }; + + const float depth = + 1 / (normWeights.x / v0.position.w + + normWeights.y / v1.position.w + + normWeights.z / v2.position.w); + + const Vector2 uv = + v0.uv * depth * normWeights.x / v0.position.w + + v1.uv * depth * normWeights.y / v1.position.w + + v2.uv * depth * normWeights.z / v2.position.w; + + const float interpolatedDepth = 1 / (normWeights.x / v0.position.w + normWeights.y / v1.position.w + normWeights.z / v2.position.w); + + auto interpolate = + [&](const Type& val0, const Type& val1, const Type& val2) -> Type + { + return (val0 / v0.position.w * normWeights.x + + val1 / v1.position.w * normWeights.y + + val2 / v2.position.w * normWeights.z) * interpolatedDepth; + + }; + + //Invert the normal + const Vector3 normal = interpolate(v0.normal, v1.normal, v2.normal).Normalized(); + const Vector3 tangent = interpolate(v0.tangent, v1.tangent, v2.tangent).Normalized(); + const Vector3 viewDir = interpolate(v0.viewDir, v1.viewDir, v2.viewDir).Normalized(); + + return Sample{ + .uv = uv, + .normal = normal, + .tangent = tangent, + .viewDirection = viewDir, + .depth = depth, + .weight = normWeights + }; +} + + + HitTest::HitTestSample HitTest::Triangle(const Vector3 &pos, const std::vector &vertices) { float minX = min(vertices[0].position.x, vertices[1].position.x, vertices[2].position.x); float maxX = max(vertices[0].position.x, vertices[1].position.x, vertices[2].position.x); @@ -46,9 +119,10 @@ HitTest::HitTestSample HitTest::Triangle(const Vector3 &pos, const std::vector(1.0f / totalWeight) }; + w0 *= invTotalWeight; + w1 *= invTotalWeight; + w2 *= invTotalWeight; assert(std::abs(w0 + w1 + w2 - 1) < 0.0001); diff --git a/project/src/HitTest.h b/project/src/HitTest.h index a1681f7..4fe4940 100644 --- a/project/src/HitTest.h +++ b/project/src/HitTest.h @@ -5,6 +5,7 @@ #ifndef GP1_RASTERIZER_HITTEST_H #define GP1_RASTERIZER_HITTEST_H +#include #include "DataTypes.h" #include "Maths.h" @@ -17,6 +18,9 @@ namespace HitTest { }; HitTestSample Triangle(const Vector3& pos, const std::vector& vertices); + + + std::optional TriangleHitTest(const Vector3& fragPos, const Vertex& v0, const Vertex& v1, const Vertex& v2); } #endif //GP1_RASTERIZER_HITTEST_H diff --git a/project/src/Matrix.cpp b/project/src/Matrix.cpp index 63720be..0eb4ccd 100644 --- a/project/src/Matrix.cpp +++ b/project/src/Matrix.cpp @@ -6,294 +6,281 @@ #include namespace dae { - Matrix::Matrix(const Vector3& xAxis, const Vector3& yAxis, const Vector3& zAxis, const Vector3& t) : - Matrix({ xAxis, 0 }, { yAxis, 0 }, { zAxis, 0 }, { t, 1 }) - { - } + Matrix::Matrix(const Vector3 &xAxis, const Vector3 &yAxis, const Vector3 &zAxis, const Vector3 &t) : + Matrix({xAxis, 0}, {yAxis, 0}, {zAxis, 0}, {t, 1}) { + } - Matrix::Matrix(const Vector4& xAxis, const Vector4& yAxis, const Vector4& zAxis, const Vector4& t) - { - data[0] = xAxis; - data[1] = yAxis; - data[2] = zAxis; - data[3] = t; - } + Matrix::Matrix(const Vector4 &xAxis, const Vector4 &yAxis, const Vector4 &zAxis, const Vector4 &t) { + data[0] = xAxis; + data[1] = yAxis; + data[2] = zAxis; + data[3] = t; + } - Matrix::Matrix(const Matrix& m) - { - data[0] = m[0]; - data[1] = m[1]; - data[2] = m[2]; - data[3] = m[3]; - } + Matrix::Matrix(const Matrix &m) { + data[0] = m[0]; + data[1] = m[1]; + data[2] = m[2]; + data[3] = m[3]; + } - Vector3 Matrix::TransformVector(const Vector3& v) const - { - return TransformVector(v.x, v.y, v.z); - } + Vector3 Matrix::TransformVector(const Vector3 &v) const { + return TransformVector(v.x, v.y, v.z); + } - Vector3 Matrix::TransformVector(float x, float y, float z) const - { - return Vector3{ - data[0].x * x + data[1].x * y + data[2].x * z, - data[0].y * x + data[1].y * y + data[2].y * z, - data[0].z * x + data[1].z * y + data[2].z * z - }; - } - - Vector3 Matrix::TransformPoint(const Vector3& p) const - { - return TransformPoint(p.x, p.y, p.z); - } - - Vector3 Matrix::TransformPoint(float x, float y, float z) const - { - return Vector3{ - data[0].x * x + data[1].x * y + data[2].x * z + data[3].x, - data[0].y * x + data[1].y * y + data[2].y * z + data[3].y, - data[0].z * x + data[1].z * y + data[2].z * z + data[3].z, - }; - } - - Vector4 Matrix::TransformPoint(const Vector4& p) const - { - return TransformPoint(p.x, p.y, p.z, p.w); - } - - Vector4 Matrix::TransformPoint(float x, float y, float z, float w) const - { - return Vector4{ - data[0].x * x + data[1].x * y + data[2].x * z + data[3].x, - data[0].y * x + data[1].y * y + data[2].y * z + data[3].y, - data[0].z * x + data[1].z * y + data[2].z * z + data[3].z, - data[0].w * x + data[1].w * y + data[2].w * z + data[3].w - }; - } - - const Matrix& Matrix::Transpose() - { - Matrix result{}; - for (int r{ 0 }; r < 4; ++r) - { - for (int c{ 0 }; c < 4; ++c) - { - result[r][c] = data[c][r]; - } - } - - data[0] = result[0]; - data[1] = result[1]; - data[2] = result[2]; - data[3] = result[3]; - - return *this; - } - - const Matrix& Matrix::Inverse() - { - //Optimized Inverse as explained in FGED1 - used widely in other libraries too. - const Vector3& a = data[0]; - const Vector3& b = data[1]; - const Vector3& c = data[2]; - const Vector3& d = data[3]; - - const float x = data[0][3]; - const float y = data[1][3]; - const float z = data[2][3]; - const float w = data[3][3]; - - Vector3 s = Vector3::Cross(a, b); - Vector3 t = Vector3::Cross(c, d); - Vector3 u = a * y - b * x; - Vector3 v = c * w - d * z; - - float det = Vector3::Dot(s, v) + Vector3::Dot(t, u); - assert((!AreEqual(det, 0.f)) && "ERROR: determinant is 0, there is no INVERSE!"); - float invDet = 1.f / det; - - s *= invDet; t *= invDet; u *= invDet; v *= invDet; - - Vector3 r0 = Vector3::Cross(b, v) + t * y; - Vector3 r1 = Vector3::Cross(v, a) - t * x; - Vector3 r2 = Vector3::Cross(d, u) + s * w; - Vector3 r3 = Vector3::Cross(u, c) - s * z; - - data[0] = Vector4{ r0.x, r1.x, r2.x, 0.f }; - data[1] = Vector4{ r0.y, r1.y, r2.y, 0.f }; - data[2] = Vector4{ r0.z, r1.z, r2.z, 0.f }; - data[3] = { { -Vector3::Dot(b, t)},{Vector3::Dot(a, t)},{-Vector3::Dot(d, s)},{Vector3::Dot(c, s)} }; - - return *this; - } - - Matrix Matrix::Transpose(const Matrix& m) - { - Matrix out{ m }; - out.Transpose(); - - return out; - } - - Matrix Matrix::Inverse(const Matrix& m) - { - Matrix out{ m }; - out.Inverse(); - - return out; - } - - Matrix Matrix::CreateLookAtLH(const Vector3& origin, const Vector3& forward, const Vector3& up) - { - Vector3 zAxis = forward.Normalized(); - Vector3 xAxis = Vector3::Cross(up, zAxis).Normalized(); - Vector3 yAxis = Vector3::Cross(zAxis, xAxis); - - return { xAxis, yAxis, zAxis, origin }; - } - - Matrix Matrix::CreatePerspectiveFovLH(float fov, float aspect, float zn, float zf) { - const float yScale = 1.f / tanf(fov / 2.f); - const float xScale = yScale / aspect; - - return { - { xScale, 0, 0, 0 }, - { 0, yScale, 0, 0 }, - { 0, 0, zf / (zf - zn), 1 }, - { 0, 0, -zn * zf / (zf - zn), 0 } + Vector3 Matrix::TransformVector(float x, float y, float z) const { + return Vector3{ + data[0].x * x + data[1].x * y + data[2].x * z, + data[0].y * x + data[1].y * y + data[2].y * z, + data[0].z * x + data[1].z * y + data[2].z * z }; } - Vector3 Matrix::GetAxisX() const - { - return data[0]; - } - Vector3 Matrix::GetAxisY() const - { - return data[1]; - } + Vector3 Matrix::TransformPoint(const Vector3 &p) const { + return TransformPoint(p.x, p.y, p.z); + } - Vector3 Matrix::GetAxisZ() const - { - return data[2]; - } + Vector3 Matrix::TransformPoint(float x, float y, float z) const { + return Vector3{ + data[0].x * x + data[1].x * y + data[2].x * z + data[3].x, + data[0].y * x + data[1].y * y + data[2].y * z + data[3].y, + data[0].z * x + data[1].z * y + data[2].z * z + data[3].z, + }; + } - Vector3 Matrix::GetTranslation() const - { - return data[3]; - } + Vector4 Matrix::TransformPoint(const Vector4 &p) const { + return TransformPoint(p.x, p.y, p.z, p.w); + } - Matrix Matrix::CreateTranslation(float x, float y, float z) - { - return CreateTranslation({ x, y, z }); - } + Vector4 Matrix::TransformPoint(float x, float y, float z, float w) const { + return Vector4{ + data[0].x * x + data[1].x * y + data[2].x * z + data[3].x, + data[0].y * x + data[1].y * y + data[2].y * z + data[3].y, + data[0].z * x + data[1].z * y + data[2].z * z + data[3].z, + data[0].w * x + data[1].w * y + data[2].w * z + data[3].w + }; + } - Matrix Matrix::CreateTranslation(const Vector3& t) - { - return { Vector3::UnitX, Vector3::UnitY, Vector3::UnitZ, t }; - } + const Matrix &Matrix::Transpose() { + Matrix result{}; + for (int r{0}; r < 4; ++r) { + for (int c{0}; c < 4; ++c) { + result[r][c] = data[c][r]; + } + } - Matrix Matrix::CreateRotationX(float pitch) - { - return { - {1, 0, 0, 0}, - {0, cos(pitch), -sin(pitch), 0}, - {0, sin(pitch), cos(pitch), 0}, - {0, 0, 0, 1} - }; - } + data[0] = result[0]; + data[1] = result[1]; + data[2] = result[2]; + data[3] = result[3]; - Matrix Matrix::CreateRotationY(float yaw) - { - return { - {cos(yaw), 0, -sin(yaw), 0}, - {0, 1, 0, 0}, - {sin(yaw), 0, cos(yaw), 0}, - {0, 0, 0, 1} - }; - } + return *this; + } - Matrix Matrix::CreateRotationZ(float roll) - { - return { - {cos(roll), sin(roll), 0, 0}, - {-sin(roll), cos(roll), 0, 0}, - {0, 0, 1, 0}, - {0, 0, 0, 1} - }; - } + const Matrix &Matrix::Inverse() { + //Optimized Inverse as explained in FGED1 - used widely in other libraries too. + const Vector3 &a = data[0]; + const Vector3 &b = data[1]; + const Vector3 &c = data[2]; + const Vector3 &d = data[3]; - Matrix Matrix::CreateRotation(float pitch, float yaw, float roll) - { - return CreateRotation({ pitch, yaw, roll }); - } + const float x = data[0][3]; + const float y = data[1][3]; + const float z = data[2][3]; + const float w = data[3][3]; - Matrix Matrix::CreateRotation(const Vector3& r) - { - return CreateRotationX(r[0]) * CreateRotationY(r[1]) * CreateRotationZ(r[2]); - } + Vector3 s = Vector3::Cross(a, b); + Vector3 t = Vector3::Cross(c, d); + Vector3 u = a * y - b * x; + Vector3 v = c * w - d * z; - Matrix Matrix::CreateScale(float sx, float sy, float sz) - { - return { {sx, 0, 0}, {0, sy, 0}, {0, 0, sz}, Vector3::Zero }; - } + float det = Vector3::Dot(s, v) + Vector3::Dot(t, u); + assert((!AreEqual(det, 0.f)) && "ERROR: determinant is 0, there is no INVERSE!"); + float invDet = 1.f / det; - Matrix Matrix::CreateScale(const Vector3& s) - { - return CreateScale(s[0], s[1], s[2]); - } + s *= invDet; + t *= invDet; + u *= invDet; + v *= invDet; + + Vector3 r0 = Vector3::Cross(b, v) + t * y; + Vector3 r1 = Vector3::Cross(v, a) - t * x; + Vector3 r2 = Vector3::Cross(d, u) + s * w; + Vector3 r3 = Vector3::Cross(u, c) - s * z; + + data[0] = Vector4{r0.x, r1.x, r2.x, 0.f}; + data[1] = Vector4{r0.y, r1.y, r2.y, 0.f}; + data[2] = Vector4{r0.z, r1.z, r2.z, 0.f}; + data[3] = {{-Vector3::Dot(b, t)}, + {Vector3::Dot(a, t)}, + {-Vector3::Dot(d, s)}, + {Vector3::Dot(c, s)}}; + + return *this; + } + + Matrix Matrix::Transpose(const Matrix &m) { + Matrix out{m}; + out.Transpose(); + + return out; + } + + Matrix Matrix::Inverse(const Matrix &m) { + Matrix out{m}; + out.Inverse(); + + return out; + } + + Matrix Matrix::CreateLookAtLH(const Vector3 &origin, const Vector3 &forward, const Vector3 &up) { + Vector3 zAxis = (forward - origin).Normalized(); + Vector3 xAxis = Vector3::Cross(up, zAxis).Normalized(); + Vector3 yAxis = Vector3::Cross(zAxis, xAxis).Normalized(); + Vector3 trans = + { + -Vector3::Dot(xAxis, origin), + -Vector3::Dot(yAxis, origin), + -Vector3::Dot(zAxis, origin) + }; + return { + {xAxis.x, yAxis.x, zAxis.x}, + {xAxis.y, yAxis.y, zAxis.y}, + {xAxis.z, yAxis.z, zAxis.z}, + {trans.x, trans.y, trans.z} + }; + } + + Matrix Matrix::CreatePerspectiveFovLH(float fovy, float aspect, float zn, float zf) { +// const float yScale = 1.f / tanf(fovy / 2.f); +// const float xScale = yScale / aspect; +// +// return { +// {xScale, 0, 0, 0}, +// {0, yScale, 0, 0}, +// {0, 0, zf / (zf - zn), 1}, +// {0, 0, -zn * zf / (zf - zn), 0} +// }; + + return Matrix( + { 1.f / (aspect * fovy), 0, 0, 0 }, + { 0, 1.f / fovy, 0, 0 }, + { 0, 0, (zf) / (zf - zn), 1 }, + { 0, 0, -(zf * zn) / (zf - zn), 0 } + ); + } + + Vector3 Matrix::GetAxisX() const { + return data[0]; + } + + Vector3 Matrix::GetAxisY() const { + return data[1]; + } + + Vector3 Matrix::GetAxisZ() const { + return data[2]; + } + + Vector3 Matrix::GetTranslation() const { + return data[3]; + } + + Matrix Matrix::CreateTranslation(float x, float y, float z) { + return CreateTranslation({x, y, z}); + } + + Matrix Matrix::CreateTranslation(const Vector3 &t) { + return {Vector3::UnitX, Vector3::UnitY, Vector3::UnitZ, t}; + } + + Matrix Matrix::CreateRotationX(float pitch) { + return { + {1, 0, 0, 0}, + {0, cos(pitch), -sin(pitch), 0}, + {0, sin(pitch), cos(pitch), 0}, + {0, 0, 0, 1} + }; + } + + Matrix Matrix::CreateRotationY(float yaw) { + return { + {cos(yaw), 0, -sin(yaw), 0}, + {0, 1, 0, 0}, + {sin(yaw), 0, cos(yaw), 0}, + {0, 0, 0, 1} + }; + } + + Matrix Matrix::CreateRotationZ(float roll) { + return { + {cos(roll), sin(roll), 0, 0}, + {-sin(roll), cos(roll), 0, 0}, + {0, 0, 1, 0}, + {0, 0, 0, 1} + }; + } + + Matrix Matrix::CreateRotation(float pitch, float yaw, float roll) { + return CreateRotation({pitch, yaw, roll}); + } + + Matrix Matrix::CreateRotation(const Vector3 &r) { + return CreateRotationX(r[0]) * CreateRotationY(r[1]) * CreateRotationZ(r[2]); + } + + Matrix Matrix::CreateScale(float sx, float sy, float sz) { + return {{sx, 0, 0}, {0, sy, 0}, {0, 0, sz}, Vector3::Zero}; + } + + Matrix Matrix::CreateScale(const Vector3 &s) { + return CreateScale(s[0], s[1], s[2]); + } #pragma region Operator Overloads - Vector4& Matrix::operator[](int index) - { - assert(index <= 3 && index >= 0); - return data[index]; - } - Vector4 Matrix::operator[](int index) const - { - assert(index <= 3 && index >= 0); - return data[index]; - } + Vector4 &Matrix::operator[](int index) { + assert(index <= 3 && index >= 0); + return data[index]; + } - Matrix Matrix::operator*(const Matrix& m) const - { - Matrix result{}; - Matrix m_transposed = Transpose(m); + Vector4 Matrix::operator[](int index) const { + assert(index <= 3 && index >= 0); + return data[index]; + } - for (int r{ 0 }; r < 4; ++r) - { - for (int c{ 0 }; c < 4; ++c) - { - result[r][c] = Vector4::Dot(data[r], m_transposed[c]); - } - } + Matrix Matrix::operator*(const Matrix &m) const { + Matrix result{}; + Matrix m_transposed = Transpose(m); - return result; - } + for (int r{0}; r < 4; ++r) { + for (int c{0}; c < 4; ++c) { + result[r][c] = Vector4::Dot(data[r], m_transposed[c]); + } + } - const Matrix& Matrix::operator*=(const Matrix& m) - { - Matrix copy{ *this }; - Matrix m_transposed = Transpose(m); + return result; + } - for (int r{ 0 }; r < 4; ++r) - { - for (int c{ 0 }; c < 4; ++c) - { - data[r][c] = Vector4::Dot(copy[r], m_transposed[c]); - } - } + const Matrix &Matrix::operator*=(const Matrix &m) { + Matrix copy{*this}; + Matrix m_transposed = Transpose(m); - return *this; - } + for (int r{0}; r < 4; ++r) { + for (int c{0}; c < 4; ++c) { + data[r][c] = Vector4::Dot(copy[r], m_transposed[c]); + } + } - bool Matrix::operator==(const Matrix& m) const - { - return data[0] == m.data[0] - && data[1] == m.data[1] - && data[2] == m.data[2] - && data[3] == m.data[3]; - } + return *this; + } + + bool Matrix::operator==(const Matrix &m) const { + return data[0] == m.data[0] + && data[1] == m.data[1] + && data[2] == m.data[2] + && data[3] == m.data[3]; + } #pragma endregion } \ No newline at end of file diff --git a/project/src/Renderer.cpp b/project/src/Renderer.cpp index 94d37e1..2ebbabd 100644 --- a/project/src/Renderer.cpp +++ b/project/src/Renderer.cpp @@ -1,11 +1,14 @@ //External includes #include +#include +#include #include "SDL_surface.h" //Project includes #include "Renderer.h" #include "Maths.h" #include "HitTest.h" +#include "utils.h" using namespace dae; @@ -21,13 +24,16 @@ Renderer::Renderer(SDL_Window *pWindow) : m_pWindow(pWindow) { m_pDepthBufferPixels = new float[m_Width * m_Height]; //Initialize Camera - m_Camera.Initialize(60.f, {.0f, .0f, -10.f}); - - //Initialize Camera - m_Camera.Initialize(60.f, {.0f, .0f, -10.f}); m_aspectRatio = static_cast(m_Width) / static_cast(m_Height); + m_Camera.Initialize(45.f, {.0f, 5.0f, -64.f}, m_aspectRatio); - m_pTexture = Texture::LoadFromFile("./Resources/uv_grid_2.png"); + m_currentDiffuse = Texture::LoadFromFile("./Resources/vehicle_diffuse.png"); + m_currentGloss = Texture::LoadFromFile("./Resources/vehicle_gloss.png"); + m_currentNormal = Texture::LoadFromFile("./Resources/vehicle_normal.png"); + m_currentSpecular = Texture::LoadFromFile("./Resources/vehicle_specular.png"); + + Utils::ParseOBJ("./Resources/vehicle.obj", m_mesh.vertices, m_mesh.indices, false); + m_worldMeshes.push_back(m_mesh); } Renderer::~Renderer() { @@ -36,6 +42,11 @@ Renderer::~Renderer() { void Renderer::Update(Timer *pTimer) { m_Camera.Update(pTimer); + + //Rotate the mesh + if(m_isRotating){ + m_worldMeshes[0].worldMatrix = Matrix::CreateRotationY(SDL_GetTicks() / 1000.f); + } } void Renderer::Render() { @@ -54,10 +65,11 @@ void Renderer::Render() { constexpr int numVerticies = 3; std::vector verticiesScreenSpace{}; -// VertexTransformationFunction(m_verticiesWorld, verticiesScreenSpace); - for (const Mesh ¤tMesh: m_meshesWorldStrip) { - VertexTransformationFunction(currentMesh.vertices, verticiesScreenSpace); + for (const Mesh ¤tMesh: m_worldMeshes) { + + const Matrix worldViewProjectionMatrix{ currentMesh.worldMatrix * m_Camera.viewMatrix * m_Camera.ProjectionMatrix }; + VertexTransformationFunction(worldViewProjectionMatrix, currentMesh, currentMesh.vertices, verticiesScreenSpace); int numTriangles{}; @@ -97,6 +109,10 @@ void Renderer::Render() { } break; } + if (!vertex0.valid and !vertex1.valid and !vertex2.valid) { + continue; + } + const float minX{std::min(vertex0.position.x, std::min(vertex1.position.x, vertex2.position.x))}; const float minY{std::min(vertex0.position.y, std::min(vertex1.position.y, vertex2.position.y))}; @@ -127,44 +143,31 @@ void Renderer::Render() { Vector3 P{static_cast(px) + 0.5f, static_cast(py) + 0.5f, 1.f}; - const Vector3 cross0{Vector3::Cross(vertex2.position - vertex1.position, P - vertex1.position)}; - if (cross0.z < 0) { - continue; - } - const Vector3 cross1{Vector3::Cross(vertex0.position - vertex2.position, P - vertex2.position)}; - if (cross1.z < 0) { - continue; - } - const Vector3 cross2{Vector3::Cross(vertex1.position - vertex0.position, P - vertex0.position)}; - if (cross2.z < 0) { + auto sample{ HitTest::TriangleHitTest(P, vertex0, vertex1, vertex2) }; + + if (!sample.has_value()) { continue; } - const int depthBufferIndex{px + (py * m_Width)}; + const Vector3 fragPos{ static_cast(px) + 0.5f, static_cast(py) + 0.5f, 1.f }; - float totalWeight{cross0.z + cross1.z + cross2.z}; - Vector3 weights{ - cross0.z / totalWeight, - cross1.z / totalWeight, - cross2.z / totalWeight, - }; + int depthBufferIndex{ px + (py * m_Width) }; - const float currentDepth = - 1 / (weights.x / vertex0.position.z + - weights.y / vertex1.position.z + - weights.z / vertex2.position.z); + float min{.985f}; + float max{1.f}; + float depthBuffer{(sample.value().depth - min) * (max - min)}; + float currentDepth = sample.value().depth; - const Vector2 UvCoords = - vertex0.uv * currentDepth * weights.x / vertex0.position.z + - vertex1.uv * currentDepth * weights.y / vertex1.position.z + - vertex2.uv * currentDepth * weights.z / vertex2.position.z; - - - if (m_pDepthBufferPixels[depthBufferIndex] >= currentDepth) { + if (m_pDepthBufferPixels[depthBufferIndex] > currentDepth) { m_pDepthBufferPixels[depthBufferIndex] = currentDepth; - //Update Color in Buffer - finalColor = m_pTexture->Sample(UvCoords); + if (m_isDepthBuffer) { + finalColor = ColorRGB{depthBuffer, depthBuffer, depthBuffer}; + } else { +// finalColor = m_pTexture->Sample(UvCoords); + finalColor = shadePixel(sample.value()); + } + finalColor.MaxToOne(); m_pBackBufferPixels[px + (py * m_Width)] = SDL_MapRGB(m_pBackBuffer->format, @@ -174,28 +177,14 @@ void Renderer::Render() { } + + } } } } - int x, y{0}; - //For loop over all the pixels - for (int px{}; px < 100; ++px) { - for (int py{}; py < 100; ++py) { - //Get the pixel position - x = px; - y = py; - ColorRGB test = m_pTexture->Sample(Vector2{static_cast(px) / 100, static_cast(py) / 100}); - m_pBackBufferPixels[x + (y * m_Width)] = SDL_MapRGB(m_pBackBuffer->format, - static_cast(test.r * 255), - static_cast(test.g * 255), - static_cast(test.b * 255)); - - } - - } //RENDER LOGIC @@ -205,20 +194,117 @@ void Renderer::Render() { SDL_UpdateWindowSurface(m_pWindow); } -void Renderer::VertexTransformationFunction(const std::vector &vertices_in, +void Renderer::VertexTransformationFunction(const Matrix& WorldViewProjectionMatrix, const Mesh& mesh, const std::vector &vertices_in, std::vector &vertices_out) const { - for (const Vertex &vert: vertices_in) { - Vector3 vertPos{m_Camera.invViewMatrix.TransformPoint(vert.position)}; +// vertices_out.clear(); + for (const Vertex& vert : vertices_in) + { + Vertex vertex_out{}; - vertPos.x = (vertPos.x / vertPos.z) / (m_aspectRatio * m_Camera.fov); - vertPos.y = (vertPos.y / vertPos.z) / m_Camera.fov; + Vector4 vertPos{ WorldViewProjectionMatrix.TransformPoint({vert.position.GetXYZ(), 1}) }; - vertPos.x = (vertPos.x + 1) / 2 * static_cast(m_Width); - vertPos.y = (1 - vertPos.y) / 2 * static_cast(m_Height); - vertices_out.push_back(Vertex{vertPos, vert.color, vert.uv}); + const Vector3 normal{ mesh.worldMatrix.TransformVector(vert.normal) }; + const Vector3 tangent{ mesh.worldMatrix.TransformVector(vert.tangent) }; + + + + vertPos.x /= vertPos.w; + vertPos.y /= vertPos.w; + vertPos.z /= vertPos.w; + + bool isValid{ true }; + + //Check if the vertex is inside the screen + if (vertPos.x < -1.f || vertPos.x > 1.f || + vertPos.y < -1.f || vertPos.y > 1.f || + vertPos.z < 0.f || vertPos.z > 1.f) + isValid = false; + + vertPos.x = ((vertPos.x + 1.f) / 2.f) * static_cast(m_Width); + vertPos.y = ((1.f - vertPos.y) / 2.f) * static_cast(m_Height); + + vertex_out.position = vertPos; + vertex_out.color = vert.color; + vertex_out.uv = vert.uv; + vertex_out.normal = normal; + vertex_out.tangent = tangent; + vertex_out.viewDir = WorldViewProjectionMatrix.TransformVector(vert.viewDir); + vertex_out.valid = isValid; + + vertices_out.push_back(vertex_out); } + } bool Renderer::SaveBufferToImage() const { return SDL_SaveBMP(m_pBackBuffer, "Rasterizer_ColorBuffer.bmp"); } + +ColorRGB Renderer::shadePixel(const Sample &sample) { + Vector3 lightDirection = { .577f, -.577f, .577f}; + Vector3 normal = sample.normal.Normalized(); + constexpr float lightIntensity{ 7.f }; + + ColorRGB color{ 1, 1, 1 }; + constexpr ColorRGB ambient{ .03f, .03f, .03f}; + + if(m_useNormals){ + const ColorRGB normalSample{ m_currentNormal->Sample(sample.uv) }; + const Vector4 normalMapSample{ + 2.f * normalSample.r - 1.f, + 2.f * normalSample.g - 1.f, + 2.f * normalSample.b - 1.f, + 0.f + }; + + const Vector3 biNormal{ Vector3::Cross(normal, sample.tangent) }; + const Matrix tangentToWorld{ + Vector4{ sample.tangent, 0.f }, + Vector4{ biNormal, 0.f }, + Vector4{ normal, 0.f }, + Vector4{ 0.f, 0.f, 0.f, 1.f } + }; + normal = tangentToWorld.TransformVector(normalMapSample).Normalized(); + } + + + const ColorRGB diffuseSample{ m_currentDiffuse->Sample(sample.uv) }; + double invPi = 1.0 / PI; + const ColorRGB lambert{ diffuseSample * lightIntensity * invPi }; + + //TODO: ask why deviding by PI causses Segmentation fault +// const ColorRGB lambert{ diffuseSample * lightIntensity / PI }; + + + float specularReflectance{ 1.f }; + float shininess{ 25.f }; + + specularReflectance *= m_currentGloss->Sample(sample.uv).r; + shininess *= m_currentSpecular->Sample(sample.uv).r; + + const float cosAngle = Vector3::Dot(normal, -lightDirection); + + const ColorRGB specular = specularReflectance * powf(cosAngle, shininess) * colors::White; + + if (cosAngle < 0) { + return ambient; + } + + switch(m_ShadeMode){ + case ShadeMode::ObservedArea: + break; + case ShadeMode::Diffuse: + color = lambert; + break; + case ShadeMode::Specular: + color = specular; + break; + case ShadeMode::Combined: + color = lambert + specular + ambient; + break; + } + + color *= ColorRGB{ cosAngle, cosAngle, cosAngle }; + + return color; +} diff --git a/project/src/Renderer.h b/project/src/Renderer.h index 77b1d41..f81117b 100644 --- a/project/src/Renderer.h +++ b/project/src/Renderer.h @@ -11,8 +11,16 @@ struct SDL_Window; struct SDL_Surface; + namespace dae { + enum class ShadeMode{ + ObservedArea, + Diffuse, + Specular, + Combined + }; + struct Mesh; class Timer; @@ -40,7 +48,48 @@ namespace dae { bool SaveBufferToImage() const; void - VertexTransformationFunction(const std::vector &vertices_in, std::vector &vertices_out) const; + VertexTransformationFunction(const Matrix& WorldViewProjectionMatrix, const Mesh& mesh, const std::vector &vertices_in, std::vector &vertices_out) const; + + ColorRGB shadePixel(const Sample& sample); + + void SwitchDepthBuffer(){ + m_isDepthBuffer = !m_isDepthBuffer; + } + + void SetShadeMode(ShadeMode mode) { + m_ShadeMode = mode; + } + + void ToggleRotation(){ + m_isRotating = !m_isRotating; + std::cout << "Rotation: " << m_isRotating << std::endl; + } + + void ToggleNormals(){ + m_useNormals = !m_useNormals; + std::cout << "Use Normals: " << m_useNormals << std::endl; + } + + void CycleRenderingMode(){ + switch (m_ShadeMode) { + case ShadeMode::ObservedArea: + m_ShadeMode = ShadeMode::Diffuse; + std::cout << "Diffuse" << std::endl; + break; + case ShadeMode::Diffuse: + m_ShadeMode = ShadeMode::Specular; + std::cout << "Specular" << std::endl; + break; + case ShadeMode::Specular: + m_ShadeMode = ShadeMode::Combined; + std::cout << "Combined" << std::endl; + break; + case ShadeMode::Combined: + m_ShadeMode = ShadeMode::ObservedArea; + std::cout << "Observed Area" << std::endl; + break; + } + } private: SDL_Window *m_pWindow{}; @@ -56,83 +105,22 @@ namespace dae { float m_aspectRatio{}; - Texture* m_pTexture{ nullptr }; -// -// std::vector m_verticiesWorld{ -// //Triangle 0 -// {{0.f,2.f,0.f}, {1,0,0}}, -// {{1.5f,-1.f,0.f}, {1,0,0}}, -// {{-1.5f,-1.f,0.f}, {1,0,0}}, -// -// //Triangle 1 -// {{0.f,4.f,2.f}, {1,0,0}}, -// {{3.f,-2.f,2.f}, {0,1,0}}, -// {{-3.f,-2.f,2.f}, {0,0,1}} -// }; + Texture* m_currentDiffuse{ nullptr }; + Texture* m_currentGloss{ nullptr }; + Texture* m_currentNormal{ nullptr }; + Texture* m_currentSpecular{ nullptr }; - std::vector m_meshesWorldList - { - Mesh - { - { - Vertex{ {-3, 3, -2 }, { 1, 1, 1 }, { 0.0f, 0.0f } }, - Vertex{ { 0, 3, -2 }, { 1, 1, 1 }, { 0.5f, 0.0f } }, - Vertex{ { 3, 3, -2 }, { 1, 1, 1 }, { 1.0f, 0.0f } }, - Vertex{ {-3, 0, -2 }, { 1, 1, 1 }, { 0.0f, 0.5f } }, - Vertex{ { 0, 0, -2 }, { 1, 1, 1 }, { 0.5f, 0.5f } }, - Vertex{ { 3, 0, -2 }, { 1, 1, 1 }, { 1.0f, 0.5f } }, - Vertex{ {-3, -3, -2 }, { 1, 1, 1 }, { 0.0f, 1.0f } }, - Vertex{ { 0, -3, -2 }, { 1, 1, 1 }, { 0.5f, 1.0f } }, - Vertex{ { 3, -3, -2 }, { 1, 1, 1 }, { 1.0f, 1.0f } } - }, - { - 3, 0, 1, 1, 4, 3, 4, 1 ,2, - 2, 5, 4, 6, 3, 4, 4, 7, 6, - 7, 4, 5, 5, 8, 7 - }, - PrimitiveTopology::TriangleList - } - }; - std::vector m_meshesWorldStrip - { - Mesh - { - std::vector{ - {{-3, 3, -2}, {colors::White}, {0,0} }, - {{0, 3, -2}, {colors::Red}, {0.5,0}}, - {{3, 3, -2}, {colors::Blue}, {1,0}}, - {{-3, 0, -2}, {colors::Red}, {0,0.5}}, - {{0, 0, -2}, {colors::Yellow}, {0.5, 0.5}}, - {{3, 0, -2}, {colors::White}, {1, 0.5}}, - {{-3, -3, -2}, {colors::White}, {0,1}}, - {{0, -3, -2}, {colors::White}, {0.5,1}}, - {{3, -3, -2}, {colors::White}, {1,1}}, - }, - std::vector{ - 3, 0, 4, 1, 5, 2, - 2, 6, - 6, 3, 7, 4, 8, 5, - }, - PrimitiveTopology::TriangleStrip, - } - }; - //square - std::vector testMesh{ - Mesh{ - std::vector{ - {{-1, 1, 0}, {colors::White}, {0, 0}}, - {{1, 1, 0}, {colors::White}, {1, 0}}, - {{-1, -1, 0}, {colors::White}, {0, 1}}, - {{1, -1, 0}, {colors::White}, {1, 1}}, - }, - std::vector{ - 0, 1, 2, 1, 3, 2 - }, - PrimitiveTopology::TriangleList - } - }; -// std::vector m_verticies_screenSpace{}; + ShadeMode m_ShadeMode{ ShadeMode::Combined }; + bool m_isRotating{ true }; + bool m_useNormals{ true }; + + Mesh m_mesh{}; + + bool m_isDepthBuffer{ false }; + + + std::vector m_worldMeshes{}; float* m_pDepthBufferPixels{}; diff --git a/project/src/Utils.h b/project/src/Utils.h index 6fe2a7b..d4ddbd4 100644 --- a/project/src/Utils.h +++ b/project/src/Utils.h @@ -4,7 +4,7 @@ #include "Maths.h" #include "DataTypes.h" -#define DISABLE_OBJ +//#define DISABLE_OBJ namespace dae { @@ -84,7 +84,7 @@ namespace dae { // OBJ format uses 1-based arrays file >> iPosition; - vertex.position = positions[iPosition - 1]; + vertex.position = positions[iPosition - 1].ToVector4(); if ('/' == file.peek())//is next in buffer == '/' ? { diff --git a/project/src/Vector4.h b/project/src/Vector4.h index 08235df..3bab6ed 100644 --- a/project/src/Vector4.h +++ b/project/src/Vector4.h @@ -6,10 +6,10 @@ namespace dae struct Vector3; struct Vector4 { - float x; - float y; - float z; - float w; + float x{}; + float y{}; + float z{}; + float w{}; Vector4() = default; Vector4(float _x, float _y, float _z, float _w); @@ -20,8 +20,8 @@ namespace dae float Normalize(); Vector4 Normalized() const; - Vector2 GetXY() const; - Vector3 GetXYZ() const; + [[nodiscard]] Vector2 GetXY() const; + [[nodiscard]] Vector3 GetXYZ() const; static float Dot(const Vector4& v1, const Vector4& v2); diff --git a/project/src/main.cpp b/project/src/main.cpp index 2fd9604..6d7e345 100644 --- a/project/src/main.cpp +++ b/project/src/main.cpp @@ -2,8 +2,10 @@ #ifdef ENABLE_VLD #include "vld.h" #endif + #include "SDL.h" #include "SDL_surface.h" + #undef main //Standard includes @@ -15,17 +17,15 @@ using namespace dae; -void ShutDown(SDL_Window* pWindow) -{ +void ShutDown(SDL_Window *pWindow) { SDL_DestroyWindow(pWindow); SDL_Quit(); } -int main(int argc, char* args[]) -{ +int main(int argc, char *args[]) { //Unreferenced parameters - (void)argc; - (void)args; + (void) argc; + (void) args; //Create window + surfaces SDL_Init(SDL_INIT_VIDEO); @@ -33,8 +33,8 @@ int main(int argc, char* args[]) const uint32_t width = 640; const uint32_t height = 480; - SDL_Window* pWindow = SDL_CreateWindow( - "Rasterizer - **Insert Name**", + SDL_Window *pWindow = SDL_CreateWindow( + "Rasterizer - Bram Verhulst", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, width, height, 0); @@ -55,14 +55,11 @@ int main(int argc, char* args[]) float printTimer = 0.f; bool isLooping = true; bool takeScreenshot = false; - while (isLooping) - { + while (isLooping) { //--------- Get input events --------- SDL_Event e; - while (SDL_PollEvent(&e)) - { - switch (e.type) - { + while (SDL_PollEvent(&e)) { + switch (e.type) { case SDL_QUIT: isLooping = false; break; @@ -70,6 +67,21 @@ int main(int argc, char* args[]) if (e.key.keysym.scancode == SDL_SCANCODE_X) takeScreenshot = true; break; + case SDL_KEYDOWN: { + auto key = e.key.keysym.scancode; + if (key == SDL_SCANCODE_F4) { + pRenderer->SwitchDepthBuffer(); + } + if (key == SDL_SCANCODE_F5) { + pRenderer->ToggleRotation(); + } + if (key == SDL_SCANCODE_F6) { + pRenderer->ToggleNormals(); + } + if (key == SDL_SCANCODE_F7) { + pRenderer->CycleRenderingMode(); + } + } } } @@ -82,15 +94,13 @@ int main(int argc, char* args[]) //--------- Timer --------- pTimer->Update(); printTimer += pTimer->GetElapsed(); - if (printTimer >= 1.f) - { + if (printTimer >= 1.f) { printTimer = 0.f; std::cout << "dFPS: " << pTimer->GetdFPS() << std::endl; } //Save screenshot after full render - if (takeScreenshot) - { + if (takeScreenshot) { if (!pRenderer->SaveBufferToImage()) std::cout << "Screenshot saved!" << std::endl; else