This commit is contained in:
2026-06-05 17:55:22 +08:00
commit 9db7e06aa1
172 changed files with 60847 additions and 0 deletions
View File
+7
View File
@@ -0,0 +1,7 @@
#pragma once
namespace Asset
{
class ObjLoader
{};
}
+158
View File
@@ -0,0 +1,158 @@
# 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. 三角形绕序与正面约定
当前项目将三角形正面统一约定为顺时针 `CW` 绕序。
这里的“顺时针”按当前渲染流程解释为:
- 三角形经过 view / projection / viewport 变换后
- 从屏幕上观察其顶点顺序时,正面三角形按顺时针排列
与这套约定配套的实现规则是:
- 背面剔除当前在 view space 中完成
- 法线方向使用 `faceNormal = (v1 - v0).cross(v2 - v0)` 计算
- 当前 demo 中,`faceNormal.dot(faceCenter) > 0` 被视为正面
这意味着:
- 所有手写或导入的三角形索引都必须保持一致绕序
- 如果未来改成逆时针 `CCW` 为正面,那么剔除判定符号也必须同步调整
- `main.cpp` 里的 `cubeTriangles` 和 `cubeFaces` 当前应继续保持与这套约定一致,不要单独翻转其中一部分
## 7. 投影与 NDC
当前透视投影相关约定如下:
- 规范化设备坐标(NDC)可见范围为 `[-1, 1]`
- `x`、`y`、`z` 都会在映射到 viewport 之前做范围检查
- 当前测试代码里,如果点超出 NDC,可能会直接判为不可见,而不是继续做线段裁剪
这意味着:
- 当前 demo 还没有实现完整的视锥裁剪
- 如果一条线段只有一部分还在屏幕内,但端点已经越出 NDC,整条线仍可能被直接丢弃
## 8. 屏幕与像素坐标
屏幕/像素坐标使用左上角为原点的约定:
- 原点在左上角
- `x` 向右增大
- `y` 向下增大
`Core::FrameBuffer` 当前行为如下:
- 有效像素范围:`x in [0, width)`
- 有效像素范围:`y in [0, height)`
- 越界写入会被直接忽略
- 像素缓冲按 row-major 排列
- 内存中的第一行对应 `y = 0`
`Camera::get_viewport_matrix()` 里也对 `Y` 做了翻转,因此 NDC 的“向上”为正,最终会映射成屏幕坐标“向下”为正。
## 9. 深度缓冲约定
当前项目已经接入 `Core::DepthBuffer`,并采用以下规则:
- `DepthBuffer` 存储类型为 `float`
- 每帧开始时必须调用 `depthBuffer->clear()`,默认清为 `INFINITY`
- 当前约定为“深度值越小,离相机越近”
- 深度测试通过后,必须同时更新 `DepthBuffer` 和 `FrameBuffer`
- `DepthBuffer` 只负责存储和读取深度,不负责决定颜色写入逻辑;是否写颜色由光栅化阶段决定
当前三角形光栅化里的深度流程为:
- 在屏幕空间遍历三角形包围盒
- 以像素中心 `x + 0.5, y + 0.5` 作为采样点
- 用屏幕空间 `x/y` 计算重心坐标
- 用同一组重心坐标判断点是否在三角形内,并插值顶点 `z`
- 若新深度更近,则写入 `DepthBuffer` 和 `FrameBuffer`
也就是说:
- 重心坐标的计算是二维问题,只使用屏幕空间 `x/y`
- 顶点 `z` 的插值使用这组重心权重完成
- 当前实现是屏幕空间线性插值,后续如果引入纹理、法线或更严格的属性插值,需要进一步考虑透视校正插值
## 10. Demo 中的可见性规则
`main.cpp` 里的旋转立方体示例,目前采用以下可见性与遮挡规则:
- 三角形是否参与光栅化,仍由投影合法性检查和背面剔除决定
- 三角形之间的遮挡,不再依赖按平均深度排序,而是由 `DepthBuffer` 逐像素决定
- 当前 demo 仍保留按面筛选后的轮廓线绘制,用于显示黑色边框
这意味着:
- `DepthBuffer` 不能替代投影合法性检查或背面剔除
- `DepthBuffer` 负责的是像素级遮挡,而不是顶点级或三角形级是否进入渲染流程
后续如果项目要加入更严格的裁剪、剔除、透视校正插值或隐藏线规则,应当以代码实现为准,并同步更新本文档。
+34
View File
@@ -0,0 +1,34 @@
#include "DepthBuffer.h"
#include <cstdint>
#include <algorithm>
#include <cmath>
namespace Core
{
void DepthBuffer::clear(const float depth)
{
std::fill(buffer.begin(), buffer.end(), depth);
}
float DepthBuffer::get_depth(const int32_t x, const int32_t y) const
{
if (x < 0 || x >= width || y < 0 || y >= height)
{
return INFINITY;
}
// Row-major layout with y = 0 on the first row, matching a top-left screen origin.
size_t index = static_cast<size_t>(y) * width + x;
return buffer.at(index);
}
void DepthBuffer::set_depth(const int32_t x, const int32_t y, const float 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<size_t>(y) * width + x;
buffer.at(index) = depth;
}
}
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include <cstdint>
#include <vector>
#include "Vector2.h"
#include <cmath>
namespace Core
{
class DepthBuffer
{
private:
int32_t width;
int32_t height;
std::vector<float> 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<float>(width* height, INFINITY)) {}
void clear(const float depth = INFINITY);
float get_depth(const Math::Vector2Int position) const { return get_depth(position.x, position.y); }
float get_depth(const int32_t x, const int32_t y) const;
void set_depth(const Math::Vector2Int position, const float depth) { set_depth(position.x, position.y, depth); }
void set_depth(const int32_t x, const int32_t y, const float depth);
};
};
+23
View File
@@ -0,0 +1,23 @@
#include "FrameBuffer.h"
#include <cstdint>
#include <algorithm>
namespace Core
{
void FrameBuffer::clear(const uint32_t color)
{
std::fill(buffer.begin(), buffer.end(), color);
}
void FrameBuffer::set_pixel(const int32_t x, const int32_t y, const uint32_t color)
{
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<size_t>(y) * width + x;
buffer.at(index) = color;
}
}
+46
View File
@@ -0,0 +1,46 @@
#pragma once
#include "Color.h"
#include "Vector2.h"
#include <cstdint>
#include <vector>
namespace Core
{
class FrameBuffer
{
private:
int32_t width;
int32_t height;
std::vector<uint32_t> 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(); }
FrameBuffer(int32_t width, int32_t height) :width(width), height(height), buffer(std::vector<uint32_t>(width * height, 0)) {}
void clear(const RenderData::Color& color)
{
clear(color.to_rgba());
}
void clear(const uint32_t color);
void set_pixel(const int32_t x, const int32_t y, const RenderData::Color& color)
{
set_pixel(Math::Vector2Int(x, y), color.to_rgba());
}
void set_pixel(const Math::Vector2Int position, const uint32_t color)
{
set_pixel(position.x, position.y, color);
}
void set_pixel(const int32_t x, const int32_t y, const uint32_t color);
};
}
View File
+7
View File
@@ -0,0 +1,7 @@
#pragma once
namespace Core
{
class Renderer
{};
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
class Cube
{
private:
public:
Cube() = default;
};
+109
View File
@@ -0,0 +1,109 @@
#pragma once
#include "Matrix4x4.h"
#include "Vector3.h"
#include <cmath>
#include "Vector2.h"
namespace Math
{
class MathUtil
{
public:
static Matrix4x4 get_translate_matrix(const float x, const float y, const float z) { return get_translate_matrix(Vector3(x, y, z)); }
static Matrix4x4 get_translate_matrix(const Vector3& translation)
{
return Matrix4x4(
1, 0, 0, translation.x,
0, 1, 0, translation.y,
0, 0, 1, translation.z,
0, 0, 0, 1
);
}
static Matrix4x4 get_scale_matrix(const float scale) { return get_scale_matrix(Vector3(scale, scale, scale)); }
static Matrix4x4 get_scale_matrix(const float x, const float y, const float z) { return get_scale_matrix(Vector3(x, y, z)); }
static Matrix4x4 get_scale_matrix(const Vector3& scale)
{
return Matrix4x4(
scale.x, 0, 0, 0,
0, scale.y, 0, 0,
0, 0, scale.z, 0,
0, 0, 0, 1
);
}
static Matrix4x4 get_rotation_matrix_x(const float radians)
{
const float cosValue = std::cos(radians);
const float sinValue = std::sin(radians);
return Matrix4x4(
1, 0, 0, 0,
0, cosValue, -sinValue, 0,
0, sinValue, cosValue, 0,
0, 0, 0, 1
);
}
static Matrix4x4 get_rotation_matrix_y(const float radians)
{
const float cosValue = std::cos(radians);
const float sinValue = std::sin(radians);
return Matrix4x4(
cosValue, 0, sinValue, 0,
0, 1, 0, 0,
-sinValue, 0, cosValue, 0,
0, 0, 0, 1
);
}
static Matrix4x4 get_rotation_matrix_z(const float radians)
{
const float cosValue = std::cos(radians);
const float sinValue = std::sin(radians);
return Matrix4x4(
cosValue, -sinValue, 0, 0,
sinValue, cosValue, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
);
}
static Matrix4x4 get_rotation_matrix(const Vector3& rotation)
{
return get_rotation_matrix_z(rotation.z) * get_rotation_matrix_y(rotation.y) * get_rotation_matrix_x(rotation.x);
}
static Matrix4x4 get_rodrigues_rotation_matrix(const Vector3& axis, const float radians)
{
const float cosValue = std::cos(radians);
const float sinValue = std::sin(radians);
const float oneMinusCos = 1.0f - cosValue;
const float powX = axis.x * axis.x;
const float powY = axis.y * axis.y;
const float powZ = axis.z * axis.z;
return Matrix4x4(
1 + 0 + oneMinusCos * (-powZ - powY), 0 + sinValue * (-axis.z) + oneMinusCos * axis.x * axis.y, 0 + sinValue * axis.z + oneMinusCos * axis.x * axis.z, 0,
0 + sinValue * axis.z + oneMinusCos * axis.x * axis.y, 1 + 0 + oneMinusCos * (-powZ - powX), 0 + sinValue * (-axis.x) + oneMinusCos * axis.y * axis.z, 0,
0 + sinValue * (-axis.y) + oneMinusCos * axis.x * axis.z, 0 + sinValue * axis.x + oneMinusCos * axis.y * axis.z, 1 + 0 + oneMinusCos * (-powY - powX), 0,
0, 0, 0, 1
);
}
static Vector3 cross(const Vector3& vec1, const Vector3& vec2)
{
return vec1.cross(vec2);
}
static Vector3 cross(const Vector2& vec1, const Vector2& vec2)
{
return vec1.cross(vec2);
}
};
}
+177
View File
@@ -0,0 +1,177 @@
#pragma once
#include <cmath>
#include "Vector3.h"
#include "Vector4.h"
namespace Math
{
class Matrix4x4
{
public:
// 约定:
// - 图形转换使用列向量语义
// - 图形变换按列向量语义做,写法是 M * v
// - 元素访问是 matrix[row][col]
// - data() 暴露的是 row-major 连续内存
// - 平移放在最后一列
Matrix4x4()
{
*this = Identity();
}
Matrix4x4(
float m00, float m01, float m02, float m03,
float m10, float m11, float m12, float m13,
float m20, float m21, float m22, float m23,
float m30, float m31, float m32, float m33)
{
values[0][0] = m00; values[0][1] = m01; values[0][2] = m02; values[0][3] = m03;
values[1][0] = m10; values[1][1] = m11; values[1][2] = m12; values[1][3] = m13;
values[2][0] = m20; values[2][1] = m21; values[2][2] = m22; values[2][3] = m23;
values[3][0] = m30; values[3][1] = m31; values[3][2] = m32; values[3][3] = m33;
}
static Matrix4x4 Zero()
{
return Matrix4x4(
0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f,
0.0f, 0.0f, 0.0f, 0.0f
);
}
static Matrix4x4 Identity()
{
return Matrix4x4(
1.0f, 0.0f, 0.0f, 0.0f,
0.0f, 1.0f, 0.0f, 0.0f,
0.0f, 0.0f, 1.0f, 0.0f,
0.0f, 0.0f, 0.0f, 1.0f
);
}
float* operator[](size_t row)
{
return values[row];
}
const float* operator[](size_t row) const
{
return values[row];
}
float* data()
{
return &values[0][0];
}
const float* data() const
{
return &values[0][0];
}
Matrix4x4 operator+ (const Matrix4x4& other) const
{
Matrix4x4 result = Zero();
for (size_t row = 0; row < 4; ++row)
{
for (size_t col = 0; col < 4; ++col)
{
result[row][col] = values[row][col] + other[row][col];
}
}
return result;
}
Matrix4x4 operator- (const Matrix4x4& other) const
{
Matrix4x4 result = Zero();
for (size_t row = 0; row < 4; ++row)
{
for (size_t col = 0; col < 4; ++col)
{
result[row][col] = values[row][col] - other[row][col];
}
}
return result;
}
Matrix4x4 operator* (float scalar) const
{
Matrix4x4 result = Zero();
for (size_t row = 0; row < 4; ++row)
{
for (size_t col = 0; col < 4; ++col)
{
result[row][col] = values[row][col] * scalar;
}
}
return result;
}
// 结果为 this x other(调用方 乘以 参数)。
Matrix4x4 operator* (const Matrix4x4& other) const
{
Matrix4x4 result = Zero();
for (size_t row = 0; row < 4; ++row)
{
for (size_t col = 0; col < 4; ++col)
{
for (size_t k = 0; k < 4; ++k)
{
result[row][col] += values[row][k] * other[k][col];
}
}
}
return result;
}
// 结果为 this x other(调用方 乘以 参数)。
Vector4 operator* (const Vector4& vector) const
{
return Vector4(
values[0][0] * vector.x + values[0][1] * vector.y + values[0][2] * vector.z + values[0][3] * vector.w,
values[1][0] * vector.x + values[1][1] * vector.y + values[1][2] * vector.z + values[1][3] * vector.w,
values[2][0] * vector.x + values[2][1] * vector.y + values[2][2] * vector.z + values[2][3] * vector.w,
values[3][0] * vector.x + values[3][1] * vector.y + values[3][2] * vector.z + values[3][3] * vector.w
);
}
Vector4 TransformPoint(const Vector3& point) const
{
return *this * Vector4::Point(point);
}
Vector4 TransformDirection(const Vector3& direction) const
{
return *this * Vector4::Direction(direction);
}
Matrix4x4 Transposed() const
{
Matrix4x4 result = Zero();
for (size_t row = 0; row < 4; ++row)
{
for (size_t col = 0; col < 4; ++col)
{
result[row][col] = values[col][row];
}
}
return result;
}
private:
float values[4][4] = {};
};
inline Matrix4x4 operator* (float scalar, const Matrix4x4& matrix)
{
return matrix * scalar;
}
}
+60
View File
@@ -0,0 +1,60 @@
#pragma once
#include <cstdint>
#include <utility>
#include <cmath>
#include "Vector3.h"
namespace Math
{
struct Vector2;
struct Vector2Int;
struct Vector2Int
{
int32_t x = 0;
int32_t y = 0;
Vector2Int() : x(0), y(0) {}
Vector2Int(int32_t x, int32_t y) : x(x), y(y) {}
/// <summary>
/// 交换当前 Vector2Int 对象与另一个 Vector2Int 对象的 x 和 y 的值
/// </summary>
/// <param name="other">要交换的对象</param>
static void swap(Vector2Int& vec1, Vector2Int& vec2)
{
std::swap(vec1.x, vec2.x);
std::swap(vec1.y, vec2.y);
}
};
struct Vector2
{
float x = 0.0f;
float y = 0.0f;
Vector2() : x(0), y(0) {}
Vector2(float x, float y) : x(x), y(y) {}
Vector2(const Vector2Int& other) :x(other.x), y(other.y) {}
/// <summary>
/// 交换当前 Vector2 对象与另一个 Vector2 对象的 x 和 y 的值
/// </summary>
/// <param name="other">要交换的对象</param>
static void swap(Vector2& vec1, Vector2& vec2)
{
std::swap(vec1.x, vec2.x);
std::swap(vec1.y, vec2.y);
}
Vector2Int to_vector2Int() const
{
return Vector2Int(static_cast<int32_t>(std::lround(x)), static_cast<int32_t>(std::lround(y)));
}
Vector3 cross(const Vector2& other) const
{
return Vector3(0, 0, this->x * other.y - this->y * other.x);
}
};
}
+85
View File
@@ -0,0 +1,85 @@
#pragma once
#include <cmath>
namespace Math
{
struct Vector3
{
float x = 0.0f;
float y = 0.0f;
float z = 0.0f;
Vector3() : x(0), y(0), z(0) {}
Vector3(float x, float y, float z) : x(x), y(y), z(z) {}
float sqrMagnitude() const
{
return x * x + y * y + z * z;
}
float magnitude() const
{
return std::sqrt(sqrMagnitude());
}
Vector3 normalized() const
{
const float length = magnitude();
if (length <= 0.0f)
{
return Vector3();
}
return *this / length;
}
float dot(const Vector3& other) const
{
return x * other.x + y * other.y + z * other.z;
}
// Returns this x other.
// Order matters:
// - a.cross(b) == -(b.cross(a))
// - the result follows the right-hand rule
// - cross product is not associative
Vector3 cross(const Vector3& other) const
{
return Vector3(
y * other.z - z * other.y,
z * other.x - x * other.z,
x * other.y - y * other.x
);
}
Vector3 operator+ (const Vector3& other) const
{
return Vector3(x + other.x, y + other.y, z + other.z);
}
Vector3 operator- (const Vector3& other) const
{
return Vector3(x - other.x, y - other.y, z - other.z);
}
Vector3 operator- () const
{
return Vector3(-x, -y, -z);
}
Vector3 operator* (float scalar) const
{
return Vector3(x * scalar, y * scalar, z * scalar);
}
Vector3 operator/ (float scalar) const
{
return Vector3(x / scalar, y / scalar, z / scalar);
}
};
inline Vector3 operator* (float scalar, const Vector3& vector)
{
return vector * scalar;
}
}
+83
View File
@@ -0,0 +1,83 @@
#pragma once
#include <cmath>
#include "Vector3.h"
namespace Math
{
struct Vector4
{
float x = 0.0f;
float y = 0.0f;
float z = 0.0f;
float w = 0.0f;
Vector4() : x(0), y(0), z(0), w(0) {}
Vector4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) {}
static Vector4 Point(const Vector3& point)
{
return Vector4(point.x, point.y, point.z, 1.0f);
}
static Vector4 Direction(const Vector3& direction)
{
return Vector4(direction.x, direction.y, direction.z, 0.0f);
}
Vector3 to_vector3() const
{
return Vector3(x, y, z);
}
float sqrMagnitude() const
{
return x * x + y * y + z * z + w * w;
}
float magnitude() const
{
return std::sqrt(sqrMagnitude());
}
Vector4 normalized() const
{
const float length = magnitude();
if (length <= 0.0f)
{
return Vector4();
}
return *this / length;
}
float dot(const Vector4& other) const
{
return x * other.x + y * other.y + z * other.z + w * other.w;
}
Vector4 operator+ (const Vector4& other) const
{
return Vector4(x + other.x, y + other.y, z + other.z, w + other.w);
}
Vector4 operator- (const Vector4& other) const
{
return Vector4(x - other.x, y - other.y, z - other.z, w - other.w);
}
Vector4 operator* (float scalar) const
{
return Vector4(x * scalar, y * scalar, z * scalar, w * scalar);
}
Vector4 operator/ (float scalar) const
{
return Vector4(x / scalar, y / scalar, z / scalar, w / scalar);
}
};
inline Vector4 operator* (float scalar, const Vector4& vector)
{
return vector * scalar;
}
}
+21
View File
@@ -0,0 +1,21 @@
#pragma once
#include <cstdint>
namespace Core
{
class FrameBuffer;
}
namespace Platform
{
class IDisplay
{
public:
virtual ~IDisplay() {}
virtual bool init(int width, int height) = 0;
virtual void present(const Core::FrameBuffer* framebuffer) = 0;
virtual void poll_events(bool& should_quit) = 0;
virtual uint32_t get_time_ms() const = 0;
virtual void shutdown() = 0;
};
}
+143
View File
@@ -0,0 +1,143 @@
#include "FBDisplay.h"
#include "FrameBuffer.h"
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/select.h>
#include <unistd.h>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <ctime>
namespace Platform
{
bool FBDisplay::init(int w, int h)
{
width = w;
height = h;
fb_fd = open("/dev/fb0", O_RDWR);
if (fb_fd < 0)
{
perror("open /dev/fb0");
return false;
}
if (ioctl(fb_fd, FBIOGET_FSCREENINFO, &finfo) < 0 ||
ioctl(fb_fd, FBIOGET_VSCREENINFO, &vinfo) < 0)
{
perror("ioctl FBIOGET_FSCREENINFO / FBIOGET_VSCREENINFO");
close(fb_fd);
fb_fd = -1;
return false;
}
printf("FB: %dx%d (virtual %dx%d), %dbpp, line_length=%d\n",
vinfo.xres, vinfo.yres, vinfo.xres_virtual, vinfo.yres_virtual,
vinfo.bits_per_pixel, finfo.line_length);
printf("RGB offsets: R=%d/%d G=%d/%d B=%d/%d\n",
vinfo.red.offset, vinfo.red.length,
vinfo.green.offset, vinfo.green.length,
vinfo.blue.offset, vinfo.blue.length);
fb_size = finfo.line_length * vinfo.yres;
fb_mem = (uint8_t*)mmap(nullptr, fb_size, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, 0);
if (fb_mem == MAP_FAILED)
{
perror("mmap");
close(fb_fd);
fb_fd = -1;
return false;
}
memset(fb_mem, 0, fb_size);
return true;
}
uint32_t FBDisplay::convert_pixel(uint32_t rgba) const
{
uint8_t r = (rgba >> 24) & 0xFF;
uint8_t g = (rgba >> 16) & 0xFF;
uint8_t b = (rgba >> 8) & 0xFF;
uint8_t a = rgba & 0xFF;
uint32_t color = 0;
if (vinfo.red.length > 0)
color |= (uint32_t)(r >> (8 - vinfo.red.length)) << vinfo.red.offset;
if (vinfo.green.length > 0)
color |= (uint32_t)(g >> (8 - vinfo.green.length)) << vinfo.green.offset;
if (vinfo.blue.length > 0)
color |= (uint32_t)(b >> (8 - vinfo.blue.length)) << vinfo.blue.offset;
if (vinfo.transp.length > 0)
color |= (uint32_t)(a >> (8 - vinfo.transp.length)) << vinfo.transp.offset;
return color;
}
void FBDisplay::present(const Core::FrameBuffer* framebuffer)
{
if (!fb_mem || !framebuffer)
return;
const uint32_t* src = static_cast<const uint32_t*>(framebuffer->get_buffer());
int dst_width = std::min(width, static_cast<int>(vinfo.xres));
int dst_height = std::min(height, static_cast<int>(vinfo.yres));
int bytes_per_pixel = vinfo.bits_per_pixel / 8;
for (int y = 0; y < dst_height; ++y)
{
for (int x = 0; x < dst_width; ++x)
{
uint32_t pixel = convert_pixel(src[y * width + x]);
uint8_t* dst = fb_mem + y * finfo.line_length + x * bytes_per_pixel;
if (vinfo.bits_per_pixel == 32)
{
*(uint32_t*)dst = pixel;
}
else if (vinfo.bits_per_pixel == 16)
{
*(uint16_t*)dst = static_cast<uint16_t>(pixel);
}
}
}
}
void FBDisplay::poll_events(bool& should_quit)
{
fd_set fds;
FD_ZERO(&fds);
FD_SET(STDIN_FILENO, &fds);
struct timeval tv = { 0, 0 };
if (select(STDIN_FILENO + 1, &fds, nullptr, nullptr, &tv) > 0)
{
char c;
if (read(STDIN_FILENO, &c, 1) == 1 && (c == 'q' || c == 'Q'))
{
should_quit = true;
}
}
}
uint32_t FBDisplay::get_time_ms() const
{
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return static_cast<uint32_t>(ts.tv_sec * 1000 + ts.tv_nsec / 1000000);
}
void FBDisplay::shutdown()
{
if (fb_mem != nullptr)
{
memset(fb_mem, 0, fb_size);
munmap(fb_mem, fb_size);
fb_mem = nullptr;
}
if (fb_fd >= 0)
{
close(fb_fd);
fb_fd = -1;
}
}
}
+27
View File
@@ -0,0 +1,27 @@
#pragma once
#include "Display.h"
#include <linux/fb.h>
namespace Platform
{
class FBDisplay : public IDisplay
{
private:
int fb_fd = -1;
uint8_t* fb_mem = nullptr;
size_t fb_size = 0;
fb_var_screeninfo vinfo;
fb_fix_screeninfo finfo;
int width = 0;
int height = 0;
uint32_t convert_pixel(uint32_t rgba) const;
public:
bool init(int w, int h) override;
void present(const Core::FrameBuffer* framebuffer) override;
void poll_events(bool& should_quit) override;
uint32_t get_time_ms() const override;
void shutdown() override;
};
}
+103
View File
@@ -0,0 +1,103 @@
#include "SDLDisplay.h"
#include "FrameBuffer.h"
#include <iostream>
namespace Platform
{
bool SDLDisplay::init(int w, int h)
{
width = w;
height = h;
if (SDL_Init(SDL_INIT_VIDEO) < 0)
{
std::cerr << "SDL_Init failed: " << SDL_GetError() << std::endl;
return false;
}
window = SDL_CreateWindow(
"IMX6U-Game",
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
width,
height,
SDL_WINDOW_SHOWN
);
if (window == nullptr)
{
std::cerr << "Window could not be created! SDL_Error: " << SDL_GetError() << std::endl;
return false;
}
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (renderer == nullptr)
{
std::cerr << "Renderer could not be created! SDL_Error: " << SDL_GetError() << std::endl;
return false;
}
texture = SDL_CreateTexture(
renderer,
SDL_PIXELFORMAT_RGBA8888,
SDL_TEXTUREACCESS_STREAMING,
width,
height
);
if (texture == nullptr)
{
std::cerr << "Texture could not be created! SDL_Error: " << SDL_GetError() << std::endl;
return false;
}
return true;
}
void SDLDisplay::present(const Core::FrameBuffer* framebuffer)
{
SDL_UpdateTexture(texture, nullptr, framebuffer->get_buffer(), width * sizeof(uint32_t));
SDL_RenderClear(renderer);
SDL_RenderCopy(renderer, texture, nullptr, nullptr);
SDL_RenderPresent(renderer);
}
void SDLDisplay::poll_events(bool& should_quit)
{
SDL_Event event;
while (SDL_PollEvent(&event))
{
if (event.type == SDL_QUIT)
{
should_quit = true;
}
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_SPACE)
{
should_quit = true;
}
}
}
uint32_t SDLDisplay::get_time_ms() const
{
return SDL_GetTicks();
}
void SDLDisplay::shutdown()
{
if (texture != nullptr)
{
SDL_DestroyTexture(texture);
texture = nullptr;
}
if (renderer != nullptr)
{
SDL_DestroyRenderer(renderer);
renderer = nullptr;
}
if (window != nullptr)
{
SDL_DestroyWindow(window);
window = nullptr;
}
SDL_Quit();
}
}
+23
View File
@@ -0,0 +1,23 @@
#pragma once
#include "Display.h"
#include <SDL.h>
namespace Platform
{
class SDLDisplay : public IDisplay
{
private:
SDL_Window* window = nullptr;
SDL_Renderer* renderer = nullptr;
SDL_Texture* texture = nullptr;
int width = 0;
int height = 0;
public:
bool init(int w, int h) override;
void present(const Core::FrameBuffer* framebuffer) override;
void poll_events(bool& should_quit) override;
uint32_t get_time_ms() const override;
void shutdown() override;
};
}
+102
View File
@@ -0,0 +1,102 @@
#include "Rasterizer.h"
#include <Vector2.h>
#include "Color.h"
#include <cmath>
#include <cstdint>
namespace Rasterizer
{
using namespace Math;
using namespace RenderData;
void Rasterizer::DrawLineHorizontal(const Vector2Int v0, const Vector2Int v1, const Color color)
{
Vector2Int start = v0, end = v1;
if (v0.x > v1.x)
{
start = v1;
end = v0;
}
int32_t dx = end.x - start.x;
int32_t dy = end.y - start.y;
int32_t dir = dy < 0 ? -1 : 1;
dy *= dir;
if (dx == 0) return;
int32_t y = start.y;
int32_t p = 2 * dy - dx;
for (int32_t x = start.x; x <= end.x; x++)
{
frameBuffer->set_pixel(x, y, color.to_rgba());
// 当 p >= 0 时,取上方像素点
if (p >= 0)
{
y += dir;
p -= 2 * dx;
}
// 当 p < 0 时,取下方像素点
p += 2 * dy;
}
}
void Rasterizer::DrawLineVertical(const Vector2Int v0, const Vector2Int v1, const Color color)
{
Vector2Int start = v0, end = v1;
if (v0.y > v1.y)
{
start = v1;
end = v0;
}
int32_t dx = end.x - start.x;
int32_t dy = end.y - start.y;
int32_t dir = dx < 0 ? -1 : 1;
dx *= dir;
if (dy == 0) return;
int32_t x = start.x;
int32_t p = 2 * dx - dy;
for (int32_t y = start.y; y <= end.y; y++)
{
frameBuffer->set_pixel(x, y, color.to_rgba());
// 当 p >= 0 时,取上方像素点
if (p >= 0)
{
x += dir;
p -= 2 * dy;
}
// 当 p < 0 时,取下方像素点
p += 2 * dx;
}
}
/// <summary>
/// 在给定的起始点和结束点之间绘制一条线段,并使用指定的颜色填充
/// </summary>
/// <param name="v0">直线顶点 1</param>
/// <param name="v1">直线顶点 2</param>
/// <param name="color">绘制的颜色</param>
/// <remarks>
/// bresenham 画线算法
/// </remarks>
void Rasterizer::DrawLine(const Vector2Int v0, const Vector2Int v1, const Color color)
{
int32_t x0 = v0.x, y0 = v0.y;
int32_t x1 = v1.x, y1 = v1.y;
if (std::abs(x1 - x0) > std::abs(y1 - y0))
{
DrawLineHorizontal(v0, v1, color);
}
else
{
DrawLineVertical(v0, v1, color);
}
}
}
+24
View File
@@ -0,0 +1,24 @@
#pragma once
#include "Color.h"
#include "FrameBuffer.h"
#include "DepthBuffer.h"
#include <Vector2.h>
namespace Rasterizer
{
class Rasterizer
{
private:
Core::FrameBuffer* frameBuffer;
Core::DepthBuffer* depthBuffer;
void DrawLineHorizontal(const Math::Vector2Int v0, const Math::Vector2Int v1, const RenderData::Color color);
void DrawLineVertical(const Math::Vector2Int v0, const Math::Vector2Int v1, const RenderData::Color color);
public:
explicit Rasterizer(Core::FrameBuffer* frameBuffer) :frameBuffer(frameBuffer), depthBuffer(nullptr) {}
explicit Rasterizer(Core::FrameBuffer* frameBuffer, Core::DepthBuffer* depthBuffer) :frameBuffer(frameBuffer), depthBuffer(depthBuffer) {}
void DrawLine(const Math::Vector2Int v0, const Math::Vector2Int v1, const RenderData::Color color);
};
}
+61
View File
@@ -0,0 +1,61 @@
#include "TriangleRasterizer.h"
#include <Triangle.h>
#include <Color.h>
#include "BoundingBox.h"
#include <Vector2.h>
#include <algorithm>
#include <cstdint>
namespace Rasterizer
{
void TriangleRasterizer::DrawTriangle2D(const RenderData::Triangle& triangle, const RenderData::Color color)
{
using namespace Math;
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++)
{
Vector2 samplePoint(x + 0.5f, y + 0.5f);
float w0 = 0, w1 = 0, w2 = 0;
if (!triangle.get_barycentric(samplePoint, w0, w1, w2))
{
continue;
}
if ((w0 < 0) || (w1 < 0) || (w2 < 0))
{
continue;
}
const float depth =
w0 * triangle.v0.position.z +
w1 * triangle.v1.position.z +
w2 * triangle.v2.position.z;
if (depthBuffer)
{
// 深度越小离相机越近
if (depthBuffer->get_depth(x, y) < depth)
{
continue;
}
depthBuffer->set_depth(x, y, depth);
frameBuffer->set_pixel(x, y, color.to_rgba());
}
else
{
frameBuffer->set_pixel(x, y, color.to_rgba());
}
}
}
}
}
+22
View File
@@ -0,0 +1,22 @@
#pragma once
#include "Triangle.h"
#include "Color.h"
#include "FrameBuffer.h"
#include <DepthBuffer.h>
namespace Rasterizer
{
class TriangleRasterizer
{
private:
Core::FrameBuffer* frameBuffer;
Core::DepthBuffer* depthBuffer;
public:
explicit TriangleRasterizer(Core::FrameBuffer* frameBuffer) :frameBuffer(frameBuffer), depthBuffer(nullptr) {};
explicit TriangleRasterizer(Core::FrameBuffer* frameBuffer, Core::DepthBuffer* depthBuffer) :frameBuffer(frameBuffer), depthBuffer(depthBuffer) {};
void DrawTriangle2D(const RenderData::Triangle& triangle, const RenderData::Color color);
};
}
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include "Vector2.h"
namespace RenderData
{
struct BoundingBox2D
{
Math::Vector2Int min;
Math::Vector2Int max;
BoundingBox2D() : min(), max() {}
BoundingBox2D(const Math::Vector2Int min, const Math::Vector2Int max) : min(min), max(max) {}
};
}
+40
View File
@@ -0,0 +1,40 @@
#pragma once
#include <cstdint>
namespace RenderData
{
struct Color
{
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
Color() : r(0), g(0), b(0), a(0) {}
Color(uint8_t r, uint8_t g, uint8_t b, uint8_t a)
{
this->r = ClampToByte(r);
this->g = ClampToByte(g);
this->b = ClampToByte(b);
this->a = ClampToByte(a);
}
uint32_t to_rgba() const
{
uint32_t value = (static_cast<uint32_t>(r) << 24) | (static_cast<uint32_t>(g) << 16) | (static_cast<uint32_t>(b) << 8) | (static_cast<uint32_t>(a));
return value;
}
static uint8_t ClampToByte(const int32_t value)
{
if (value < 0) return 0;
if (value > 255) return 255;
return static_cast<uint8_t>(value);
}
static Color Red() { return Color(255, 0, 0, 255); }
static Color Green() { return Color(0, 255, 0, 255); }
static Color Blue() { return Color(0, 0, 255, 255); }
static Color White() { return Color(255, 255, 255, 255); }
static Color Black() { return Color(0, 0, 0, 255); }
};
}
+67
View File
@@ -0,0 +1,67 @@
#pragma once
#include "Vector2.h"
#include "BoundingBox.h"
#include "Vertex.h"
#include <cstdint>
#include <algorithm>
#include <cmath>
#include <Vector3.h>
#include <cstdlib>
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<int32_t>(std::floor(std::min({ v0.position.x, v1.position.x, v2.position.x })));
int32_t maxX = static_cast<int32_t>(std::ceil(std::max({ v0.position.x, v1.position.x, v2.position.x })));
int32_t minY = static_cast<int32_t>(std::floor(std::min({ v0.position.y, v1.position.y, v2.position.y })));
int32_t maxY = static_cast<int32_t>(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);
}
/// <summary>
/// 给定屏幕像素坐标,输出该点的面积坐标
/// </summary>
/// <param name="pos">要计算的屏幕像素点</param>
/// <param name="w0">面积坐标的 x 分量(引用)</param>
/// <param name="w1">面积坐标的 y 分量(引用)</param>
/// <param name="w2">面积坐标的 z 分量(引用)</param>
/// <returns>是否计算成功</returns>
bool get_barycentric(const Math::Vector2& p, float& w0, float& w1, float& w2) const
{
using namespace Math;
const float x0 = v0.position.x;
const float y0 = v0.position.y;
const float x1 = v1.position.x;
const float y1 = v1.position.y;
const float x2 = v2.position.x;
const float y2 = v2.position.y;
const float square2D = (y1 - y2) * (x0 - x2) + (x2 - x1) * (y0 - y2);
if (std::abs(square2D) < 1e-6f)
{
return false;
}
w0 = ((y1 - y2) * (p.x - x2) + (x2 - x1) * (p.y - y2)) / square2D;
w1 = ((y2 - y0) * (p.x - x2) + (x0 - x2) * (p.y - y2)) / square2D;
w2 = 1.0f - w0 - w1;
return true;
}
};
}
+71
View File
@@ -0,0 +1,71 @@
#pragma once
#include "Matrix4x4.h"
#include "Camera.h"
#include <Vector3.h>
#include <cmath>
namespace Scene
{
Math::Matrix4x4 Camera::get_view_matrix() const
{
using namespace Math;
const Vector3 position = transform.position;
const Vector3 right = transform.get_right();
const Vector3 cameraUp = transform.get_up();
const Vector3 backward = -transform.get_forward();
return Matrix4x4(
right.x, right.y, right.z, -right.dot(position),
cameraUp.x, cameraUp.y, cameraUp.z, -cameraUp.dot(position),
backward.x, backward.y, backward.z, -backward.dot(position),
0, 0, 0, 1
);
}
Math::Matrix4x4 Camera::get_orthographic_projection_matrix(float width, float height) const
{
using namespace Math;
const float l = -width * 0.5f;
const float r = width * 0.5f;
const float b = -height * 0.5f;
const float t = height * 0.5f;
const float n = nearPlane;
const float f = farPlane;
return Matrix4x4(
2.0f / (r - l), 0, 0, -(r + l) / (r - l),
0, 2.0f / (t - b), 0, -(t + b) / (t - b),
0, 0, 2.0f / (n - f), -(n + f) / (f - n),
0, 0, 0, 1
);
}
Math::Matrix4x4 Camera::get_perspective_projection_matrix(float aspectRatio) const
{
using namespace Math;
const float n = nearPlane;
const float f = farPlane;
return Matrix4x4(
1.0f / (aspectRatio * std::tan(verticalFovRadians / 2)), 0, 0, 0,
0, 1.0f / std::tan(verticalFovRadians / 2), 0, 0,
0, 0, -(f + n) / (f - n), -(2 * f * n) / (f - n),
0, 0, -1, 0
);
}
Math::Matrix4x4 Camera::get_viewport_matrix(float width, float height) const
{
using namespace Math;
return Matrix4x4(
(width - 1) / 2, 0, 0, (width - 1) / 2,
0, -(height - 1) / 2, 0, (height - 1) / 2,
0, 0, 1, 0,
0, 0, 0, 1
);
}
}
+26
View File
@@ -0,0 +1,26 @@
#pragma once
#include "Matrix4x4.h"
#include "Transform.h"
namespace Scene
{
class Camera
{
public:
Camera() = default;
Transform transform;
float verticalFovRadians = 1.0471975512f;
float nearPlane = 0.1f;
float farPlane = 100.0f;
Math::Matrix4x4 get_view_matrix() const;
Math::Matrix4x4 get_orthographic_projection_matrix(float width, float height) const;
Math::Matrix4x4 get_perspective_projection_matrix(float aspectRatio) const;
Math::Matrix4x4 get_viewport_matrix(float width, float height) const;
};
}
+7
View File
@@ -0,0 +1,7 @@
#pragma once
namespace Scene
{
class Mesh
{};
}
+7
View File
@@ -0,0 +1,7 @@
#pragma once
namespace Scene
{
class Model
{};
}
+31
View File
@@ -0,0 +1,31 @@
#pragma once
#include "Vector3.h"
#include "MathUtil.h"
#include <Matrix4x4.h>
namespace Scene
{
class Transform
{
public:
Transform() = default;
Transform(Math::Vector3 position, Math::Vector3 rotation, Math::Vector3 scale) : position(position), rotation(rotation), scale(scale) {}
Math::Vector3 position = Math::Vector3(0, 0, 0);
Math::Vector3 rotation = Math::Vector3(0, 0, 0);
Math::Vector3 scale = Math::Vector3(1, 1, 1);
Math::Vector3 transform_direction(const Math::Vector3& direction) const
{
const Math::Matrix4x4 rotationMatrix = Math::MathUtil::get_rotation_matrix(rotation);
return rotationMatrix.TransformDirection(direction).to_vector3().normalized();
}
Math::Vector3 get_right() const { return transform_direction(Math::Vector3(1.0f, 0.0f, 0.0f)); }
Math::Vector3 get_left() const { return transform_direction(Math::Vector3(-1.0f, 0.0f, 0.0f)); }
Math::Vector3 get_up() const { return transform_direction(Math::Vector3(0.0f, 1.0f, 0.0f)); }
Math::Vector3 get_down() const { return transform_direction(Math::Vector3(0.0f, -1.0f, 0.0f)); }
Math::Vector3 get_forward() const { return transform_direction(Math::Vector3(0.0f, 0.0f, 1.0f)); }
Math::Vector3 get_back() const { return transform_direction(Math::Vector3(0.0f, 0.0f, -1.0f)); }
};
}
+14
View File
@@ -0,0 +1,14 @@
#pragma once
#include "Vector3.h"
namespace Scene
{
class Vertex
{
public:
Vertex() = default;
Vertex(Math::Vector3 position) :position(position) {}
Math::Vector3 position = Math::Vector3(0, 0, 0);
};
}
View File
+7
View File
@@ -0,0 +1,7 @@
#pragma once
namespace Shading
{
class BlinnPhongShader
{};
}
+9
View File
@@ -0,0 +1,9 @@
#pragma once
namespace Shading
{
enum class ShaderType
{
BlinnPhong,
};
}
+257
View File
@@ -0,0 +1,257 @@
#include <iostream>
#include <array>
#include <cstdint>
#include "Vector2.h"
#include "Vector3.h"
#include "Vector4.h"
#include "Matrix4x4.h"
#include "MathUtil.h"
#include "Color.h"
#include "FrameBuffer.h"
#include "Rasterizer.h"
#include "TriangleRasterizer.h"
#include "Triangle.h"
#include "Camera.h"
#include <cstdlib>
#include "Vertex.h"
#include "DepthBuffer.h"
#include "Display.h"
#ifdef USE_FRAMEBUFFER
#include "FBDisplay.h"
#else
#include "SDLDisplay.h"
#endif
const int32_t width = 800;
const int32_t height = 600;
struct ProjectedVertex
{
Math::Vector3 screen;
bool visible = false;
};
struct CubeFace
{
std::array<int, 4> vertices;
};
struct CubeTriangle
{
std::array<int, 3> vertices;
};
static ProjectedVertex ProjectToScreen(
const Math::Vector3& vertex,
const Math::Matrix4x4& mvp,
const Math::Matrix4x4& viewport)
{
using namespace Math;
const Vector4 clip = mvp * Vector4::Point(vertex);
if (std::abs(clip.w) < 1e-5f)
{
return {};
}
const float invW = 1.0f / clip.w;
const float ndcX = clip.x * invW;
const float ndcY = clip.y * invW;
const float ndcZ = clip.z * invW;
if (ndcX < -1.0f || ndcX > 1.0f || ndcY < -1.0f || ndcY > 1.0f || ndcZ < -1.0f || ndcZ > 1.0f)
{
return {};
}
const Vector4 screen = viewport * Vector4(ndcX, ndcY, ndcZ, 1.0f);
return { Math::Vector3(screen.x, screen.y, screen.z), true };
}
static bool IsFaceVisible(const CubeFace& face, const std::array<Math::Vector3, 8>& viewSpaceVertices)
{
using namespace Math;
const Vector3& v0 = viewSpaceVertices[face.vertices[0]];
const Vector3& v1 = viewSpaceVertices[face.vertices[1]];
const Vector3& v2 = viewSpaceVertices[face.vertices[2]];
const Vector3 faceNormal = (v1 - v0).cross(v2 - v0);
const Vector3 faceCenter =
(viewSpaceVertices[face.vertices[0]] +
viewSpaceVertices[face.vertices[1]] +
viewSpaceVertices[face.vertices[2]] +
viewSpaceVertices[face.vertices[3]]) / 4.0f;
return faceNormal.dot(faceCenter) > 0.0f;
}
static bool IsTriangleVisible(const CubeTriangle& triangle, const std::array<Math::Vector3, 8>& 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[])
{
#ifdef USE_FRAMEBUFFER
Platform::IDisplay* display = new Platform::FBDisplay();
#else
Platform::IDisplay* display = new Platform::SDLDisplay();
#endif
if (!display->init(width, height))
{
delete display;
return -1;
}
Core::FrameBuffer* frameBuffer = new Core::FrameBuffer(width, height);
Core::DepthBuffer* depthBuffer = new Core::DepthBuffer(width, height);
Rasterizer::Rasterizer rasterizer(frameBuffer, depthBuffer);
Rasterizer::TriangleRasterizer triangleRasterizer(frameBuffer, depthBuffer);
Scene::Camera camera;
camera.transform.position = Math::Vector3(0.0f, 0.0f, 3.0f);
camera.transform.rotation = Math::Vector3(0.0f, 3.1415926535f, 0.0f);
const std::array<Math::Vector3, 8> cubeVertices = {
Math::Vector3(-0.5f, -0.5f, -0.5f),
Math::Vector3(0.5f, -0.5f, -0.5f),
Math::Vector3(0.5f, 0.5f, -0.5f),
Math::Vector3(-0.5f, 0.5f, -0.5f),
Math::Vector3(-0.5f, -0.5f, 0.5f),
Math::Vector3(0.5f, -0.5f, 0.5f),
Math::Vector3(0.5f, 0.5f, 0.5f),
Math::Vector3(-0.5f, 0.5f, 0.5f)
};
const std::array<CubeFace, 6> cubeFaces = {
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<CubeTriangle, 12> 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);
const RenderData::Color cubeColor(240, 240, 240, 255);
const float aspectRatio = static_cast<float>(width) / static_cast<float>(height);
bool isRuning = true;
while (isRuning)
{
display->poll_events(isRuning);
frameBuffer->clear(clearColor);
depthBuffer->clear();
const float timeSeconds = static_cast<float>(display->get_time_ms()) * 0.001f;
const Math::Matrix4x4 model =
Math::MathUtil::get_rotation_matrix_y(timeSeconds) *
Math::MathUtil::get_rotation_matrix_x(timeSeconds * 0.6f);
const Math::Matrix4x4 view = camera.get_view_matrix();
const Math::Matrix4x4 modelView = view * model;
const Math::Matrix4x4 projection = camera.get_perspective_projection_matrix(aspectRatio);
const Math::Matrix4x4 viewport = camera.get_viewport_matrix(static_cast<float>(width), static_cast<float>(height));
const Math::Matrix4x4 mvp = projection * modelView;
std::array<Math::Vector3, 8> viewSpaceVertices;
std::array<ProjectedVertex, 8> projectedVertices;
for (size_t i = 0; i < cubeVertices.size(); ++i)
{
viewSpaceVertices[i] = (modelView * Math::Vector4::Point(cubeVertices[i])).to_vector3();
projectedVertices[i] = ProjectToScreen(cubeVertices[i], mvp, viewport);
}
std::array<bool, 6> visibleFaces = {};
for (size_t faceIndex = 0; faceIndex < cubeFaces.size(); ++faceIndex)
{
visibleFaces[faceIndex] = IsFaceVisible(cubeFaces[faceIndex], viewSpaceVertices);
}
std::array<RenderData::Triangle, 12> drawTriangles;
size_t drawCommandCount = 0;
for (const CubeTriangle& cubeTriangle : cubeTriangles)
{
if (!IsTriangleVisible(cubeTriangle, viewSpaceVertices))
{
continue;
}
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;
}
const Math::Vector3& viewV0 = viewSpaceVertices[cubeTriangle.vertices[0]];
const Math::Vector3& viewV1 = viewSpaceVertices[cubeTriangle.vertices[1]];
const Math::Vector3& viewV2 = viewSpaceVertices[cubeTriangle.vertices[2]];
drawTriangles[drawCommandCount++] =
RenderData::Triangle(
Scene::Vertex(v0.screen),
Scene::Vertex(v1.screen),
Scene::Vertex(v2.screen)
);
}
for (size_t i = 0; i < drawCommandCount; ++i)
{
triangleRasterizer.DrawTriangle2D(drawTriangles[i], 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);
}
}
display->present(frameBuffer);
}
display->shutdown();
delete display;
delete frameBuffer;
delete depthBuffer;
return 0;
}
+155
View File
@@ -0,0 +1,155 @@
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <algorithm>
struct Point
{
int x;
int y;
};
static uint32_t make_color(uint8_t r, uint8_t g, uint8_t b, const fb_var_screeninfo& vinfo)
{
uint32_t color = 0;
color |= (uint32_t)(r >> (8 - vinfo.red.length)) << vinfo.red.offset;
color |= (uint32_t)(g >> (8 - vinfo.green.length)) << vinfo.green.offset;
color |= (uint32_t)(b >> (8 - vinfo.blue.length)) << vinfo.blue.offset;
return color;
}
static void set_pixel(uint8_t* fbmem, const fb_fix_screeninfo& finfo, const fb_var_screeninfo& vinfo,
int x, int y, uint32_t color)
{
if (x < 0 || x >= (int)vinfo.xres || y < 0 || y >= (int)vinfo.yres)
return;
uint8_t* ptr = fbmem + y * finfo.line_length + x * (vinfo.bits_per_pixel / 8);
if (vinfo.bits_per_pixel == 32)
{
*(uint32_t*)ptr = color;
}
else if (vinfo.bits_per_pixel == 16)
{
*(uint16_t*)ptr = (uint16_t)color;
}
}
static void draw_line(uint8_t* fbmem, const fb_fix_screeninfo& finfo, const fb_var_screeninfo& vinfo,
int x0, int y0, int x1, int y1, uint32_t color)
{
int dx = abs(x1 - x0);
int dy = abs(y1 - y0);
int sx = (x0 < x1) ? 1 : -1;
int sy = (y0 < y1) ? 1 : -1;
int err = dx - dy;
while (1)
{
set_pixel(fbmem, finfo, vinfo, x0, y0, color);
if (x0 == x1 && y0 == y1)
break;
int e2 = 2 * err;
if (e2 > -dy) { err -= dy; x0 += sx; }
if (e2 < dx) { err += dx; y0 += sy; }
}
}
static void draw_filled_triangle(uint8_t* fbmem, const fb_fix_screeninfo& finfo, const fb_var_screeninfo& vinfo,
const Point& p0, const Point& p1, const Point& p2, uint32_t color)
{
Point pts[3] = { p0, p1, p2 };
if (pts[1].y < pts[0].y) std::swap(pts[0], pts[1]);
if (pts[2].y < pts[0].y) std::swap(pts[0], pts[2]);
if (pts[2].y < pts[1].y) std::swap(pts[1], pts[2]);
auto interpolate_x = [](const Point& a, const Point& b, int y) -> int {
if (a.y == b.y) return a.x;
return a.x + (b.x - a.x) * (y - a.y) / (b.y - a.y);
};
for (int y = pts[0].y; y <= pts[2].y; ++y)
{
int xl, xr;
if (y < pts[1].y)
{
xl = interpolate_x(pts[0], pts[1], y);
xr = interpolate_x(pts[0], pts[2], y);
}
else
{
xl = interpolate_x(pts[1], pts[2], y);
xr = interpolate_x(pts[0], pts[2], y);
}
if (xl > xr) std::swap(xl, xr);
for (int x = xl; x <= xr; ++x)
set_pixel(fbmem, finfo, vinfo, x, y, color);
}
}
int main()
{
int fb = open("/dev/fb0", O_RDWR);
if (fb < 0)
{
perror("open /dev/fb0");
return 1;
}
fb_var_screeninfo vinfo;
fb_fix_screeninfo finfo;
if (ioctl(fb, FBIOGET_FSCREENINFO, &finfo) < 0 ||
ioctl(fb, FBIOGET_VSCREENINFO, &vinfo) < 0)
{
perror("ioctl FBIOGET_FSCREENINFO / FBIOGET_VSCREENINFO");
close(fb);
return 1;
}
printf("fb0: %dx%d, %dbpp\n", vinfo.xres, vinfo.yres, vinfo.bits_per_pixel);
printf("line_length=%d\n", finfo.line_length);
printf("RGB offsets: R=%d/%d G=%d/%d B=%d/%d\n",
vinfo.red.offset, vinfo.red.length,
vinfo.green.offset, vinfo.green.length,
vinfo.blue.offset, vinfo.blue.length);
size_t screensize = finfo.line_length * vinfo.yres;
uint8_t* fbmem = (uint8_t*)mmap(nullptr, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fb, 0);
if (fbmem == MAP_FAILED)
{
perror("mmap");
close(fb);
return 1;
}
memset(fbmem, 0, screensize);
uint32_t green = make_color(0, 255, 0, vinfo);
uint32_t white = make_color(255, 255, 255, vinfo);
int cx = vinfo.xres / 2;
int cy = vinfo.yres / 2;
Point p0 = { cx, cy - 120 };
Point p1 = { cx - 100, cy + 80 };
Point p2 = { cx + 100, cy + 80 };
draw_filled_triangle(fbmem, finfo, vinfo, p0, p1, p2, green);
draw_line(fbmem, finfo, vinfo, p0.x, p0.y, p1.x, p1.y, white);
draw_line(fbmem, finfo, vinfo, p1.x, p1.y, p2.x, p2.y, white);
draw_line(fbmem, finfo, vinfo, p2.x, p2.y, p0.x, p0.y, white);
printf("Triangle drawn. Press Enter to clear and exit...\n");
getchar();
memset(fbmem, 0, screensize);
munmap(fbmem, screensize);
close(fb);
return 0;
}