diff --git a/CPU-Software-Renderer/CONVENTIONS.md b/CPU-Software-Renderer/CONVENTIONS.md
new file mode 100644
index 0000000..f4f26ab
--- /dev/null
+++ b/CPU-Software-Renderer/CONVENTIONS.md
@@ -0,0 +1,110 @@
+# CPU 软件渲染器项目约定
+
+本文档用于记录当前项目已经采用的坐标系、矩阵、相机和屏幕空间约定,避免后续开发时在符号、方向和乘法顺序上产生混乱。
+
+## 1. 通用约定
+
+- 项目使用右手坐标系。
+- `Vector3::cross(a, b)` 遵循右手定则。
+- 除非特别说明,向量按列向量理解。
+- 变换写法按 `M * v` 解释。
+
+## 2. 世界空间与局部空间
+
+世界空间与物体局部空间目前使用同一套方向命名:
+
+- `+X`:右
+- `+Y`:上
+- `+Z`:前
+
+`Scene::Transform` 也遵循这套约定:
+
+- `get_right()`:旋转后的局部右方向
+- `get_up()`:旋转后的局部上方向
+- `get_forward()`:旋转后的局部前方向
+- `get_left()`、`get_down()`、`get_back()`:分别为对应反方向
+
+也就是说,`Transform` 中的 `forward` 明确定义为 `+Z`。
+
+## 3. 旋转约定
+
+- 欧拉角使用弧度制。
+- `rotation.x`:绕 `X` 轴旋转
+- `rotation.y`:绕 `Y` 轴旋转
+- `rotation.z`:绕 `Z` 轴旋转
+- 组合旋转顺序为 `Rz * Ry * Rx`
+
+当前项目里,方向向量的旋转方式是先构造旋转矩阵,再去变换基础方向轴。
+
+## 4. 矩阵约定
+
+`Math::Matrix4x4` 当前采用以下规则:
+
+- 语义上按列向量使用,写法为 `M * v`
+- 元素访问方式为 `matrix[row][col]`
+- `data()` 暴露的是连续的 row-major 内存
+- 平移分量存放在最后一列
+
+因此,常见的组合变换阅读顺序是从右往左:
+
+- `worldPosition = Translation * Rotation * Scale * localPosition`
+
+## 5. 相机与视图空间
+
+相机目前由 `Scene::Camera::transform` 驱动。
+
+相机自身局部方向定义为:
+
+- 相机右方向:`transform.get_right()`
+- 相机上方向:`transform.get_up()`
+- 相机前方向:`transform.get_forward()`
+
+但进入视图空间后,当前项目采用的是常见的相机空间约定:
+
+- 位于相机前方的点,其 view-space `z` 为负值
+- 视图矩阵第三行存的是相机 backward,而不是 forward
+
+这和当前透视投影矩阵实现是一致的,因为那里对应的是 `clip.w = -viewZ` 这套约定。
+
+## 6. 投影与 NDC
+
+当前透视投影相关约定如下:
+
+- 规范化设备坐标(NDC)可见范围为 `[-1, 1]`
+- `x`、`y`、`z` 都会在映射到 viewport 之前做范围检查
+- 当前测试代码里,如果点超出 NDC,可能会直接判为不可见,而不是继续做线段裁剪
+
+这意味着:
+
+- 当前 demo 还没有实现完整的视锥裁剪
+- 如果一条线段只有一部分还在屏幕内,但端点已经越出 NDC,整条线仍可能被直接丢弃
+
+## 7. 屏幕与像素坐标
+
+屏幕/像素坐标使用左上角为原点的约定:
+
+- 原点在左上角
+- `x` 向右增大
+- `y` 向下增大
+
+`Core::FrameBuffer` 当前行为如下:
+
+- 有效像素范围:`x in [0, width)`
+- 有效像素范围:`y in [0, height)`
+- 越界写入会被直接忽略
+- 像素缓冲按 row-major 排列
+- 内存中的第一行对应 `y = 0`
+
+`Camera::get_viewport_matrix()` 里也对 `Y` 做了翻转,因此 NDC 的“向上”为正,最终会映射成屏幕坐标“向下”为正。
+
+## 8. Demo 中的可见性规则
+
+`main.cpp` 里的旋转立方体示例,目前采用的是一个简化版隐藏线规则:
+
+- 先在 view space 中判断哪些面朝向相机
+- 再把这些可见面的边标记为可绘制
+- 最后只绘制被标记出来的边
+
+这只是一个 demo 级别的近似方案,不是完整的隐藏线消除算法。
+
+后续如果项目要加入更严格的裁剪、剔除或可见性规则,应当以代码实现为准,并同步更新本文档。
diff --git a/CPU-Software-Renderer/CPU-Software-Renderer.vcxproj b/CPU-Software-Renderer/CPU-Software-Renderer.vcxproj
index 83aed61..32da52f 100644
--- a/CPU-Software-Renderer/CPU-Software-Renderer.vcxproj
+++ b/CPU-Software-Renderer/CPU-Software-Renderer.vcxproj
@@ -157,7 +157,7 @@
-
+
diff --git a/CPU-Software-Renderer/CPU-Software-Renderer.vcxproj.filters b/CPU-Software-Renderer/CPU-Software-Renderer.vcxproj.filters
index c4e8875..3614f5c 100644
--- a/CPU-Software-Renderer/CPU-Software-Renderer.vcxproj.filters
+++ b/CPU-Software-Renderer/CPU-Software-Renderer.vcxproj.filters
@@ -77,11 +77,11 @@
-
- 资源文件
-
+
+ 资源文件
+
diff --git a/CPU-Software-Renderer/Core/DepthBuffer.cpp b/CPU-Software-Renderer/Core/DepthBuffer.cpp
index e69de29..63ea6bd 100644
--- a/CPU-Software-Renderer/Core/DepthBuffer.cpp
+++ b/CPU-Software-Renderer/Core/DepthBuffer.cpp
@@ -0,0 +1,23 @@
+#include "DepthBuffer.h"
+#include
+#include
+
+namespace Core
+{
+ void DepthBuffer::clear(const uint8_t depth)
+ {
+ std::fill(buffer.begin(), buffer.end(), depth);
+ }
+
+
+ void DepthBuffer::set_pixel(const int32_t x, const int32_t y, const uint8_t depth)
+ {
+ if (x < 0 || x >= width || y < 0 || y >= height)
+ {
+ return;
+ }
+ // Row-major layout with y = 0 on the first row, matching a top-left screen origin.
+ size_t index = static_cast(y) * width + x;
+ buffer.at(index) = depth;
+ }
+}
diff --git a/CPU-Software-Renderer/Core/DepthBuffer.h b/CPU-Software-Renderer/Core/DepthBuffer.h
index 4e113e6..ca508d5 100644
--- a/CPU-Software-Renderer/Core/DepthBuffer.h
+++ b/CPU-Software-Renderer/Core/DepthBuffer.h
@@ -1,7 +1,36 @@
-#pragma once
-
-namespace Core
-{
- class DepthBuffer
- {};
-}
\ No newline at end of file
+#pragma once
+#include
+#include
+#include "Color.h"
+#include "Vector2.h"
+
+namespace Core
+{
+ class DepthBuffer
+ {
+ private:
+ int32_t width;
+ int32_t height;
+ std::vector buffer;
+
+ public:
+ int32_t get_width() const { return width; }
+
+ int32_t get_height() const { return height; }
+
+ size_t total_pixels() const { return buffer.size(); }
+
+ void* get_buffer() const { return (void*)buffer.data(); }
+
+ DepthBuffer(int32_t width, int32_t height) :width(width), height(height), buffer(std::vector(width* height, 0)) {}
+
+ void clear(const uint8_t depth);
+
+ void set_pixel(const Math::Vector2Int position, const uint8_t depth)
+ {
+ set_pixel(position.x, position.y, depth);
+ }
+
+ void set_pixel(const int32_t x, const int32_t y, const uint8_t depth);
+ };
+};
diff --git a/CPU-Software-Renderer/Core/FrameBuffer.cpp b/CPU-Software-Renderer/Core/FrameBuffer.cpp
index 9792785..a7e85f1 100644
--- a/CPU-Software-Renderer/Core/FrameBuffer.cpp
+++ b/CPU-Software-Renderer/Core/FrameBuffer.cpp
@@ -1,8 +1,6 @@
#include "FrameBuffer.h"
#include
-#include
#include
-using namespace Math;
namespace Core
{
diff --git a/CPU-Software-Renderer/Rasterizer/TriangleRasterizer.cpp b/CPU-Software-Renderer/Rasterizer/TriangleRasterizer.cpp
index 294704f..65bea00 100644
--- a/CPU-Software-Renderer/Rasterizer/TriangleRasterizer.cpp
+++ b/CPU-Software-Renderer/Rasterizer/TriangleRasterizer.cpp
@@ -1,31 +1,31 @@
-#include "TriangleRasterizer.h"
-#include
-#include
-#include "BoundingBox.h"
-#include
-#include
-#include
-
-namespace Rasterizer
-{
- void TriangleRasterizer::DrawTriangle2D(const RenderData::Triangle2D triangle, const RenderData::Color color)
- {
- auto boundingBox = triangle.get_boundingBox();
-
- int32_t minX = std::max(0, boundingBox.min.x);
- int32_t maxX = std::min(frameBuffer.get_width() - 1, boundingBox.max.x);
- int32_t minY = std::max(0, boundingBox.min.y);
- int32_t maxY = std::min(frameBuffer.get_height() - 1, boundingBox.max.y);
-
+#include "TriangleRasterizer.h"
+#include
+#include
+#include "BoundingBox.h"
+#include
+#include
+#include
+
+namespace Rasterizer
+{
+ void TriangleRasterizer::DrawTriangle2D(const RenderData::Triangle& triangle, const RenderData::Color color)
+ {
+ auto boundingBox = triangle.get_boundingBox();
+
+ int32_t minX = std::max(0, boundingBox.min.x);
+ int32_t maxX = std::min(frameBuffer.get_width() - 1, boundingBox.max.x);
+ int32_t minY = std::max(0, boundingBox.min.y);
+ int32_t maxY = std::min(frameBuffer.get_height() - 1, boundingBox.max.y);
+
for (int x = minX; x <= maxX; x++)
{
for (int y = minY; y <= maxY; y++)
{
- if (triangle.ContainsPixel(Math::Vector2(x, y)))
+ if (triangle.ContainsPixel(Math::Vector2Int(x, y)))
{
frameBuffer.set_pixel(Math::Vector2Int(x, y), color.to_rgba());
}
}
}
- }
-}
\ No newline at end of file
+ }
+}
diff --git a/CPU-Software-Renderer/Rasterizer/TriangleRasterizer.h b/CPU-Software-Renderer/Rasterizer/TriangleRasterizer.h
index bd51c20..9863337 100644
--- a/CPU-Software-Renderer/Rasterizer/TriangleRasterizer.h
+++ b/CPU-Software-Renderer/Rasterizer/TriangleRasterizer.h
@@ -1,19 +1,19 @@
-#pragma once
-#include "Triangle.h"
-#include "Color.h"
-#include "FrameBuffer.h"
-
-namespace Rasterizer
-{
- class TriangleRasterizer
- {
- private:
- Core::FrameBuffer& frameBuffer;
-
-
- public:
- explicit TriangleRasterizer(Core::FrameBuffer& frameBuffer) :frameBuffer(frameBuffer) {};
-
- void DrawTriangle2D(const RenderData::Triangle2D triangle, const RenderData::Color color);
- };
+#pragma once
+#include "Triangle.h"
+#include "Color.h"
+#include "FrameBuffer.h"
+
+namespace Rasterizer
+{
+ class TriangleRasterizer
+ {
+ private:
+ Core::FrameBuffer& frameBuffer;
+
+
+ public:
+ explicit TriangleRasterizer(Core::FrameBuffer& frameBuffer) :frameBuffer(frameBuffer) {};
+
+ void DrawTriangle2D(const RenderData::Triangle& triangle, const RenderData::Color color);
+ };
}
\ No newline at end of file
diff --git a/CPU-Software-Renderer/RenderData/Triangle.h b/CPU-Software-Renderer/RenderData/Triangle.h
index dcf359f..0edab97 100644
--- a/CPU-Software-Renderer/RenderData/Triangle.h
+++ b/CPU-Software-Renderer/RenderData/Triangle.h
@@ -1,56 +1,59 @@
-#pragma once
-#include "Vector2.h"
-#include "BoundingBox.h"
-#include
-#include
-#include
-
-namespace RenderData
-{
- struct Triangle2D
- {
- Math::Vector2 v0;
- Math::Vector2 v1;
- Math::Vector2 v2;
- Triangle2D() : v0(), v1(), v2() {}
- Triangle2D(const Math::Vector2 a, const Math::Vector2 b, const Math::Vector2 c) : v0(a), v1(b), v2(c) {}
-
- BoundingBox2D get_boundingBox() const
- {
- using namespace Math;
-
- int32_t minX = static_cast(std::floor(std::min({ v0.x, v1.x, v2.x })));
- int32_t maxX = static_cast(std::ceil(std::max({ v0.x, v1.x, v2.x })));
- int32_t minY = static_cast(std::floor(std::min({ v0.y, v1.y, v2.y })));
- int32_t maxY = static_cast(std::ceil(std::max({ v0.y, v1.y, v2.y })));
-
- Vector2Int min(minX, minY);
- Vector2Int max(maxX, maxY);
-
- return BoundingBox2D(min, max);
- }
-
- bool ContainsPixel(const Math::Vector2 point) const
- {
- using namespace Math;
-
- auto cross = [](const Vector2& p1, const Vector2& p2, const Vector2& p3) -> float
- {
- const float x1 = p2.x - p1.x;
- const float y1 = p2.y - p1.y;
- const float x2 = p3.x - p1.x;
- const float y2 = p3.y - p1.y;
- return x1 * y2 - y1 * x2;
- };
-
- const float c0 = cross(v0, v1, point);
- const float c1 = cross(v1, v2, point);
- const float c2 = cross(v2, v0, point);
-
- const bool hasNeg = (c0 < 0) || (c1 < 0) || (c2 < 0);
- const bool hasPos = (c0 > 0) || (c1 > 0) || (c2 > 0);
-
- return !(hasNeg && hasPos);
- }
- };
+#pragma once
+#include "Vector2.h"
+#include "BoundingBox.h"
+#include "Vertex.h"
+#include
+#include
+#include
+#include
+
+namespace RenderData
+{
+ struct Triangle
+ {
+ Scene::Vertex v0;
+ Scene::Vertex v1;
+ Scene::Vertex v2;
+ Triangle() : v0(), v1(), v2() {}
+ Triangle(const Scene::Vertex a, const Scene::Vertex b, const Scene::Vertex c) : v0(a), v1(b), v2(c) {}
+
+ BoundingBox2D get_boundingBox() const
+ {
+ using namespace Math;
+
+ int32_t minX = static_cast(std::floor(std::min({ v0.position.x, v1.position.x, v2.position.x })));
+ int32_t maxX = static_cast(std::ceil(std::max({ v0.position.x, v1.position.x, v2.position.x })));
+ int32_t minY = static_cast(std::floor(std::min({ v0.position.y, v1.position.y, v2.position.y })));
+ int32_t maxY = static_cast(std::ceil(std::max({ v0.position.y, v1.position.y, v2.position.y })));
+
+ Vector2Int min(minX, minY);
+ Vector2Int max(maxX, maxY);
+
+ return BoundingBox2D(min, max);
+ }
+
+ bool ContainsPixel(const Math::Vector2Int point) const
+ {
+ using namespace Scene;
+
+ auto cross = [](const Vertex& v1, const Vertex& v2, const Math::Vector2Int& point) -> float
+ {
+ Scene::Vertex v3(Math::Vector3(point.x, point.y, 0));
+ const float x1 = v2.position.x - v1.position.x;
+ const float y1 = v2.position.y - v1.position.y;
+ const float x2 = v3.position.x - v1.position.x;
+ const float y2 = v3.position.y - v1.position.y;
+ return x1 * y2 - y1 * x2;
+ };
+
+ const float c0 = cross(v0, v1, point);
+ const float c1 = cross(v1, v2, point);
+ const float c2 = cross(v2, v0, point);
+
+ const bool hasNeg = (c0 < 0) || (c1 < 0) || (c2 < 0);
+ const bool hasPos = (c0 > 0) || (c1 > 0) || (c2 > 0);
+
+ return !(hasNeg && hasPos);
+ }
+ };
}
\ No newline at end of file
diff --git a/CPU-Software-Renderer/main.cpp b/CPU-Software-Renderer/main.cpp
index 7e423d5..5c3dbd3 100644
--- a/CPU-Software-Renderer/main.cpp
+++ b/CPU-Software-Renderer/main.cpp
@@ -14,11 +14,15 @@
#include "Color.h"
#include "FrameBuffer.h"
#include "Rasterizer.h"
+#include "TriangleRasterizer.h"
+#include "Triangle.h"
#include "Camera.h"
#include "SDL_events.h"
#include "SDL_keycode.h"
#include "SDL_timer.h"
#include
+#include
+#include "Vertex.h"
const uint32_t SDL_INIT_FLAGS = SDL_INIT_VIDEO;
const int32_t width = 800;
@@ -117,14 +121,24 @@ static bool EnsureTexture()
struct ProjectedVertex
{
- Math::Vector2Int screen;
+ Math::Vector3 screen;
bool visible = false;
};
struct CubeFace
{
std::array vertices;
- std::array edges;
+};
+
+struct CubeTriangle
+{
+ std::array vertices;
+};
+
+struct TriangleDrawCommand
+{
+ RenderData::Triangle triangle;
+ float averageViewSpaceZ = 0.0f;
};
static ProjectedVertex ProjectToScreen(
@@ -151,10 +165,7 @@ static ProjectedVertex ProjectToScreen(
}
const Vector4 screen = viewport * Vector4(ndcX, ndcY, ndcZ, 1.0f);
- const float screenX = screen.x;
- const float screenY = screen.y;
-
- return { Vector2(screenX, screenY).to_vector2Int(), true };
+ return { Math::Vector3(screen.x, screen.y, screen.z), true };
}
static bool IsFaceVisible(const CubeFace& face, const std::array& viewSpaceVertices)
@@ -174,6 +185,19 @@ static bool IsFaceVisible(const CubeFace& face, const std::array 0.0f;
}
+static bool IsTriangleVisible(const CubeTriangle& triangle, const std::array& viewSpaceVertices)
+{
+ using namespace Math;
+
+ const Vector3& v0 = viewSpaceVertices[triangle.vertices[0]];
+ const Vector3& v1 = viewSpaceVertices[triangle.vertices[1]];
+ const Vector3& v2 = viewSpaceVertices[triangle.vertices[2]];
+ const Vector3 faceNormal = (v1 - v0).cross(v2 - v0);
+ const Vector3 faceCenter = (v0 + v1 + v2) / 3.0f;
+
+ return faceNormal.dot(faceCenter) > 0.0f;
+}
+
int main(int argc, char* argv[])
{
if (!EnsureSDLInitialized()) return -1;
@@ -186,6 +210,7 @@ int main(int argc, char* argv[])
Core::FrameBuffer frameBuffer(width, height);
Rasterizer::Rasterizer rasterizer(frameBuffer);
+ Rasterizer::TriangleRasterizer triangleRasterizer(frameBuffer);
Scene::Camera camera;
camera.transform.position = Math::Vector3(0.0f, 0.0f, 3.0f);
@@ -202,18 +227,21 @@ int main(int argc, char* argv[])
Math::Vector3(-0.5f, 0.5f, 0.5f)
};
- const std::array, 12> cubeEdges = {
- std::pair(0, 1), std::pair(1, 2), std::pair(2, 3), std::pair(3, 0),
- std::pair(4, 5), std::pair(5, 6), std::pair(6, 7), std::pair(7, 4),
- std::pair(0, 4), std::pair(1, 5), std::pair(2, 6), std::pair(3, 7)
- };
const std::array cubeFaces = {
- CubeFace{ { 0, 3, 2, 1 }, { 0, 1, 2, 3 } },
- CubeFace{ { 4, 5, 6, 7 }, { 4, 5, 6, 7 } },
- CubeFace{ { 0, 4, 7, 3 }, { 8, 7, 11, 3 } },
- CubeFace{ { 1, 2, 6, 5 }, { 1, 10, 5, 9 } },
- CubeFace{ { 0, 1, 5, 4 }, { 0, 9, 4, 8 } },
- CubeFace{ { 3, 7, 6, 2 }, { 11, 6, 10, 2 } }
+ CubeFace{ { 0, 3, 2, 1 } },
+ CubeFace{ { 4, 5, 6, 7 } },
+ CubeFace{ { 0, 4, 7, 3 } },
+ CubeFace{ { 1, 2, 6, 5 } },
+ CubeFace{ { 0, 1, 5, 4 } },
+ CubeFace{ { 3, 7, 6, 2 } }
+ };
+ const std::array cubeTriangles = {
+ CubeTriangle{ { 0, 3, 2 } }, CubeTriangle{ { 0, 2, 1 } },
+ CubeTriangle{ { 4, 5, 6 } }, CubeTriangle{ { 4, 6, 7 } },
+ CubeTriangle{ { 0, 4, 7 } }, CubeTriangle{ { 0, 7, 3 } },
+ CubeTriangle{ { 1, 2, 6 } }, CubeTriangle{ { 1, 6, 5 } },
+ CubeTriangle{ { 0, 1, 5 } }, CubeTriangle{ { 0, 5, 4 } },
+ CubeTriangle{ { 3, 7, 6 } }, CubeTriangle{ { 3, 6, 2 } }
};
const RenderData::Color clearColor(18, 18, 24, 255);
@@ -257,36 +285,80 @@ int main(int argc, char* argv[])
projectedVertices[i] = ProjectToScreen(cubeVertices[i], mvp, viewport);
}
- std::array visibleEdges = {};
- for (const CubeFace& face : cubeFaces)
+ std::array visibleFaces = {};
+ for (size_t faceIndex = 0; faceIndex < cubeFaces.size(); ++faceIndex)
{
- if (!IsFaceVisible(face, viewSpaceVertices))
- {
- continue;
- }
-
- for (const int edgeIndex : face.edges)
- {
- visibleEdges[edgeIndex] = true;
- }
+ visibleFaces[faceIndex] = IsFaceVisible(cubeFaces[faceIndex], viewSpaceVertices);
}
- for (size_t edgeIndex = 0; edgeIndex < cubeEdges.size(); ++edgeIndex)
+ std::array drawCommands;
+ size_t drawCommandCount = 0;
+ for (const CubeTriangle& cubeTriangle : cubeTriangles)
{
- if (!visibleEdges[edgeIndex])
+ if (!IsTriangleVisible(cubeTriangle, viewSpaceVertices))
{
continue;
}
- const auto& edge = cubeEdges[edgeIndex];
- const ProjectedVertex& start = projectedVertices[edge.first];
- const ProjectedVertex& end = projectedVertices[edge.second];
- if (!start.visible || !end.visible)
+ const ProjectedVertex& v0 = projectedVertices[cubeTriangle.vertices[0]];
+ const ProjectedVertex& v1 = projectedVertices[cubeTriangle.vertices[1]];
+ const ProjectedVertex& v2 = projectedVertices[cubeTriangle.vertices[2]];
+ if (!v0.visible || !v1.visible || !v2.visible)
{
continue;
}
- rasterizer.DrawLine(start.screen, end.screen, cubeColor);
+ const Math::Vector3& viewV0 = viewSpaceVertices[cubeTriangle.vertices[0]];
+ const Math::Vector3& viewV1 = viewSpaceVertices[cubeTriangle.vertices[1]];
+ const Math::Vector3& viewV2 = viewSpaceVertices[cubeTriangle.vertices[2]];
+
+ drawCommands[drawCommandCount++] = TriangleDrawCommand{
+ RenderData::Triangle(
+ Scene::Vertex(v0.screen),
+ Scene::Vertex(v1.screen),
+ Scene::Vertex(v2.screen)
+ ),
+ (viewV0.z + viewV1.z + viewV2.z) / 3.0f
+ };
+ }
+
+ std::sort(
+ drawCommands.begin(),
+ drawCommands.begin() + drawCommandCount,
+ [](const TriangleDrawCommand& a, const TriangleDrawCommand& b)
+ {
+ return a.averageViewSpaceZ < b.averageViewSpaceZ;
+ });
+
+ for (size_t i = 0; i < drawCommandCount; ++i)
+ {
+ triangleRasterizer.DrawTriangle2D(drawCommands[i].triangle, cubeColor);
+ }
+
+ for (size_t faceIndex = 0; faceIndex < cubeFaces.size(); ++faceIndex)
+ {
+ if (!visibleFaces[faceIndex])
+ {
+ continue;
+ }
+
+ const CubeFace& face = cubeFaces[faceIndex];
+ for (size_t edgeOffset = 0; edgeOffset < face.vertices.size(); ++edgeOffset)
+ {
+ const int startIndex = face.vertices[edgeOffset];
+ const int endIndex = face.vertices[(edgeOffset + 1) % face.vertices.size()];
+ const ProjectedVertex& start = projectedVertices[startIndex];
+ const ProjectedVertex& end = projectedVertices[endIndex];
+ if (!start.visible || !end.visible)
+ {
+ continue;
+ }
+
+ rasterizer.DrawLine(
+ Math::Vector2(start.screen.x, start.screen.y).to_vector2Int(),
+ Math::Vector2(end.screen.x, end.screen.y).to_vector2Int(),
+ clearColor);
+ }
}
SDL_UpdateTexture(texture, nullptr, frameBuffer.get_buffer(), width * sizeof(uint32_t));