函数核心目标
实现屏幕空间内三角形的光栅化,将三角形覆盖的像素点颜色填充到帧缓冲区,同时处理深度测试(Z-Buffer)。这是渲染管线中几何阶段到像素阶段的关键步骤
包围盒计算(Bounding Box)**
- 功能:确定三角形在屏幕空间的最小包围区域,减少无效像素遍历。
- 实现逻辑:
- 遍历三角形的三个顶点,取最小和最大的x、y坐标值,形成矩形包围盒。
遍历包围盒内像素**
- 功能:对包围盒内的每个像素进行“是否在三角形内”的测试。
- 实现逻辑:
- 对每个像素点
(x, y)
,调用insideTriangle
函数判断其是否在三角形内部
- 对每个像素点
若在三角形内,则进行深度插值和颜色填充
点是否在三角形内的判断**
- 算法:通过叉乘符号一致性测试(Cross Product Sign Test)
(4) 重心坐标与深度插值**
- 功能:通过重心坐标插值计算当前像素的深度值(Z值),用于深度测试
深度测试与颜色填充**
- 深度测试:比较当前像素的插值深度与深度缓冲区的值。若更小(更近),则更新深度缓冲区并填充颜色。
- 颜色设置:使用三角形颜色插值(作业2)或纹理采样(作业3)
,调用set_pixel
函数写入帧缓冲区。
关键函数调用链
- **
draw()
**:处理模型-视图-投影变换(MVP),将顶点转换到屏幕空间。 - **
rasterize_triangle()
**:执行光栅化逻辑。 - **
set_pixel()
**:将最终颜色写入帧缓冲区的指定位置。
注意事项
- 性能优化:包围盒可结合整数坐标计算,减少浮点运算误差。
- 抗锯齿:作业2为基础实现,实际渲染中可能需要超采样(MSAA)处理锯齿问题。
该函数用于在离散像素网格中绘制一条颜色为白色(RGB 255,255,255)的直线,基于经典的Bresenham直线算法
其核心逻辑是通过整数运算逐像素逼近理想直线,避免浮点运算以提高效率。算法根据斜率大小分为两种处理模式:
- 当斜率绝对值≤1(水平方向变化更大)时,以
x
轴为主步进方向。 - 当斜率绝对值>1(垂直方向变化更大)时,以
y
轴为主步进方向
t
是什么?
-
**
t
是三角形对象**:在光栅化器中,t
是Triangle
类的实例,表示一个需要被绘制的三角形。这个类通常包含以下信息:- 三个顶点的位置(
v[0]
,v[1]
,v[2]
) - 顶点颜色、纹理坐标等属性(取决于具体实现)
- 三个顶点的位置(
-
来源:在
rasterize_triangle(const Triangle& t)
函数中,t
是函数的参数,表示当前正在处理的三角形。
t.v[0]
、t.v[1]
、t.v[2]
是什么?
-
顶点数组:
t.v
是存储三角形三个顶点的数组,每个顶点是一个 三维向量(例如Eigen::Vector3f
),包含以下信息:- x():顶点的 x 坐标(屏幕空间或投影空间)
- y():顶点的 y 坐标
- z():顶点的深度值(用于深度缓冲)
-
**
pos_buffer
(顶点位置缓冲区)**- 类型:
rst::pos_buf_id
- 作用:存储三角形顶点的模型空间坐标。通过
pos_buffer.pos_id
从全局位置缓冲区中获取顶点数据,每个顶点为三维坐标(如Eigen::Vector3f
)。
- 类型:
-
**
ind_buffer
(索引缓冲区)**- 类型:
rst::ind_buf_id
- 作用:定义顶点如何组合成三角形。通过
ind_buffer.ind_id
获取索引数组,每组3个索引对应一个三角形的三个顶点(如i[0]
,i[1]
,i[2]
)。
- 类型:
-
**
type
(图元类型)**- 类型:
rst::Primitive
- 作用:指定渲染的图元类型。当前代码仅支持
Triangle
类型,其他类型会抛出异常。这是为了适配图形管线中三角形光栅化的特定需求
- 类型:
auto v = t.toVector4();
这一操作的作用是将三角形顶点从三维坐标转换为四维齐次坐标,其核心目的是为了支持透视投影下的正确深度插值和透视校正属性插值
在图形管线中,顶点经过MVP矩阵变换后会处于齐次裁剪空间(Homogeneous Clip Space),此时坐标是四维的(x, y, z, w)。通过调用 t.toVector4()
可以获取顶点在齐次空间中的完整信息
深度插值
-
问题背景
在透视投影中,物体“近大远小”的特性导致屏幕空间的均匀步长对应视图空间中的非线性步长。若直接对屏幕空间坐标线性插值,视图空间的深度值(z
)会失真
传统的GPU渲染流水线(管线)是基于光栅化的一套流程,之所以要强调传统,是为了将之区别于基于光线追踪(ray trace)的流水线和基于体素化的流水线。在光栅管线中,最基本的2个着色器是顶点着色器和像素着色器,在下图中,除了2个着色器可编程,中间三个时钟节点都是固定的,只能配置不可编程。
新的形状保持了一些特性:平行线仍然是平行的,各处密度均匀,原点不变。如果原点位置变化的话那就得加上平移,线性矩阵变成仿射矩阵
那什么是线性插值呢?即均匀地插值,比如线段的中点的插值一定是两端之和处以2,这个例子是一维的插值,多维也是类似。下图中列举了顶点色和顶点法线的线性插值
关于“密度”可以这样理解:在原始三角形上均匀的撒一些散点,待它被投影到屏幕三角形上之后,这些点是否仍然分布均匀?想象一下,很显然在正交投影的情况下,是均匀的,但透视投影中,距离相机近的部位散点更稀疏,远处的散点更密集。
所以我们要找到插值和插值点之间真正的函数关系,所以我引入了下面的视锥侧剖图:其中O点是摄像机,L是近截面,ax+bz=c是三角形。我们抽象一个虚拟的插值点t,范围是0~1,t从(P1,-e)出发,匀速运动至(p2,-e),t的值也匀速地从0增长至1。图中可以看出,近截面上的均匀散点反投影到三角形上时变得不均匀了,此外还能得出,插值点的x坐标P与t线性相关。
仿射矩阵是线性变换与平移变换的结合形式,其核心作用是将原本分离的线性操作(如旋转、缩放)和平移操作统一在一个矩阵框架下处理
FOV(视场角)的定义
FOV(Field of View),即视场角,用于描述光学设备(如摄像头、镜头、人眼等)能够捕捉到的最大可见范围,通常以角度(°)为单位.其本质是一个几何概念,类似于人眼的视野范围,但受设备硬件(如传感器尺寸、焦距)和光学设计的限制
-
未校正的插值
假设一个长条形纹理贴在一个倾斜的平面上,若直接线性插值,近处的纹理会被压缩,远处的会被拉伸,导致纹理扭曲(如棋盘格变成梯形)。 -
校正后的效果
通过透视校正,纹理在视图空间中保持均匀分布,屏幕空间中的非线性变化被抵消,纹理显示正确(如棋盘格保持正方形)
此阶段通过透视投影矩阵将顶点从视图坐标空间(观察坐标系)变换到裁剪空间(Clip Space)。该矩阵的作用包括:
- 近大远小:模拟人眼的视觉特征,使远处物体缩小,近处物体放大。
- 视锥体压缩:将视图空间中的视锥体(由近/远裁剪平面和视角定义的棱台)映射到规则观察体(Canonical View Volume),即边长为2的立方体(范围[-1,1])。
- 深度非线性处理:通过矩阵运算将视图空间的Z值转换为裁剪空间的W分量,为后续透视除法做准备
透视除法(齐次除法)
裁剪空间的顶点坐标需进行齐次除法(即各分量除以W分量),得到归一化设备坐标(NDC):
- 公式:
(Xndc, Yndc, Zndc) = (Xclip/Wclip, Yclip/Wclip, Zclip/Wclip)
- 深度非线性:NDC的Z轴范围[-1,1],但视图空间的Z值与NDC的Z值呈非线性关系,
- 模型空间 → 视图空间:通过视图矩阵(View Matrix)转换,处理摄像机位置与朝向。
- 视图空间 → 裁剪空间:应用透视投影矩阵,完成视锥体压缩。
- 裁剪空间 → NDC空间:齐次除法实现非线性映射。
- NDC空间 → 屏幕空间:视口变换适配显示设备。
三角形有很多特性,非常适合作为渲染的最小单位,如:
-
各顶点/各边在同一平面上
-
内部的点很好定义
-
三角形内的顶点之间插值容易实现(质心插值)
齐次坐标通过引入第四维 w,,w不是一个值,而是一个维度,如x,y,z的表示一样
支持透视投影与深度感知
在透视投影中,w 分量存储深度信息(如相机到物体的距离)。通过投影矩阵修改 w 的值(例如 w_clip = -z_eye
),后续透视除法(x/w, y/w, z/w
)能实现“近大远小”的视觉效果
例如,远处的物体因 w 值较大,其投影后的坐标会被压缩,符合人眼透视规律
顶点之间的插值指通过三角形三个顶点的已知属性(如颜色、纹理坐标、法线等),利用数学方法(如重心坐标)计算三角形内部任意点的属性值的过程
例如,已知顶点颜色为红、绿、蓝,插值后三角形内部会呈现平滑的渐变混合效果
为什么需要插值?
-
属性传递需求
三角形顶点仅存储少量属性(如位置、法线),但渲染时需要为每个内部像素(或片元)赋予属性值。插值通过顶点数据的加权混合,将离散顶点属性扩散到整个三角形表面
示例: 若顶点存储纹理坐标,内部像素的纹理坐标需通过插值计算,才能正确映射贴图
几何连续性的保证
三角形是平面多边形中最简单的形式,其线性插值特性(如重心坐标的非负性和归一化)能保证属性在三角形内部平滑过渡,避免突变
+1.0
将NDC坐标范围从[-1,1]映射到[0,2]0.5*width
将坐标缩放到屏幕实际像素尺寸,例如width=800时,x=1.0会被映射到800像素位置
- 将NDC的z值[-1,1]映射到深度缓冲区范围[0.1,50]
- 线性变换公式:zbuffer=(zndc×24.95)+25.05,确保深度值适配渲染管线的深度测试范围
head<3>
的含义
- 语法作用:
head<3>
是Eigen库的向量操作方法,表示取前3个分量 - 数值意义:当应用于
Eigen::Vector4f
(四维向量)时,vec.head<3>()
会返回一个三维向量(Eigen::Vector3f
) - 应用场景:
- 顶点坐标
v[i]
经过MVP变换和视口变换后是四维向量(x, y, z, w) - 但三角形顶点只需要三维坐标(x, y, z),因此需要截取前三个分量
- 顶点坐标
投影矩阵的约定
代码中f1=(50-0.1)/2.0
和f2=(50+0.1)/2.0
的推导源于投影矩阵的参数设置。这里的50和0.1分别对应视锥体的远(far plane)和近(near plane)平面距离,映射公式z' = z*f1 + f2
实际是线性变换:
zbuffer=2zndc+1⋅(far−near)+near
将NDC的[-1,1]线性映射到[near, far]的实际深度范围
在齐次除法中,除以vec.w()
是因为经过模型-视图-投影(MVP)矩阵变换后,顶点的齐次坐标的w
分量可能不再为1,尤其是当使用透视投影时。以下是关键点解析:
-
投影矩阵的作用:
- 透视投影矩阵会修改顶点的
w
分量,通常将其设置为顶点的原始z
值(或相关值)。例如,OpenGL的透视投影矩阵会将顶点变换到裁剪空间,此时w
分量变为-z
。 - 例如,应用如下投影矩阵后的顶点齐次坐标为:
(a*x, b*y, c*z + d, -z)
,此时w = -z
而非1。
- 透视投影矩阵会修改顶点的
-
齐次除法的必要性:
- 齐次除法(
vec /= vec.w()
)将裁剪空间坐标转换为归一化设备坐标(NDC),范围为[-1, 1]³
。 - 此步骤通过除以
w
实现透视校正,使远处的物体看起来更小(透视效果)。
- 齐次除法(
-
代码流程分析:
- **
mvp * to_vec4(buf[i[...]], 1.0f)
**:顶点初始化为(x, y, z, 1)
,但经过投影矩阵后w
被改变。 - 除以
vec.w()
:确保正确投影到NDC,无论w
是否被修改(如透视投影时)。
- **
-
正交投影的特殊情况:
- 若使用正交投影,投影矩阵通常保持
w = 1
,此时齐次除法无实际影响,但仍需统一处理。
- 若使用正交投影,投影矩阵通常保持
总结:即使顶点初始w
为1,投影矩阵会修改w
,因此必须进行齐次除法才能正确投影到屏幕空间。这是实现透视效果的关键步骤。