Games 101 作业1代码解析

推荐一下大佬

Fijiisland:

https://www.cnblogs.com/1Kasshole/p/13993749.html,他这个清晰的多了,也感谢他的授权。我这个就是简单写写自己的见解。。。

 

假定我们不从命令行输入函数,先从main函数入手吧

 1 int main(int argc, const char** argv)
 2 {
 3     float angle = 0;
 4     bool command_line = false;
 5     /*坐标轴顺序:
 6     *    x
 7     * z     y
 8     */
 9     rst::rasterizer r(700, 700); // 初始化光栅化器的实例r,且定义显示大小为700*700
10     Eigen::Vector3f eye_pos = { 0, 0, -2 };//摄像机的位置
11     std::vector<Eigen::Vector3f> pos{ {1,-1,2}, {0,1,2}, {-1,-1,2} };//三角形的位置,硬编码
12     std::vector<Eigen::Vector3i> ind{ {0,1,2} };//深度信息?
13 
14     auto pos_id = r.load_positions(pos);//actually is int, but for type-safety
15     auto ind_id = r.load_indices(ind);//like above
16 
17     int key = 0;
18     int frame_count = 0;
19 
20     while (key != 27) //就是esc
21     {
22         r.clear(rst::Buffers::Color | rst::Buffers::Depth);
23 
24         r.set_model(get_model_matrix(angle));
25         r.set_view(get_view_matrix(eye_pos));
26         r.set_projection(get_projection_matrix(45, 1, 1, 5));
27         //above are matrixs` set-up, which is already talk above it
28 
29         r.draw(pos_id, ind_id, rst::Primitive::Triangle);
30 
31         cv::Mat image(700, 700, CV_32FC3, r.frame_buffer().data());
32         image.convertTo(image, CV_8UC3, 1.0f);
33         cv::imshow("image", image);
34         key = cv::waitKey(10);
35 
36         std::cout << "frame count: " << frame_count++ << '\n';
37 
38         if (key == 'a') {
39             angle -= 10;
40         }
41         else if (key == 'd') {
42             angle += 10;
43         }
44     }
45     return 0;
46 } 

这里有很多自定义的函数,我们先不去解析,先来看看这个东西会输出什么图像,下图。有可能和本来的代码不太一样,我改了改颜色和大小。

