type
status
date
slug
summary
tags
category
icon
password
视口变换
经过变换之后,需要将[-1,1]空间上的图形显示在屏幕上,这个过程就是光栅化。
首先需要定义屏幕,通常人们使用fovY(field-of-view,垂直可视角度)和宽高比(aspect radio)来描述屏幕

在游戏中设置的这些参数就可以推导出近平面的上下左右。b=-t,l=-r。
屏幕是由很多像素组成的,在抽象上,是一个最小单位。1920x1080指的是屏幕上有这么多个像素。像素的颜色通过红绿蓝,也就是RGB来表示。

像素可以通过整数得到它的位置,比如图中蓝色像素可以表示为(2,1)。像素的中心一般是(x+0.5,y+0.5),比如蓝色像素的中心就是(2.5,1.5)。
光栅化需要将变换得到的转化到这个像素屏幕上。
屏幕是二维的,变换得到的立方体是三维的,因此目前暂时不需要管z坐标。只需要看xy坐标。对于xy,需要将这个立方体内的物体缩放到和屏幕一样的宽高。另外屏幕左下角是(0,0),而立方体的中心为(0,0),因此还需要做一个平移变换。所以可以写出简单的矩阵
这个矩阵的变换也叫视口变换。
经过这个变换,就得到了一个场景的二维照片。接下来需要将这个图打散成像素,才可以显示在屏幕上。
三角形的光栅化
三角形可以表示三维空间中的物体也可以表示二维空间的物体。很多多边形都可以拆解成三角形,是最基础的多边形。
三角形还有一下的一些性质
- 一定是一个平面
- 内外的边界清晰
- 只需要定义三角形三个点的属性,在三角形内的某一个点根据它和三个点的关系很容易去做插值。

经过mvp变换和视口变换可以得到三角形在屏幕上的xy坐标,接下来需要做的是判断这个像素和三角形的位置关系。接下来需要用到采样。
采样
采样是将一个函数离散化的过程。需要对屏幕中[0,width][0,height]中所有的像素进行一次采样来判断这个像素是否在三角形内。通常用像素的中心点去做采样。

判断每个像素的中心是否在三角形内。可以通过一个函数,输入三角形的值t,屏幕中心像素的坐标x,y
对于inside函数判断一个点是否在三角形内,只需要做叉积即可。
有一个很常见的问题,如果遇到像素点在三角形边上,对于这个点是否在三角形内取决于自己的定义。opengl和directX定义如果点落在三角形的上边和左边都认为在三角形内,否则不在。在games101上不做这个处理。
上面判断点是否在三角形内遍历了所有的像素,这里可以做一个优化。

一个三角形通常只占屏幕空间的一个很小的范围。可以根据三角形做一个包围盒,只需要遍历包围盒里面的像素即可。

离散化得到的三角形由于采样率远低于信号导致锯齿很严重,这也是常说的走样。
反走样(anti-aliasing)
走样的本质是采样的速度跟不上函数(信号)的变化。

反走样通过给函数加上一个滤波,使得原本的三角形变得模糊,再进行采样来完成。
频域(frequency domain)

首先是熟悉的三角函数,通过调整x前的系数可以控制它们的频率。

接着还需要知道傅里叶级数展开。这里需要描述的函数是黑色的方块的线。是一条直线,然后三角函数是后面的。相加起来得到右边的三角函数曲线来描述这么一个方块的东西。

还可以引入更多的项来描述它,这里引入了绿色的函数,显然右边的描述比上面的更准确一点。

再引入更多的项得到的结果更加接近。

由上可得,给定一个函数经过一个复杂的傅里叶变换可以将它从实域得到它的频域

傅里叶函数通常用来将函数分解成不同的频率,并且可以将它们显示出来。假设每个一段时间进行采样,绿色是需要采样的函数,蓝色是连接各个采样点得到的结果。对于得到的采样的结果还是可以接受的,但是对于高频率的变化是无法得到一个正确的采样结果的,得到的结果是走样的。

想这里的两个函数,假设相同的频率采样蓝色和黑色的函数,得到的结果是完全一样的。但是蓝色和黑色的频率差别很大,这种现象也就是走样。
再进一步之前还需要了解一下滤波。滤波的作用是将某一个特定的频段过滤掉。

比如将一张图片做一个傅里叶变换,可以得到这个图片的频谱。对于一个图片有多少信息在这个频率上可以通过亮度来表示,这里的低频信号在中间,越往外频率越高。上图的信息大多在低频上。

假设对这个图片做一个高通滤波,只让高频率的信号通过,再做逆傅里叶变换得到图像。图像只剩下了人物的边界。边界就是两边的地方差了很多的一条界限,两边发生的变化很多,也说明信号变化大,这一块也就是高频信息,因此高通滤波之后得到的图像只剩下边界,能看出人的轮廓。

如果做一个低通滤波,只让低频率的信号通过,可以发现边界没有了,图片就会变得模糊。
卷积
滤波从另外一个角度来看也是平均或者叫卷积。

假设原始信号是一串数字,滤波是一个三个格子的窗口,每次都通过这个窗口进行对原始信号的进行一个点乘的操作,得到另一个信号。这个操作很像加权平均,也叫卷积。

对一个实域做卷积相当于让它们的频域相乘。通常将一个实域做一个傅里叶变换得到频域,再做一个卷积之后得到的频域之后进行逆傅里叶变换得到实域的结果。
比如上面的滤波是一个3x3的窗口,用这个窗口对图片做一个卷积操作,每一个像素是周围像素加上自己的平均。
采样

采样是将一个函数离散化,只需要函数一定频率下得到的值。函数a是需要采样的函数,函数c是每隔一段时间内采样的值,两个函数相乘得到的函数e就是每隔一段时间采样函数a的值,这个过程就是采样。而右边三个函数则是左边三个函数的实域经过傅里叶变换得到的频域。b卷积d得到的结果就是f。
可以发现f的频谱是将a的频谱再每个采样点复制粘贴了一次,得到了重复的a的频谱,因此采样也是重复原始信号的频谱。

而走样的原因是采样率低,实域上采样点之间的距离很大,在频谱上中间的间隔很小,使得复制的频谱重叠了起来。
因此走样的解决办法之一是增加采样率,但是并不现实。


常用的解决方法是对原本的信号做一个模糊(低通滤波),再去做一个采样,那么原本的采样率就能满足信息的采样了。这里的低通滤波可以是上面的每一个像素等于周围像素加上自己一共九个像素的平均。

可以通过一个像素内覆盖面积的不同,做一次平均得到正确的结果。

在实际中,是对一个像素里面划分成很多个像素,比如上面分成4x4,全部采样之后再做平均得到这个像素反走样的结果。这个也是MSAA。


这里的msaa的性能上的消耗是原本的四倍。
深度缓冲(z-buffer)
对于一张图片,要表示远近,可以用画家算法,先渲染远处的物体,再渲染近处的物体。渲染中可以对物体的z值排序,先渲染远处的物体来达到效果。

但是对于互相遮挡的物体很难定义他们的深度关系。

渲染的时候,不仅渲染这个图像还会维护一个深度图。前者(frame-buffer)会记录图像,后者(z-buffer)会记录它的深度。每次着色这个像素的时候如果发现它的深度比z-buffer上记录的深度要大就不会去着色它,否则着色并更新z-buffer。
- Author:lltouchingfish
- URL:https://tangly1024.com/article/1f7be847-332a-80b9-a98c-cc20fe4e7496
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!
Relate Posts