diff --git a/project/CMakeLists.txt b/project/CMakeLists.txt index a8426ae..1a4bbbb 100644 --- a/project/CMakeLists.txt +++ b/project/CMakeLists.txt @@ -8,6 +8,7 @@ set(SOURCES "src/Vector2.cpp" "src/Vector3.cpp" "src/Vector4.cpp" + "src/HitTest.cpp" ) set(IMGUI_SOURCES diff --git a/project/src/ColorRGB.h b/project/src/ColorRGB.h index 50b447f..be4a15c 100644 --- a/project/src/ColorRGB.h +++ b/project/src/ColorRGB.h @@ -3,6 +3,7 @@ namespace dae { + struct ColorRGB { float r{}; diff --git a/project/src/DataTypes.h b/project/src/DataTypes.h index c144d01..be6d5a2 100644 --- a/project/src/DataTypes.h +++ b/project/src/DataTypes.h @@ -1,4 +1,6 @@ #pragma once + +#include #include "Maths.h" #include "vector" @@ -8,7 +10,7 @@ namespace dae { Vector3 position{}; ColorRGB color{colors::White}; - //Vector2 uv{}; //W2 + Vector2 uv{}; //W2 //Vector3 normal{}; //W4 //Vector3 tangent{}; //W4 //Vector3 viewDirection{}; //W4 @@ -18,7 +20,7 @@ namespace dae { Vector4 position{}; ColorRGB color{ colors::White }; - //Vector2 uv{}; + Vector2 uv{}; //Vector3 normal{}; //Vector3 tangent{}; //Vector3 viewDirection{}; diff --git a/project/src/HitTest.cpp b/project/src/HitTest.cpp new file mode 100644 index 0000000..ea42f92 --- /dev/null +++ b/project/src/HitTest.cpp @@ -0,0 +1,78 @@ +// +// Created by Bram on 10/11/2024. +// + +#include +#include "HitTest.h" + +float min(float a, float b, float c) { + return std::min(a, std::min(b, c)); +} + +float max(float a, float b, float c) { + return std::max(a, std::max(b, 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); +} + + +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); + + float minY = min(vertices[0].position.y, vertices[1].position.y, vertices[2].position.y); + float maxY = max(vertices[0].position.y, vertices[1].position.y, vertices[2].position.y); + + if (pos.x < minX || pos.x > maxX || pos.y < minY || pos.y > maxY) { + return HitTestSample{ColorRGB{1, 0, 0}, 0}; + } + + // first edge + double w0{ CrossZ(vertices[1].position, vertices[2].position, pos) }; + const bool inEdge0 = w0 <= 0; + + double w1{ CrossZ(vertices[2].position, vertices[0].position, pos) }; + const bool inEdge1 = w1 <= 0; + + double w2{ CrossZ(vertices[0].position, vertices[1].position, pos) }; + const bool inEdge2 = w2 <= 0; + + if(!inEdge0 || !inEdge1 || !inEdge2){ + return {ColorRGB{0, 0, 1}, 0}; + } + + const double totalWeight{ w0 + w1 + w2 }; + w0 /= totalWeight; + w1 /= totalWeight; + w2 /= totalWeight; + + + assert(std::abs(w0 + w1 + w2 - 1) < 0.0001); + + float v0z = vertices[0].position.z; + float v1z = vertices[1].position.z; + float v2z = vertices[2].position.z; + + float depth = w0 * v0z + w1 * v1z + w2 * v2z; + + if(depth < 0 || depth > 1) { + return HitTestSample{ColorRGB{0, 0, 0}, 0}; + } + + Vector4 color{}; + + color += Vector4(float(w0) * Vector3(vertices[0].color.r, vertices[0].color.g, vertices[0].color.b) , 0); + color += Vector4(float(w1) * Vector3(vertices[1].color.r, vertices[1].color.g, vertices[1].color.b) , 0); + color += Vector4(float(w2) * Vector3(vertices[2].color.r, vertices[2].color.g, vertices[2].color.b) , 0); + + + return HitTestSample{ColorRGB{ + static_cast(color.x), + static_cast(color.y), + static_cast(color.z) + }, depth}; +} diff --git a/project/src/HitTest.h b/project/src/HitTest.h new file mode 100644 index 0000000..a1681f7 --- /dev/null +++ b/project/src/HitTest.h @@ -0,0 +1,22 @@ +// +// Created by Bram on 10/11/2024. +// + +#ifndef GP1_RASTERIZER_HITTEST_H +#define GP1_RASTERIZER_HITTEST_H + +#include "DataTypes.h" +#include "Maths.h" + +using namespace dae; + +namespace HitTest { + struct HitTestSample{ + ColorRGB color{0, 1, 1}; + float depth{0}; + }; + + HitTestSample Triangle(const Vector3& pos, const std::vector& vertices); +} + +#endif //GP1_RASTERIZER_HITTEST_H diff --git a/project/src/Renderer.cpp b/project/src/Renderer.cpp index e28d6aa..94d37e1 100644 --- a/project/src/Renderer.cpp +++ b/project/src/Renderer.cpp @@ -1,9 +1,11 @@ //External includes +#include #include "SDL_surface.h" //Project includes #include "Renderer.h" #include "Maths.h" +#include "HitTest.h" using namespace dae; @@ -24,6 +26,8 @@ Renderer::Renderer(SDL_Window *pWindow) : m_pWindow(pWindow) { //Initialize Camera m_Camera.Initialize(60.f, {.0f, .0f, -10.f}); m_aspectRatio = static_cast(m_Width) / static_cast(m_Height); + + m_pTexture = Texture::LoadFromFile("./Resources/uv_grid_2.png"); } Renderer::~Renderer() { @@ -50,91 +54,154 @@ void Renderer::Render() { constexpr int numVerticies = 3; std::vector verticiesScreenSpace{}; - VertexTransformationFunction(m_verticiesWorld, verticiesScreenSpace); - const int numTriangles{static_cast(m_verticiesWorld.size()) / numVerticies}; +// VertexTransformationFunction(m_verticiesWorld, verticiesScreenSpace); - for (int triangleIndex{}; triangleIndex < numTriangles; ++triangleIndex) { + for (const Mesh ¤tMesh: m_meshesWorldStrip) { + VertexTransformationFunction(currentMesh.vertices, verticiesScreenSpace); - const Vector3 &vertexPos0{verticiesScreenSpace[triangleIndex * numVerticies].position}; - const Vector3 &vertexPos1{verticiesScreenSpace[triangleIndex * numVerticies + 1].position}; - const Vector3 &vertexPos2{verticiesScreenSpace[triangleIndex * numVerticies + 2].position}; + int numTriangles{}; - const float minX{std::min(vertexPos0.x, std::min(vertexPos1.x, vertexPos2.x))}; - const float minY{std::min(vertexPos0.y, std::min(vertexPos1.y, vertexPos2.y))}; + switch (currentMesh.primitiveTopology) { + case PrimitiveTopology::TriangleList: + numTriangles = static_cast(currentMesh.indices.size()) / numVerticies; + break; + case PrimitiveTopology::TriangleStrip: + numTriangles = static_cast(currentMesh.indices.size()) - 2; + break; + } - const float maxX{std::max(vertexPos0.x, std::max(vertexPos1.x, vertexPos2.x))}; - const float maxY{std::max(vertexPos0.y, std::max(vertexPos1.y, vertexPos2.y))}; - const int indexOffset{triangleIndex * numVerticies}; + for (int triangleIndex{}; triangleIndex < numTriangles; ++triangleIndex) { - const Vector3 side1{ - verticiesScreenSpace[indexOffset + 1].position - verticiesScreenSpace[indexOffset].position}; - const Vector3 side2{ - verticiesScreenSpace[indexOffset + 2].position - verticiesScreenSpace[indexOffset].position}; + Vertex vertex0, vertex1, vertex2; - const float area{0.5f * Vector3::Cross(side1, side2).z}; + switch (currentMesh.primitiveTopology) { + case PrimitiveTopology::TriangleList: + vertex0 = verticiesScreenSpace[currentMesh.indices[triangleIndex * numVerticies + 0]]; + vertex1 = verticiesScreenSpace[currentMesh.indices[triangleIndex * numVerticies + 1]]; + vertex2 = verticiesScreenSpace[currentMesh.indices[triangleIndex * numVerticies + 2]]; + break; + case PrimitiveTopology::TriangleStrip: + vertex0 = verticiesScreenSpace[currentMesh.indices[triangleIndex + 0]]; + vertex1 = verticiesScreenSpace[currentMesh.indices[triangleIndex + 1]]; + vertex2 = verticiesScreenSpace[currentMesh.indices[triangleIndex + 2]]; - const int startX = std::max(0, static_cast(minX)); - const int endX = std::min(m_Width - 1, static_cast(maxX)); - - const int startY = std::max(0, static_cast(minY)); - const int endY = std::min(m_Height - 1, static_cast(maxY)); - - for (int px{startX}; px < endX; ++px) { - for (int py{startY}; py < endY; ++py) { - - Vector3 P{px + 0.5f, py + 0.5f, 1}; - - bool inTriangle{true}; - float currDepth{0.0f}; - ColorRGB pointColor{}; - - for (int vertexIndex{0}; vertexIndex < numVerticies; ++vertexIndex) { - const Vertex &nextVert{verticiesScreenSpace[(vertexIndex + 1) % numVerticies + indexOffset]}; - const Vector3 ¤tVecPos{verticiesScreenSpace[vertexIndex + indexOffset].position}; - - const Vector3 eVec{nextVert.position - currentVecPos}; - const Vector3 pVec{P - currentVecPos}; - - const float crossResult{Vector3::Cross(eVec, pVec).z}; - - if (crossResult < 0) { - inTriangle = false; - break; + if (triangleIndex % 2 == 1) { + std::swap(vertex1, vertex2); } - currDepth += - verticiesScreenSpace[((vertexIndex + 2) % numVerticies + indexOffset)].position.z * - ((crossResult * 0.5f) / area); - pointColor += verticiesScreenSpace[((vertexIndex + 2) % numVerticies + indexOffset)].color * - ((crossResult * 0.5f) / area); - } + if (vertex0.position == vertex1.position || + vertex0.position == vertex2.position || + vertex1.position == vertex2.position) { + continue; + } + break; + } - if (!inTriangle) { - 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))}; - const int depthBufferIndex{px + (py * m_Width)}; + const float maxX{std::max(vertex0.position.x, std::max(vertex1.position.x, vertex2.position.x))}; + const float maxY{std::max(vertex0.position.y, std::max(vertex1.position.y, vertex2.position.y))}; - if (m_pDepthBufferPixels[depthBufferIndex] >= currDepth) { - m_pDepthBufferPixels[depthBufferIndex] = currDepth; - finalColor = pointColor; - finalColor.MaxToOne(); + const Vector3 side1{vertex1.position - vertex0.position}; + const Vector3 side2{vertex2.position - vertex0.position}; + + const float totalArea{Vector3::Cross(side1, side2).z}; + + const int startX = std::max(0, static_cast(minX)) - 1; + const int endX = std::min(m_Width - 1, static_cast(maxX)) + 1; + + const int startY = std::max(0, static_cast(minY)) - 1; + const int endY = std::min(m_Height - 1, static_cast(maxY)) + 1; + + for (int px{startX}; px < endX; ++px) { + for (int py{startY}; py < endY; ++py) { + + if (px < 0 || px >= m_Width || py < 0 || py >= m_Height) { + //TEMP fix for out of bounds + //This is to remove triangles having an incorrect edge on their bounding box + continue; + } + + 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) { + continue; + } + + const int depthBufferIndex{px + (py * m_Width)}; + + float totalWeight{cross0.z + cross1.z + cross2.z}; + Vector3 weights{ + cross0.z / totalWeight, + cross1.z / totalWeight, + cross2.z / totalWeight, + }; + + const float currentDepth = + 1 / (weights.x / vertex0.position.z + + weights.y / vertex1.position.z + + weights.z / vertex2.position.z); + + 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) { + m_pDepthBufferPixels[depthBufferIndex] = currentDepth; + + //Update Color in Buffer + finalColor = m_pTexture->Sample(UvCoords); + finalColor.MaxToOne(); + + m_pBackBufferPixels[px + (py * m_Width)] = SDL_MapRGB(m_pBackBuffer->format, + static_cast(finalColor.r * 255), + static_cast(finalColor.g * 255), + static_cast(finalColor.b * 255)); + + } - m_pBackBufferPixels[px + (py * m_Width)] = SDL_MapRGB(m_pBackBuffer->format, - static_cast(finalColor.r * 255), - static_cast(finalColor.g * 255), - static_cast(finalColor.b * 255)); } } } } - //RENDER LOGIC + 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 SDL_UnlockSurface(m_pBackBuffer); - SDL_BlitSurface(m_pBackBuffer, nullptr, m_pFrontBuffer, nullptr); + SDL_BlitSurface(m_pBackBuffer, + nullptr, m_pFrontBuffer, nullptr); SDL_UpdateWindowSurface(m_pWindow); } @@ -148,7 +215,7 @@ void Renderer::VertexTransformationFunction(const std::vector &vertices_ 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}); + vertices_out.push_back(Vertex{vertPos, vert.color, vert.uv}); } } diff --git a/project/src/Renderer.h b/project/src/Renderer.h index d7dcd05..77b1d41 100644 --- a/project/src/Renderer.h +++ b/project/src/Renderer.h @@ -5,12 +5,13 @@ #include "Camera.h" #include "DataTypes.h" +#include "Maths.h" +#include "Texture.h" struct SDL_Window; struct SDL_Surface; namespace dae { - class Texture; struct Mesh; @@ -55,32 +56,34 @@ namespace dae { float m_aspectRatio{}; - 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_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}} +// }; std::vector m_meshesWorldList { Mesh { { - Vertex{ {-3, 3, -2 }, { 1, 1, 1 }, }, - Vertex{ { 0, 3, -2 }, { 1, 1, 1 }, }, - Vertex{ { 3, 3, -2 }, { 1, 1, 1 }, }, - Vertex{ {-3, 0, -2 }, { 1, 1, 1 }, }, - Vertex{ { 0, 0, -2 }, { 1, 1, 1 }, }, - Vertex{ { 3, 0, -2 }, { 1, 1, 1 }, }, - Vertex{ {-3, -3, -2 }, { 1, 1, 1 }, }, - Vertex{ { 0, -3, -2 }, { 1, 1, 1 }, }, - Vertex{ { 3, -3, -2 }, { 1, 1, 1 }, } + 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, @@ -95,27 +98,41 @@ namespace dae { { Mesh { - { - Vertex{ {-3, 3, -2 }, { 1, 1, 1 }, }, - Vertex{ { 0, 3, -2 }, { 1, 1, 1 }, }, - Vertex{ { 3, 3, -2 }, { 1, 1, 1 }, }, - Vertex{ {-3, 0, -2 }, { 1, 1, 1 }, }, - Vertex{ { 0, 0, -2 }, { 1, 1, 1 }, }, - Vertex{ { 3, 0, -2 }, { 1, 1, 1 }, }, - Vertex{ {-3, -3, -2 }, { 1, 1, 1 }, }, - Vertex{ { 0, -3, -2 }, { 1, 1, 1 }, }, - Vertex{ { 3, -3, -2 }, { 1, 1, 1 }, } + 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 + 6, 3, 7, 4, 8, 5, }, - PrimitiveTopology::TriangleStrip + PrimitiveTopology::TriangleStrip, } }; - - std::vector m_verticies_screenSpace{}; + //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{}; float* m_pDepthBufferPixels{}; diff --git a/project/src/Texture.cpp b/project/src/Texture.cpp index 4cb6b5e..51a89b9 100644 --- a/project/src/Texture.cpp +++ b/project/src/Texture.cpp @@ -1,6 +1,8 @@ #include "Texture.h" #include "Vector2.h" #include +#include +#include namespace dae { @@ -21,18 +23,33 @@ namespace dae Texture* Texture::LoadFromFile(const std::string& path) { - //TODO - //Load SDL_Surface using IMG_LOAD - //Create & Return a new Texture Object (using SDL_Surface) + SDL_Surface* pSurface = IMG_Load(path.c_str()); + if (pSurface == nullptr) + { + std::cerr << "Texture::LoadFromFile > Failed to load texture: " << path << " Error: " << IMG_GetError() << std::endl; + throw std::runtime_error("Failed to load texture"); + } - return nullptr; + if (pSurface->format->BytesPerPixel != 4) + { + std::cerr << "Texture::LoadFromFile > Texture is not in the right format: " << path << std::endl; + throw std::runtime_error("Texture is not in the right format"); + } + + return new Texture(pSurface); } - ColorRGB Texture::Sample(const Vector2& uv) const - { - //TODO - //Sample the correct texel for the given uv + ColorRGB Texture::Sample(const Vector2& uv) const + { + Uint8 red, green, blue; + Vector2 clamped = {std::clamp(uv.x, 0.0f, 1.0f), std::clamp(uv.y, 0.0f, 1.0f)}; + + SDL_GetRGB(m_pSurfacePixels[ + static_cast(clamped.x * static_cast(m_pSurface->w - 1)) + + static_cast(clamped.y * static_cast(m_pSurface->h - 1)) * (m_pSurface->w) + ], m_pSurface->format, &red, &green, &blue); + + return ColorRGB{static_cast(red)/255.0f, static_cast(green)/255.0f, static_cast(blue)/255.0f}; + } - return {}; - } } \ No newline at end of file