搭建虚拟机环境
- 安装Oracle VM VirtualBox虚拟机,安装虚拟硬盘,配置Linux Ubuntu-64 bit系统,
- 启动虚拟机,发生冲突错误:
- 将Vmware虚拟设备取消挂起状态,关机
- 确保 Hyper-V 完全关闭:bcdedit /set hypervisorlaunchtype off
- 重启计算机
- 安装增强功能,未找到iso错误:ISO下载地址:Index of http://download.virtualbox.org/virtualbox
- 首先升级虚拟机版本置7.0.2
- 通过update Guest additions
- 最后安装增强功能
- 通过终端调用cmake编译文件,
- 使用硬盘中的vscode编写main.cpp
- 通过终端创建文件touch CMakeLists.txt
- 编写文件(作业框架里有)
- cmake编译目录
- make编译
- 执行运行程序
# 指定最低版本要求
cmake_minimum_required(VERSION 2.8.5)
# 项目名称
project(Hello)
# 设置 C++ 标准
set(CMAKE_CXX_STANDARD 11)
#
find_package(Eigen3 REQUIRED)
include_directories(EIGEN3_INCLUDE_DIR)
# 添加源文件
add_executable(pa0 main.cpp)
作业0:
题目:
给定一个点 P=(2,1), 将该点绕原点先逆时针旋转 45◦,再平移 (1,2), 计算出 变换后点的坐标(要求用齐次坐标进行计算)。
解:
首先为向量增加一个分量(1),将变换的值带入,SL:3*3的旋转(绕z) * 平移矩阵公式中,最后乘以具有3个分量的向量
auto angle = 45 * 3.14 / 180.0f;
Eigen::Matrix3f R_Mat3;
R_Mat3 <<
std::cos(angle),-std::sin(angle),0,
std::sin(angle), std::cos(angle),0,
0,0,1;
Eigen::Matrix3f T_Mat3;
T_Mat3 <<
1,0,1,
0,1,2,
0,0,1;
Eigen::Vector3f p(2.0f,1.0f,1.0f);
auto result = T_Mat3 * R_Mat3* p;
std::cout << result;
作业1(OpenCV,Eigen):
题目:
在给定作业框架填写一个旋转矩阵get_model_matrix()和一个透视投影矩阵get_projection_matrix(),给定三维下三个 点 v0(2.0, 0.0, −2.0), v1(0.0, 2.0, −2.0), v2(−2.0, 0.0, −2.0), 你需要将这三个点的坐 标变换为屏幕坐标并在屏幕上绘制出对应的线框三角形
解:
旋转矩阵:就是利用绕z旋转矩阵公式
double fangle = rotation_angle / 180 * MY_PI;
Eigen::Matrix4f rotation;
rotation <<
cos(fangle), -sin(fangle), 0, 0,
sin(fangle), cos(fangle), 0, 0,
0, 0, 1, 0,
0, 0, 0, 1;
model = rotation * model;
透视投影矩阵:同样带入公式即可
Eigen::Matrix4f proj, ortho;
proj <<
zNear, 0, 0, 0,
0, zNear, 0, 0,
0, 0, zNear + zFar, -zNear * zFar,
0, 0, 1, 0;
double w, h, z;
h = zNear * tan(eye_fov / 2) * 2;
w = h * aspect_ratio;
z = zFar - zNear;
ortho <<
2 / w, 0, 0, 0,
0, 2 / h, 0, 0,
0, 0, 2 / z, -(zFar+zNear) / 2,
0, 0, 0, 1;
projection = ortho * proj * projection;
![](https://i-blog.csdnimg.cn/direct/7de18d992be341d4b77045a43661e5bc.png)
提高部分题目:
在 main.cpp 中构造一个函数,该函数的作用是得到绕任意 过原点的轴的旋转变换矩阵。Eigen::Matrix4f get_rotation(Vector3f axis, float angle)
解:
很明显从函数参数可以看出应用的是罗德里格斯公式,所以这里其实就是带入公式
Eigen::Matrix4f get_rotation(Vector3f axis, float angle) {
double fangle = angle / 180 * MY_PI;
Eigen::Matrix4f I, N, Rod;
Eigen::Vector4f axi;
Eigen::RowVector4f taxi;
axi << axis.x(), axis.y(), axis.z(), 0;
I=Eigen::Matrix4f::Identity();
N << 0, -axis.z(), axis.y(), 0,
axis.z(), 0, -axis.x(), 0,
-axis.y(), axis.x(), 0, 0,
0, 0, 0, 1;
Rod = cos(fangle) * I + (1 - cos(fangle)) * axi * axi.transpose() + sin(fangle) * N;
Rod(3, 3) = 1;
return Rod;
}
理解代码:
对于c语言使用.h,.c的扩展名,而对于c++使用.h,hpp(为了区分 C 和 C++ 代码),.cpp扩展名
main.cpp:变换矩阵的计算,模拟了图形管线,旋转三角形的控制
int argc,命令行参数的个数 const char** argv |const char* argv[]是一个数组,每个元素都是指向常量字符的指针
为什么argv[2]没有解引用?因为std::cout
会自动将 const char*
解释为一个以 \0
结尾的字符串。
int main(int argc, const char** argv)//在运行是输入命令行参数
{
float angle = 0;//摄像机角度
bool command_line = false;//设置命令行开关
std::string filename = "output.png";//字符串
if (argc >= 3) {//当3个参数
command_line = true;
angle = std::stof(argv[2]); // -r by default//从命令行获取旋转角度
if (argc == 4) {//当4个参数
filename = std::string(argv[3]);//从命令行获取文件名
}
else
return 0;
}
rst::rasterizer r(700, 700);//光栅化器类的实例,传入视口大小
Eigen::Vector3f eye_pos = {0, 0, 5};//相机未知
std::vector<Eigen::Vector3f> pos{{2, 0, -2}, {0, 2, -2}, {-2, 0, -2}};//3个顶点
std::vector<Eigen::Vector3i> ind{{0, 1, 2}};//顶点索引
auto pos_id = r.load_positions(pos);//保存顶点
auto ind_id = r.load_indices(ind);//保存索引
int key = 0;//键盘输入
int frame_count = 0;
if (command_line) {//如果传入参数>=3
r.clear(rst::Buffers::Color | rst::Buffers::Depth);
r.set_model(get_model_matrix(angle));
r.set_view(get_view_matrix(eye_pos));
r.set_projection(get_projection_matrix(45, 1, 0.1, 50));
r.draw(pos_id, ind_id, rst::Primitive::Triangle);
cv::Mat image(700, 700, CV_32FC3, r.frame_buffer().data());
image.convertTo(image, CV_8UC3, 1.0f);
cv::imwrite(filename, image);
return 0;
}
while (key != 27) {//非ECS
r.clear(rst::Buffers::Color | rst::Buffers::Depth);
//计算模型、视图和投影矩阵,并传入光栅化器中,光栅化器在屏幕上显示出变换的结果
r.set_model(get_model_matrix(angle));
r.set_view(get_view_matrix(eye_pos));
r.set_projection(get_projection_matrix(45, 1, 0.1, 50));
r.draw(pos_id, ind_id, rst::Primitive::Triangle);//绘制三角形
cv::Mat image(700, 700, CV_32FC3, r.frame_buffer().data());
image.convertTo(image, CV_8UC3, 1.0f);
cv::imshow("image", image);//显示图像
key = cv::waitKey(10);
std::cout << "frame count: " << frame_count++ << '\n';
if (key == 'a') {//逆时针旋转10
angle += 10;
}
else if (key == 'd') {//顺时针旋转10
angle -= 10;
}
}
return 0;
}
rasterizer.hpp , rasterizer.cpp光栅器 | 渲染器,生成渲染器界面与绘制。
rst::pos_buf_id rst::rasterizer::load_positions(const std::vector<Eigen::Vector3f> &positions) // 保存到
{
auto id = get_next_id(); // 从0开始分配,每次调用后索引增加
pos_buf.emplace(id, positions); // 存放到顶点数组中
return {id};
}
rst::ind_buf_id rst::rasterizer::load_indices(const std::vector<Eigen::Vector3i> &indices)
{
auto id = get_next_id();
ind_buf.emplace(id, indices);
return {id};
}
// Bresenham's line drawing algorithm,
// Bresenham算法,绘制线
// Code taken from a stack overflow answer: https://stackoverflow.com/a/16405254
void rst::rasterizer::draw_line(Eigen::Vector3f begin, Eigen::Vector3f end)
{
/* 起点和终点坐标 */
auto x1 = begin.x();
auto y1 = begin.y();
auto x2 = end.x();
auto y2 = end.y();
/* 颜色 */
Eigen::Vector3f line_color = {255, 255, 255};
/*x, y当前绘制的像素点的坐标,
dx, dy直线在 x 和 y 方向上的增量,
dx1, dy1用于计算偏移量,
px, py用于确定下一个像素点的坐标 ,
xe, ye: 用于循环控制,表示直线的终点*/
int x, y, dx, dy, dx1, dy1, px, py, xe, ye, i;
dx = x2 - x1;
dy = y2 - y1;
dx1 = fabs(dx);
dy1 = fabs(dy);
px = 2 * dy1 - dx1;
py = 2 * dx1 - dy1;
if (dy1 <= dx1)/* 如果在y轴上的偏移量比x轴上要小,更趋向于水平线 */
{
if (dx >= 0)/* 从起点坐标开始 */
{
x = x1;
y = y1;
xe = x2;
}
else/* 从终点坐标开始 */
{
x = x2;
y = y2;
xe = x1;
}
Eigen::Vector3f point = Eigen::Vector3f(x, y, 1.0f);/* 创建起点 */
set_pixel(point, line_color);/* 设置为白色 */
for (i = 0; x < xe; i++)/* 通过迭代来逐步绘制直线的每个像素点。 */
{
x = x + 1;
if (px < 0)/* 像素的 y 坐标不需要改变 */
{
px = px + 2 * dy1;/* 更新 px 的值 */
}
else
{
if ((dx < 0 && dy < 0) || (dx > 0 && dy > 0))/* 表示 y 方向向上移动 */
{
y = y + 1;
}
else/* 表示 y 方向向下移动。 */
{
y = y - 1;
}
px = px + 2 * (dy1 - dx1);/* 更新 px 的值 */
}
//delay(0);
Eigen::Vector3f point = Eigen::Vector3f(x, y, 1.0f);/* 新的点绘制到屏幕上 */
set_pixel(point, line_color);
}
}
else
{
……
}
}
auto to_vec4(const Eigen::Vector3f &v3, float w = 1.0f) // 默认创建点,引入齐次坐标
{
return Vector4f(v3.x(), v3.y(), v3.z(), w);
}
void rst::rasterizer::draw(rst::pos_buf_id pos_buffer, rst::ind_buf_id ind_buffer, rst::Primitive type)
{
if (type != rst::Primitive::Triangle)
{
throw std::runtime_error("Drawing primitives other than triangle is not implemented yet!");
}
//2维数组
auto &buf = pos_buf[pos_buffer.pos_id];/* 根据id访问实际数据,3个顶点的坐标 */
auto &ind = ind_buf[ind_buffer.ind_id];/* 三个顶点的索引 */
float f1 = (100 - 0.1) / 2.0;
float f2 = (100 + 0.1) / 2.0;
Eigen::Matrix4f mvp = projection * view * model; // MVP
for (auto &i : ind)//第一次为{0,1,2}
{
Triangle t;/* 三角形对象 */
/* MVP变换 ,对3个顶点 */
Eigen::Vector4f v[] = {
mvp * to_vec4(buf[i[0]], 1.0f),
mvp * to_vec4(buf[i[1]], 1.0f),
mvp * to_vec4(buf[i[2]], 1.0f)};
/* 遍历每个顶点,除齐次坐标 */
for (auto &vec : v)
{
vec /= vec.w();
}
/* 对每个顶点,分解为xyz,变换到视口,应用视口矩阵 */
for (auto &vert : v)
{
vert.x() = 0.5 * width * (vert.x() + 1.0);
vert.y() = 0.5 * height * (vert.y() + 1.0);
vert.z() = vert.z() * f1 + f2;
}
/* 为三角形设置数据 */
for (int i = 0; i < 3; ++i)
{
t.setVertex(i, v[i].head<3>());
t.setVertex(i, v[i].head<3>());
t.setVertex(i, v[i].head<3>());
}
// 设置颜色
t.setColor(0, 255.0, 0.0, 0.0);
t.setColor(1, 0.0, 255.0, 0.0);
t.setColor(2, 0.0, 0.0, 255.0);
rasterize_wireframe(t); // 绘制
}
}
void rst::rasterizer::rasterize_wireframe(const Triangle &t) // 绘制三角形
{
draw_line(t.c(), t.a());/* abc为3个顶点 */
draw_line(t.c(), t.b());
draw_line(t.b(), t.a());
}
void rst::rasterizer::set_model(const Eigen::Matrix4f &m) // 设置成员变量
{
model = m;
}
void rst::rasterizer::set_view(const Eigen::Matrix4f &v)
{
view = v;
}
void rst::rasterizer::set_projection(const Eigen::Matrix4f &p)
{
projection = p;
}
void rst::rasterizer::clear(rst::Buffers buff) // 清理颜色和深度缓冲区
{
if ((buff & rst::Buffers::Color) == rst::Buffers::Color)
{
std::fill(frame_buf.begin(), frame_buf.end(), Eigen::Vector3f{0, 0, 0});
}
if ((buff & rst::Buffers::Depth) == rst::Buffers::Depth)
{
std::fill(depth_buf.begin(), depth_buf.end(), std::numeric_limits<float>::infinity());
}
}
rst::rasterizer::rasterizer(int w, int h) : width(w), height(h) // 根据宽高比设置帧缓冲大小和深度缓冲大小
{
frame_buf.resize(w * h);
depth_buf.resize(w * h);
}
int rst::rasterizer::get_index(int x, int y) // 根据坐标求像素在缓冲区的序号
{
return (height - y) * width + x;
}
void rst::rasterizer::set_pixel(const Eigen::Vector3f &point, const Eigen::Vector3f &color) // 将屏幕像素点 (x, y) 设 为 (r, g, b) 的颜色,并写入相应的帧缓冲区位置。
{
// old index: auto ind = point.y() + point.x() * width;
if (point.x() < 0 || point.x() >= width ||
point.y() < 0 || point.y() >= height)
return;
auto ind = (height - point.y()) * width + point.x();
frame_buf[ind] = color;
}
Triangle.hpp, Triangle.cpp三角形数据 ,提供了数据设置的接口
class Triangle
{
public:
/*the original coordinates of the triangle, v0, v1, v2 in counter clockwise order*/
/*Per vertex values*/
/* 顶点,颜色,纹理坐标,法线 */
Vector3f v[3];
Vector3f color[3]; // color at each vertex;
Vector2f tex_coords[3]; // texture u,v
Vector3f normal[3]; // normal vector for each vertex
// Texture *tex;
Triangle();
/* 返回三角形顶点坐标 */
Eigen::Vector3f a() const { return v[0]; }
Eigen::Vector3f b() const { return v[1]; }
Eigen::Vector3f c() const { return v[2]; }
void setVertex(int ind, Vector3f ver); /*set i-th vertex coordinates */
void setNormal(int ind, Vector3f n); /*set i-th vertex normal vector*/
void setColor(int ind, float r, float g, float b); /*set i-th vertex color*/
void setTexCoord(int ind, float s,
float t); /*set i-th vertex texture coordinate*/
std::array<Vector4f, 3> toVector4() const;
};
#endif // RASTERIZER_TRIANGLE_H
//cpp
Triangle::Triangle()/* 初始值都为0 */
{
v[0] << 0, 0, 0;
v[1] << 0, 0, 0;
v[2] << 0, 0, 0;
color[0] << 0.0, 0.0, 0.0;
color[1] << 0.0, 0.0, 0.0;
color[2] << 0.0, 0.0, 0.0;
tex_coords[0] << 0.0, 0.0;
tex_coords[1] << 0.0, 0.0;
tex_coords[2] << 0.0, 0.0;
}
void Triangle::setVertex(int ind, Eigen::Vector3f ver) { v[ind] = ver; }/* 设置指定索引的值 */
void Triangle::setNormal(int ind, Vector3f n) { normal[ind] = n; }
void Triangle::setColor(int ind, float r, float g, float b)/* 将颜色从0--255,转到0--1 */
{
if ((r < 0.0) || (r > 255.) || (g < 0.0) || (g > 255.) || (b < 0.0) ||
(b > 255.)) {
throw std::runtime_error("Invalid color values");/* 超出范围抛出异常 */
}
color[ind] = Vector3f((float)r / 255., (float)g / 255., (float)b / 255.);
return;
}
void Triangle::setTexCoord(int ind, float s, float t)
{
tex_coords[ind] = Vector2f(s, t);
}
std::array<Vector4f, 3> Triangle::toVector4() const/* 三个顶点从 Vector3f 类型转换为 Vector4f 类型 */
{
std::array<Vector4f, 3> res;
std::transform(std::begin(v), std::end(v), res.begin(), [](auto& vec) {/* 遍历每个顶点 */
return Vector4f(vec.x(), vec.y(), vec.z(), 1.f);
});
return res;
}
阅读复杂指针
如何阅读C/C++中的复杂的指针类型声明(源码中常遇到)_c++复杂阅读指针-CSDN博客
右左法则:从标识符开始,从右到左,优先级越高
括号法则:运算优先级更高
规律:普通变量(左边是本身类型),指针|引用(左边表示指向的类型),数组(左边表示元素类型),函数(左边表示返回类型)
int * a [10];a是一个包含10个数据的数组,每个元素是一个指向int的指针
int (*a) [3]; a是一个指针,指向3个元素的数组,每个元素是int类型
int *foo();foo是一个函数,返回指向int指针
int (*foo)();foo是一个指针,指向函数,函数返回int
int (*(*vtable)[])();vtable是一个指针,指向数组,每个元素是指针,并指向函数,函数返回类型为int
int name[] [n],name是一个数组,每个元素也是数组,包含n个int类型元素
const char** argv,argv是一个指针,指向常量字符的指针
const char* argv[]argv是一个数组,每个元素都是指向常量字符的指针
总结
整体课程相比于OpenGL还是偏底层的,手写MVP矩阵,手写Bresenham算法绘制线,和三角形,计算齐次坐标等,并没有opengl封装好的接口