games101 作业1及作业2分析及解决

去年的时候把games101的课程以及作业完成,但是整个过程比较粗略,也借助了不少外界的力量(doge),于是最近准备抽几天集中再把作业(1-7)过一遍,常看常新嘛 环境配置直接用:https://github.com/roeas/GAMES101-Premake 之前是在虚拟机上 这次用vs也方便一些

有时间也会研究一下大作业

作业一

代码分析

简要分析一下整体的一个绘制流程

首先定义了绘制的视口 同时初始化了像素缓冲区 与 深度缓冲区:

rst::rasterizer r(700, 700);
rst::rasterizer::rasterizer(int w, int h) : width(w), height(h)
{
frame_buf.resize(w * h);
depth_buf.resize(w * h);
}

定义相机位置、三角形三个顶点在空间中的位置,三个顶点的索引顺序,注意我这里相机位置和顶点位置设置的都和原来不一样,这里后面再提:

Eigen::Vector3f eye_pos = {0, 0, 0};

std::vector<Eigen::Vector3f> pos{{2, 0, 12}, {0, 2, 12}, {-2, 0, 12}};

std::vector<Eigen::Vector3i> ind{{0, 1, 2}};

然后创建对应三角形的顶点缓冲区以及索引缓冲区:

auto pos_id = r.load_positions(pos);
auto ind_id = r.load_indices(ind);
rst::pos_buf_id rst::rasterizer::load_positions(const std::vector<Eigen::Vector3f> &positions)
{
auto id = get_next_id();
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};
}

然后就是设置模型、观察以及透视矩阵,最后绘制

绘制部分:

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!");
}
读取对应的三角形的顶点以及索引信息
auto& buf = pos_buf[pos_buffer.pos_id];
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;
for (auto& i : ind)
{
Triangle t;
转换到屏幕空间
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();
}
转换到像素空间
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);
绘制 这里是用线框形式绘制 使用的画线算法是Bresenham
rasterize_wireframe(t);
}
}

理论分析



贴一张大致的总结图

重点分析透视矩阵的推导

这里我介绍一下d3d12龙书的推导过程

把点投影到我们的投影平面上 利用相似我们可以得到的关系是(假设投影平面到我们摄像机的距离为1):

