init
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace Asset
|
||||
{
|
||||
class ObjLoader
|
||||
{};
|
||||
}
|
||||
@@ -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` 负责的是像素级遮挡,而不是顶点级或三角形级是否进入渲染流程
|
||||
|
||||
后续如果项目要加入更严格的裁剪、剔除、透视校正插值或隐藏线规则,应当以代码实现为准,并同步更新本文档。
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace Core
|
||||
{
|
||||
class Renderer
|
||||
{};
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
|
||||
class Cube
|
||||
{
|
||||
private:
|
||||
|
||||
|
||||
|
||||
public:
|
||||
Cube() = default;
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -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) {}
|
||||
};
|
||||
}
|
||||
@@ -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); }
|
||||
};
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace Scene
|
||||
{
|
||||
class Mesh
|
||||
{};
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace Scene
|
||||
{
|
||||
class Model
|
||||
{};
|
||||
}
|
||||
@@ -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)); }
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace Shading
|
||||
{
|
||||
class BlinnPhongShader
|
||||
{};
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
namespace Shading
|
||||
{
|
||||
enum class ShaderType
|
||||
{
|
||||
BlinnPhong,
|
||||
};
|
||||
}
|
||||
+257
@@ -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
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user