这玩意实际是这个样子的,看下图(https://www.geogebra.org/m/jx2ezkwg),其中红色是x轴,绿色是y轴,蓝色是z轴。

现在我们再回过头去观察这个代码。首先我们第一个注意到的就是这个奇怪的在namespace rst的rasterizer了,深入看看这个是什么。先看rasterizer吧,也就是光栅器。

 1 class rasterizer
 2 {
 3 public:
 4     rasterizer(int w, int h);
 5     pos_buf_id load_positions(const std::vector<Eigen::Vector3f>& positions);
 6     ind_buf_id load_indices(const std::vector<Eigen::Vector3i>& indices);
 7     col_buf_id load_colors(const std::vector<Eigen::Vector3f>& colors);
 8 
 9     void set_model(const Eigen::Matrix4f& m);
10     void set_view(const Eigen::Matrix4f& v);
11     void set_projection(const Eigen::Matrix4f& p);
12 
13     void set_pixel(const Eigen::Vector3f& point, const Eigen::Vector3f& color);
14 
15     void clear(Buffers buff);
16 
17     void draw(pos_buf_id pos_buffer, ind_buf_id ind_buffer, col_buf_id col_buffer, Primitive type);
18 
19     std::vector<Eigen::Vector3f>& frame_buffer() { return frame_buf; }
20 
21 private:
22     void draw_line(Eigen::Vector3f begin, Eigen::Vector3f end);
23 
24     void rasterize_triangle(const Triangle& t);
25 
26 private:
27     Eigen::Matrix4f model;
28     Eigen::Matrix4f view;
29     Eigen::Matrix4f projection;
30 
31     std::map<int, std::vector<Eigen::Vector3f>> pos_buf;
32     std::map<int, std::vector<Eigen::Vector3i>> ind_buf;
33     std::map<int, std::vector<Eigen::Vector3f>> col_buf;
34 
35     std::vector<Eigen::Vector3f> frame_buf;
36 
37     std::vector<float> depth_buf;
38     int get_index(int x, int y);
39 
40     int width, height;
41 
42     int next_id = 0;
43     int get_next_id() { return next_id++; }
44 };

其实基本上看函数名字和变量的名字就大概知道这些东西是干嘛的了,不过有一部分函数我觉得很重要,得拿出来bb两句。

变量:

1 std::map<int, std::vector<Eigen::Vector3f>> pos_buf;
2 std::map<int, std::vector<Eigen::Vector3i>> ind_buf;
3 std::map<int, std::vector<Eigen::Vector3f>> col_buf;
4 std::vector<Eigen::Vector3f> frame_buf;
5 std::vector<float> depth_buf;

函数:

void draw(pos_buf_id pos_buffer, ind_buf_id ind_buffer, col_buf_id col_buffer, Primitive type);
void draw_line(Eigen::Vector3f begin, Eigen::Vector3f end);
void rasterize_triangle(const Triangle& t);

 

不过,为了解释这些函数,还得先看完main的过程。我们还是看这个constructor吧。

1 rst::rasterizer::rasterizer(int w, int h) : width(w), height(h)
2 {
3     frame_buf.resize((long(w)) * h);
4     depth_buf.resize((long(w)) * h);
5 }

是的,就这么几句话。这个构造函数是干什么的呢?我们注意到其中的两个变量:frame_buf和depth_buf。先不去管depth_buf,这个东西目前除了clear和这个constructor之外别的地方没用到。我们看frame_buf。变量字如其名,就是一帧的缓存。这就是画布:整个画面就是从这里呈现的,至于之后opencv的image函数是怎么处理的,这里暂且不表,我也不会。

回去main那里,接下来就是设置一些信息:

1 Eigen::Vector3f eye_pos = { 0, 0, -2 };//摄像机的位置
2 std::vector<Eigen::Vector3f> pos{ {1,-1,2}, {0,1,2}, {-1,-1,2} };//三角形的位置,硬编码,整体视为一个model
3 std::vector<Eigen::Vector3i> ind{ {0,1,2} };//深度信息?

 

抱歉的是第三个inditation这个我着实不知道是干嘛的,我猜测可能是和深度信息,做遮挡的时候有关。

接着往下看,就是这个load系列函数了。

 1 auto pos_id = r.load_positions(pos);//就是int,不过为了类型安全 2 auto ind_id = r.load_indices(ind);//如同上面那样 

auto可以被推导出来,分别是rst::pos_buf_id和rst::ind_buf_id。这两个又是什么呢?    

 1 struct pos_buf_id{int pos_id = 0;}; 2 struct ind_buf_id{int ind_id = 0;}; 

其实就是两个被包装好的int,这里源代码也解释了,是为了防止混用,或者说,类型安全。可能还有方便继承什么的,不过在这里没有体现出来。

那这两行代码到底是干什么的呢?

 1 rst::pos_buf_id rst::rasterizer::load_positions(const std::vector<Eigen::Vector3f>& positions)
 2 {
 3     auto id = get_next_id();
 4     pos_buf.emplace(id, positions);
 5     return { id };
 6 }
 7 
 8 rst::ind_buf_id rst::rasterizer::load_indices(const std::vector<Eigen::Vector3i>& indices)//返回
 9 {
10     auto id = get_next_id();
11     ind_buf.emplace(id, indices);
12     return { id };
13 }

 

通过内部维护一个int next_id = 0; int get_next_id() { return next_id++; }这样的函数,来保证每个model和inditation都是不同且不会被混用的。这在接下来的绘制中有用,不过并没有被体现出来。由于这里只有一个三角形model,因此全程这个pos_buf是只有一个的,同理于ind_buf。

回来main这里,就是两个变量了:key和frame_count。key就是键盘信号的表示,frame_count就是计数已经有多少个这样的帧被显示了。开始看while循环吧。

 1 while (key != 27) //就是esc
 2 {
 3     r.clear(rst::Buffers::Color | rst::Buffers::Depth);
 4 
 5     r.set_model(get_model_matrix(angle));
 6     r.set_view(get_view_matrix(eye_pos));
 7     r.set_projection(get_projection_matrix(45, 1, 1, 5));
 8     //above are matrixs` set-up, which is already talk above it
 9 
10     r.draw(pos_id, ind_id, rst::Primitive::Triangle);
11 
12     cv::Mat image(700, 700, CV_32FC3, r.frame_buffer().data());
13     image.convertTo(image, CV_8UC3, 1.0f);
14     cv::imshow("image", image);
15     key = cv::waitKey(10);
16 
17     std::cout << "frame count: " << frame_count++ << '\n';
18 
19     if (key == 'a') {
20         angle -= 10;
21     }
22     else if (key == 'd') {
23         angle += 10;
24     }
25 }

 

clear函数出现了,但是似乎它的参数十分令人费解,我们进去看一下。可以发现这个仍然是在namespace rst里面的东西,和rasterizer类一样:

 1 enum class Buffers
 2 {
 3     Color = 1,
 4     Depth = 2
 5 };
 6 
 7 inline Buffers operator|(Buffers a, Buffers b)
 8 {
 9 return Buffers((int)a | (int)b);
10 }
11 
12 inline Buffers operator&(Buffers a, Buffers b)
13 {
14     return Buffers((int)a & (int)b);
15 }

枚举类Buffer指示了什么和什么对应,并同时定义了一些运算。看向二进制,1就是0001,2就是0010,正好错开,这是一个很有用的特性,之后有用。其实就是clear要用到。下面看看函数吧:

 1 void rst::rasterizer::clear(rst::Buffers buff)//初始化函数
 2 {
 3     if ((buff & rst::Buffers::Color) == rst::Buffers::Color)//若当前的buff有指示颜色信息
 4     {
 5         std::fill(frame_buf.begin(), frame_buf.end(), Eigen::Vector3f{ 0,0,0 });//屏幕像素全部统一为黑色
 6     }
 7     if ((buff & rst::Buffers::Depth) == rst::Buffers::Depth)//若当前的buff有指示深度信息
 8     {
 9         std::fill(depth_buf.begin(), depth_buf.end(), std::numeric_limits<float>::infinity());//所有像素全部归于无穷远
10     }
11 }

 

回顾之前讲的,std::vector<Eigen::Vector3f> frame_buf;,其中Vector3f作为RGB值出现,std::vector<float> depth_buf;就是深度信息了(老实说这部分我不是太懂)。剩下的,我想注释应该就能解释清楚了,如果不明白还请评论指出。注意位运算。回来main这里。

1 r.set_model(get_model_matrix(angle));
2 r.set_view(get_view_matrix(eye_pos));
3 r.set_projection(get_projection_matrix(45, 1, 1, 5));

这就是三个矩阵了:model,view和projection matrices了。我想这里也需要一些篇幅来讲,不过网上资料应该挺多,我就略过去吧。

算了还是讲讲吧(逃

顺便给我自己整理一下。

暂且不论几何是怎么生成的,模型是怎么着色的,我们先来谈谈到底我们怎么光栅化(raster)一个场景。让我们假设一下,现在我们有一个任务,我们要把一个场景显示到屏幕上。比如农药的场景是怎么渲染到屏幕的?当然现在就来搞农药也太快进了,我也不会,还是先来关注一下这个三角形模型吧。

如你所见,上面那个就是我们的目标了,我们的眼睛就是摄像机,我们要想办法看到这个三角形。由此,我们可以引申出一些问题:我们要怎么看?我们看到的是怎么样的三角形?颜色暂且不表,关键是我们会怎么看到?

这个问题事实上可以简化。与Games 101作业一的渲染顺序一致,我们先不去管这个“线”——后面自有所谓的“Bresenham的画线算法”来处理,等下会讲,我们现在处理一些更为简化的情况——我们管“点“。我们想知道这个点是怎么被我们的眼睛看到的;毕竟,在这个十分简化的模型里面,我们都有了点,三角形是很好建立的。

也许你会说:我直接在摄像机所在平面那里竖一块平面, 比如这样:

这样可以吗?当然可以,事实上这就是一种投影方式了,我们称之为正交投影(Orthogonal projection),具体的严格定义我就不讲了,简单来说就空间中的两根平行线,从那个平面捕捉到的全是平行的。但是生活经验告诉我们,这是不对的。想象一下,我们平常是不是有句话说“近大远小”?看向下图,这是不是更像是人们平常看到的东西?他感觉是平行的,但图片上是相交的,事实上又是平行的。

这就要牵扯到一些东西了:我们人类看到的世界是怎么样的?想象自己的眼睛是一个发射线光的灯泡,然后对于那三个点而言,碰到就是直接原路返回——还请不要在这里杠什么光的波粒二象性和点的理想模型,这就是个比喻——这样也许会更加接近于真实人类的观察方式。当然这个理解有许多的偏颇和不恰当之处,不过也为我们引入透视投影(Perspective projection)打下了一点点感性的印象。



什么是透视投影呢?

如上图左边所示,这就是透视投影了,右边是正交投影。我们选取一个点作为消失点,反向的去画线,找model。找到之后,线直接原路返回,物体被直接投射到眼睛上,当它们遇到眼睛前面的视屏时就会被画出来。就如同图里面做的那样。这样也就完成了光栅化。

那直接干啊,你看,点都有了,model也有了,干嘛那么麻烦还搞什么矩阵什么运算的,直接算!好想法少年,但有个问题——你看向哪里?你想全部渲染然后慢慢挑?

离线渲染(大概是指不像游戏一样一秒多少多少帧那样,而是几十分钟几十天一帧)也就罢了,实时渲染经得起这样耗?我仿佛已经听到了显卡的哀嚎和香气,想必很快就能上菜了吧。开个玩笑。这样无限制的渲染当然是不可取的,所以我们得限制范围。我们要限制一定的空间。我们要看向一个方向,在一定的空间内把里面的物体渲染出来。

(咕咕咕,预计部分:为什么要完成三大矩阵,剩下的函数解析,Bresenham`s line drawing algorithm)

(另外本人水平有限,若有不足之处还请海涵,若您能指出那就再好不过了,感谢。)

posted @ 2021-02-02 23:02  Lemon-GPU  阅读(627)  评论(0编辑  收藏  举报