\[x^{'} = \frac{x}{z}
\]
\[y^{'} = \frac{y}{z}
\]

为了规范化归一化 我们是要把投影平面\(x\in [-width/2,width/2]\)与\(y\in [-height/2,height/2]\) 转换到[-1,1]的这个平面上,要经历变换:

\[x^{'} = \frac{x*2}{W}
\]
\[y^{'} = \frac{y*2}{H}
\]

如果我们使用fov 与 宽高比(r)来表示 则可以转化为:

\[x^{'} = \frac{x}{(r* tan \frac{Fov}{2})}
\]
\[y^{'} = \frac{y}{tan \frac{Fov}{2}}
\]

可以看出我们其实是要对x,y进行两步变换 我们可以第一步先进行归一化变换

同时为了进行透视除法 我们需要存储z坐标,所以在第一步中我们要利用w分量来存储z值,得到的变换过程如下:

\[\begin{bmatrix}
\frac{1}{r\tan \frac{Fov}{2}} & 0 &0 &0 \\
0& \frac{1}{\tan \frac{Fov}{2}}&0 &0 \\
0& 0& A& B\\
0& 0& 1&0
\end{bmatrix} \begin{bmatrix}
x\\
y \\
z\\
1
\end{bmatrix}= \begin{bmatrix}
\frac{x}{(r* tan \frac{Fov}{2})}\\
\frac{y}{tan \frac{Fov}{2}} \\
Az+B\\
z
\end{bmatrix}\]

之后第二步再进行透视除法:

\[\begin{bmatrix}
\frac{x}{(rz* tan \frac{Fov}{2})}\\
\frac{y}{ztan \frac{Fov}{2}} \\
A+\frac{B}{z}\\
1
\end{bmatrix}\]

最后我们还需要对z深度值进行归一化操作 将z值转换到0-1 在上述矩阵中我们可以直接利用 A与B来进行,令近平面上的点深度值为0,远平面上的点深度值为1:

最终的透视矩阵:

\[\begin{bmatrix}
\frac{1}{r\tan \frac{Fov}{2}} & 0 &0 &0 \\
0& \frac{1}{\tan \frac{Fov}{2}}&0 &0 \\
0& 0& \frac{f}{f-n} & \frac{-nf}{f-n} \\
0& 0& 1& 0
\end{bmatrix}\]

实际解决

注意这里我设置的相机以及顶点位置发生变化:

Eigen::Vector3f eye_pos = {0, 0, 0};

std::vector<Eigen::Vector3f> pos{{2, 0, 12}, {0, 2, 12}, {-2, 0, 12}};
r.set_projection(get_projection_matrix(45, 1, 0.1, 50));

这样设置就不会出现原来三角形倒置的问题了

因为按照原来的设置 z轴是朝外的 近平面原平面都设置为正 相当于相机朝向是z轴正方向 而三角形却在z轴负半轴方向 这样会产生问题

我觉得这样改会比网上那个直接改透视矩阵要简单一些

Eigen::Matrix4f get_model_matrix(float rotation_angle)
{
Eigen::Matrix4f model = Eigen::Matrix4f::Identity(); // TODO: Implement this function
// Create the model matrix for rotating the triangle around the Z axis.
// Then return it.
float Cos = cos(rotation_angle / 180.f * MY_PI);
float Sin = sin(rotation_angle / 180.f * MY_PI);
model << Cos, -Sin, 0, 0,
Sin, Cos, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1;
return model;
} Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio,
float zNear, float zFar)
{
// Students will implement this function Eigen::Matrix4f projection = Eigen::Matrix4f::Identity();
float TanFov = tan((eye_fov / 2) / 180.f * MY_PI); projection << 1 / (aspect_ratio * TanFov), 0, 0, 0,
0, 1 / TanFov, 0, 0,
0, 0, zFar / zFar - zNear, -zFar * zNear / zFar - zNear,
0, 0, 1, 0; return projection;
}

效果展示:

作业二

理论分析

整个代码框架和作业一变化不大

最大的差别就是将之前使用画线算法绘制线框 改为 实际填充像素光栅化 即

draw函数的变化

整个绘制过程如下:

1.找到三角形图元的boundingbox

2.判断范围内每个像素块是否在三角形内(使用叉积判断)叉积得到的是一个三维向量 我们应该使用z坐标来判断(xy平面上做叉积得到的是一个垂直于xy平面的向量)如果三个叉积的结果同号 则说明点(像素块中心点)在三角形内

3.使用面积比例计算得到重心坐标

4.使用重心坐标插值得到三角形内像素点的深度 这里要进行透视校正插值 但是原代码的方法是有错误的 应该使用三维空间中的正确深度值 而不是像素空间被压缩之后的深度值 详细说明见:https://www.cnblogs.com/dyccyber/p/17873365.htmlhttps://zhuanlan.zhihu.com/p/509902950

5.进行深度测试

实际解决

覆盖测试:

这里我直接计算了z坐标 没有整体计算叉积

static bool insideTriangle(float x, float y, const Vector3f* _v)
{
// TODO : Implement this function to check if the point (x, y) is inside the triangle represented by _v[0], _v[1], _v[2]
Vector2f v0P(x - _v[0].x(), y - _v[0].y());
Vector2f v1P(x - _v[1].x(), y - _v[1].y());
Vector2f v2P(x - _v[2].x(), y - _v[2].y());
Vector2f v0v1(_v[1].x() - _v[0].x(), _v[1].y() - _v[0].y());
Vector2f v1v2(_v[2].x() - _v[1].x(), _v[2].y() - _v[1].y());
Vector2f v2v0(_v[0].x() - _v[2].x(), _v[0].y() - _v[2].y());
float Xp0 = v0v1.x() * v0P.y() - v0v1.y() * v0P.x();
float Xp1 = v1v2.x() * v1P.y() - v1v2.y() * v1P.x();
float Xp2 = v2v0.x() * v2P.y() - v2v0.y() * v2P.x();
return (Xp0 < 0 && Xp1 < 0 && Xp2 < 0) || (Xp0 > 0 && Xp1 > 0 && Xp2 > 0); }

屏幕空间光栅化:

这里我使用了4xssaa进行抗锯齿 要建立一个四倍的framebuffer与depthbuffer 依次对每个采样点进行覆盖与深度测试 然后求平均颜色

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});
std::fill(frame_sample.begin(), frame_sample.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 * 4);
frame_sample.resize(w * h * 4);
helper[0].x() = 0.25;
helper[0].y() = 0.25; helper[1].x() = 0.75;
helper[1].y() = 0.25; helper[2].x() = 0.25;
helper[2].y() = 0.75; helper[3].x() = 0.75;
helper[3].y() = 0.75; }
void rst::rasterizer::rasterize_triangle(const Triangle& t) {
auto v = t.toVector4();
int XMin = std::min(std::min(v[0].x(), v[1].x()), v[2].x());
int XMax = std::max(std::max(v[0].x(), v[1].x()), v[2].x());
int YMin = std::min(std::min(v[0].y(), v[1].y()), v[2].y());
int YMax = std::max(std::max(v[0].y(), v[1].y()), v[2].y());
for (int x = XMin; x < XMax; x++) {
for (int y = YMin; y < YMax; y++) {
int index = get_index(x, y) * 4;
for (int i = 0; i < 4; i++) {
if (insideTriangle(x + helper[i].x(), y + helper[i].y(), t.v)) {
auto [alpha, beta, gamma] = computeBarycentric2D(x + helper[i].x(), y + helper[i].y(), t.v);
float w_reciprocal = 1.0 / (alpha / v[0].w() + beta / v[1].w() + gamma / v[2].w());
float z_interpolated = alpha * v[0].z() / v[0].w() + beta * v[1].z() / v[1].w() + gamma * v[2].z() / v[2].w();
z_interpolated *= w_reciprocal;
if (z_interpolated < depth_buf[index+i]) {
depth_buf[index+i] = z_interpolated;
frame_sample[index+i] = t.getColor();
}
}
}
frame_buf[index / 4] = (frame_sample[index] + frame_sample[index + 1] + frame_sample[index + 2] + frame_sample[index + 3]) / 4; }
} }

games101 作业1及作业2分析及解决的更多相关文章

  1. 【1414软工助教】团队作业10——复审与事后分析(Beta版本) 得分榜

    题目 团队作业10--复审与事后分析(Beta版本) 往期成绩 个人作业1:四则运算控制台 结对项目1:GUI 个人作业2:案例分析 结对项目2:单元测试 团队作业1:团队展示 团队作业2:需求分析& ...

  2. 实验作业:使gdb跟踪分析一个系统调用内核函数

    实验作业:使gdb跟踪分析一个系统调用内核函数(我使用的是getuid) 20135313吴子怡.北京电子科技学院 [第一部分] 根据视频演示的步骤,先做第一部分,步骤如下 ①更新menu代码到最新版 ...

  3. 集美大学1414班软件工程个人作业2——个人作业2:APP案例分析

    一.作业链接 个人作业2:APP案例分析 二.博文要求 通过分析你选中的产品,结合阅读<构建之法>,写一篇随笔,包含下述三个环节的所有要求.  第一部分 调研, 评测 下载软件并使用起来, ...

  4. 【第二次个人作业】结对作业Core第一组:四则运算生成PB16061082+PB16120517

    [整体概况] 1.描述最终的代码的实现思路以及关键代码. 2.结对作业两个人配合的过程和两个人分工. 3.API接口文档和两个组的对接. 4.效能分析,优化分析和心得体会. [代码实现] 一. 实现功 ...

  5. 2003031121-浦娟-python数据分析第四周作业-第二次作业

    项目 内容 课程班级博客链接 20级数据班(本) 作业链接 Python第四周作业第二次作业 博客名称 2003031121-浦娟-python数据分析第四周作业-matolotlib的应用 要求 每 ...

  6. 2003031118—李伟—Python数据分析第四周作业—第二次作业

    项目 matplotlib的使用 课程班级博客链接 班级博客 这个作业要求链接 作业要求 博客名称 2003031118-李伟-Python数据分析第四周作业-第二次作业 要求 每道题要有题目,代码( ...

  7. [福大软工] Z班 团队作业——UML设计 作业成绩

    团队作业--UML设计 作业链接 http://www.cnblogs.com/easteast/p/7745703.html 作业要求 1)团队分工(5分) 描述团队的每个成员分别完成了UML图的哪 ...

  8. 团队小组NABCD(通用作业和个人作业)特点

    NABCD框架(通用作业和个人作业): N(need,需求): 你的创意解决了用户的什么需求? 使用户能够很好的区分作业情况,将班里所有同学的作业和自己私人的作业分开,通用作业指在一个班一同上课的公共 ...

  9. Mybatis关联查询和数据库不一致问题分析与解决

    Mybatis关联查询和数据库不一致问题分析与解决 本文的前提是,确定sql语句没有问题,确定在数据库中使用sql和项目中结果不一致. 在使用SpringMVC+Mybatis做多表关联时候,发现也不 ...

  10. C#中异常:“The type initializer to throw an exception(类型初始值设定项引发异常)”的简单分析与解决方法

    对于C#中异常:“The type initializer to throw an exception(类型初始值设定项引发异常)”的简单分析,目前本人分析两种情况,如下: 情况一: 借鉴麒麟.NET ...

随机推荐

  1. CAT监控指标

    CAT监控指标 CAT 是基于 Java 开发的实时应用监控平台.官方文档:https://github.com/dianping/cat CAT提供以下几种报表:Transaction报表 一段代码 ...

  2. C++与Unity C#交互

    C++与Unity C#交互 C++转C#小工具:https://github.com/jaredpar/pinvoke-interop-assistant C++ Custom.h #pragma ...

  3. Abp vNext 模块化系统简单介绍

    怎么使用模块1. 建立模块直接的依赖关系,可以通过DependsOnAttribute特性来确定依赖关系2. 先配置模块,实现为模块填充数据和功能设置.3. 使用模块提供的功能接口 怎么定义模块1. ...

  4. Vim编辑的小技巧

    Vim编辑的小技巧 如何快速纠错 Ctrl + h 删除上一个字符, Ctrl + w 删除上一个单词, Ctrl + u 删除当前行. 从编辑模式快速切换到Nornal模式 1.Esc 2.Ctrl ...

  5. win10系统常用命令(netstat、ping、telnet、sc、netsh命令)

    netstat命令 1. 查找端口占用 netstat -ano netstat -ano | findstr 5000 ping命令 ping 192.168.1.1 ping baidu.com ...

  6. TI AM64x工业核心板硬件说明书(双核ARM Cortex-A53 + 单/四核Cortex-R5F + 单核Cortex-M4F,主频1GHz)

    1          硬件资源 创龙科技SOM-TL64x是一款基于TI Sitara系列AM64x双核ARM Cortex-A53 + 单/四核Cortex-R5F + 单核Cortex-M4F设计 ...

  7. windows10 iis 环境下部署 asp.net core 应用程序的步骤

    1.运行powershell,在运行窗口中输入:powershell,点回车,如下图: 2.安装choco,在打开的powershell窗口中输入:Set-ExecutionPolicy Bypass ...

  8. MES 与 PLC 的几种交互方式

    在 MES 开发领域,想要从 PLC 获取数据就必须要和 PLC 有信号交互.高效准确的获取 PLC 数据一直是优秀 MES 系统开发的目标之一.初涉相关系统开发的工程师往往不能很好的理解 PLC 和 ...

  9. oeasy教您玩转vim - 39 - # 剪切粘贴

    ​ 剪切粘贴 回忆上节课内容 我们大幅度地复习了整个 motion: 直接运动 h j k l 行运动 首行g g 末行G 第n行n G 单词运动 wbe w 是到下一个 word 的开头 b 是到当 ...

  10. 我从 Python 潮流周刊提取了 800 个链接,精选文章、开源项目、播客视频集锦

    你好,我是豌豆花下猫.前几天,我重新整理了 Python 潮流周刊的往期分享,推出了第 1 季的图文版电子书,受到了很多读者的一致好评. 但是,合集和电子书的篇幅很长,阅读起来要花不少时间.所以,为了 ...