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

news/2024/11/17 17:45:23/文章来源:https://www.cnblogs.com/dyccyber/p/18350558

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);绘制 这里是用线框形式绘制 使用的画线算法是Bresenhamrasterize_wireframe(t);}
}

理论分析

img
贴一张大致的总结图
重点分析透视矩阵的推导
这里我介绍一下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:
img

最终的透视矩阵:

\[\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 functionEigen::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;
}

效果展示:
img

作业二

理论分析

整个代码框架和作业一变化不大
最大的差别就是将之前使用画线算法绘制线框 改为 实际填充像素光栅化 即
draw函数的变化
整个绘制过程如下:
1.找到三角形图元的boundingbox
2.判断范围内每个像素块是否在三角形内(使用叉积判断)叉积得到的是一个三维向量 我们应该使用z坐标来判断(xy平面上做叉积得到的是一个垂直于xy平面的向量)如果三个叉积的结果同号 则说明点(像素块中心点)在三角形内
3.使用面积比例计算得到重心坐标
4.使用重心坐标插值得到三角形内像素点的深度 这里要进行透视校正插值 但是原代码的方法是有错误的 应该使用三维空间中的正确深度值 而不是像素空间被压缩之后的深度值 详细说明见:https://www.cnblogs.com/dyccyber/p/17873365.html 与 https://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;}}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/780233.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ComfyUI插件:ComfyUI_Noise节点

前言: 学习ComfyUI是一场持久战,ComfyUI_Noise是对ComfyUI中的噪声进行控制的一个插件库,该库可以完成图像噪声的反推,并通过采样再以几乎无损的方式返回原图,通过该库的使用可以更好的帮助图像恢复原始的相貌,非常适合在生成视频领域用作人物转绘使用。祝大家学习顺利,…

初学Java5

对Java的基础内容有了一定的了解,学习编写一个比较全面的练习题:

远程控制游戏软件,ToDesk低延迟安全又稳定

远程控制软件极大程度方便了打工人的办公需求,但有时想要用来远程打打游戏,上游戏做一下日常签到之类的任务,普通的远控软件的配置却无法匹配,不是画质不够高清,就是延迟太高画面卡顿容易掉线。 这时候还是需要专业选手上场,比如ToDesk远程控制的游戏版。小社长使用了一段…

掀起云端革命!ToDesk云电脑与传统PC电脑的差异分析

在科技日新月异的今天,传统PC电脑的市场地位正悄然发生变化。随着云计算技术的不断成熟与普及,云电脑逐渐走进大众视野,不同于传统PC电脑的高昂的成本和易退化的硬件性能,云电脑正以其轻成本高性能的优势吸引来越来越多的用户。其中ToDesk云电脑的推出更是掀起了云端革命的…

浏览器标签页多行显示:使用Floorp浏览器 最先进的跨平台 Firefox 衍生品 开源之光

浏览器打开了很多标签页,查看需要滚动 这查找效率就不是O(1)了,比如在编辑器中标签页直接多行显示 找了解决方案 平常的主力浏览器是Firefox chrome safari firefox firefox之前有拓展Tab Mix可以用,现在弄得跟孙子似得,官方拓展不承认还要安装还要好的步骤,也没成功 还有…

怎么远程控制其他电脑手机?ToDesk轻松搞定!

远程控制电脑和手机一般是通过互联网或者局域网,将相隔两地的设备进行远控连接,主控设备可以操控被控设备的屏幕文件鼠标等等。 这种技术极大的方便了用户线上办公和日常生活,让用户可以在不同地点的电脑手机上进行远程办公、软件安装、故障排查等操作。本文小社长将介绍远程…

Linux三剑客之sed

工作原理sed是Stream Editor(流编辑器)的缩写,简称(行)流编辑器;用来处理文件的。首先sed读取文件中的一行内容,把其保存在一个临时内存空间中(也称为模式空间); 然后根据需求处理临时缓冲区中的行,完成后把该行发送到屏幕上; 默认不编辑源文件,只对模式空间做处理…

2024.7.28 模拟赛10

模拟赛 \(long\ long\ ago\)。。。 T1 Company Acquisitions 鞅的停时定理。 赛时应该不可做的吧。 手膜两组样例发现肯定不能用常规方法做。然后开始新科技。 势能函数!!! 设计一个势能函数去表示一种状态,这个势能函数要满足每操作一步势能减一,这样初势能减末势能就是期…

关于虚树

关于譃漱关于虚树 瞎扯 某些树上问题,给了巨多节点,而实际上它们之中只有小部分能做出贡献,其余都是些水军,为杀尽 OIers的脑细胞 做出努力 考虑重新种一棵树,浓缩信息,简化节点个数,于是产生了 虚树。 大概是长这个样子: 红色结点是我们选择的关键点,即能够做出贡献的…

OpenGauss部署案例之---OpenEuler 20.03部署OpenGauss企业版

OpenGauss、OpenEuler案例说明: 在OpenEuler20.03系统,x86架构下部署OpenGauss 5.0.1企业版单实例数据库。 **数据库版本: ** openGauss=# select version();version -------------------------------------------------------------------------------------------------…

预训练语言模型公平性-公平性度量、去偏方法

一、内在偏见与外在偏见  1、内在偏见:训练前数据集中存在的刻板印象; 2、外在偏见:用来衡量偏差如何在下游任务中传播。通常包括微调,然后评估其关于性别和种族等敏感属性的表现; 3、许多NLP应用程序对现有的语言模型进行了微调,这些模型将外在偏见和内在偏见交织在一…

航天科技 “小快轻准”解决方案助力中小企业数字化转型

数字化转型喊了很多年,中小企业为何对降本增效无动于衷?“数字化转型,是中小企业的必答题。” 但是面临动辄十几万甚至几十万上百万的改造成本,投入真金白银,存在回报期长,不确定性大的问题,所以成本受限成为一大绊脚石。除了资金问题,缺人缺技术缺数据也是转型的难点…