从GAMES101作业1看光栅化器框架:一个三角形背后的渲染管线全解析
从GAMES101作业1看光栅化器框架一个三角形背后的渲染管线全解析在计算机图形学的入门阶段GAMES101课程作业1往往成为许多学习者接触真实渲染管线的第一道门槛。这个看似简单的任务——在屏幕上绘制一个旋转的三角形——实则蕴含了现代GPU渲染管线的核心思想。本文将带您深入剖析这个教学用光栅化器框架揭示从顶点数据到屏幕像素的完整转换流程。1. 光栅化器框架的整体架构这个教学框架虽然精简却完整呈现了图形渲染管线的关键组件。让我们先来看框架的主要构成模块核心数据结构Eigen::Matrix4f用于存储4x4变换矩阵Eigen::Vector3f三维向量表示顶点位置或颜色frame_buf帧缓冲区存储最终像素颜色depth_buf深度缓冲区用于后续可能的深度测试主要功能模块// 矩阵变换相关 Eigen::Matrix4f get_model_matrix(float rotation_angle); Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar); // 渲染管线核心 void draw(pos_buf_id pos_buffer, ind_buf_id ind_buffer, Primitive type); void rasterize_wireframe(const Triangle t); void draw_line(Eigen::Vector3f begin, Eigen::Vector3f end);这个框架的设计体现了经典图形管线的分层思想高层负责几何变换中层处理图元装配底层实现像素级操作。虽然省略了现代GPU中的许多优化但保留了最核心的管线阶段。2. 从顶点到屏幕完整的变换流程2.1 模型-视图-投影(MVP)变换作业要求实现的第一个关键部分就是构建MVP矩阵链。让我们看看这个过程中每个矩阵的作用模型变换Eigen::Matrix4f get_model_matrix(float rotation_angle) { Eigen::Matrix4f model Eigen::Matrix4f::Identity(); double fangle rotation_angle / 180 * MY_PI; Eigen::Matrix4f rotation; rotation cos(fangle), -sin(fangle), 0, 0, sin(fangle), cos(fangle), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1; model rotation * model; return model; }这个绕Z轴旋转的矩阵是模型变换的最简形式实际应用中可能包含更复杂的变换组合。投影变换Eigen::Matrix4f get_projection_matrix(float eye_fov, float aspect_ratio, float zNear, float zFar) { Eigen::Matrix4f projection Eigen::Matrix4f::Identity(); Eigen::Matrix4f proj, ortho; proj zNear, 0, 0, 0, 0, zNear, 0, 0, 0, 0, zNear zFar, -zNear * zFar, 0, 0, 1, 0; double h zNear * tan(eye_fov / 2) * 2; double w h * aspect_ratio; double z zFar - zNear; ortho 2 / w, 0, 0, 0, 0, 2 / h, 0, 0, 0, 0, 2 / z, -(zFarzNear) / 2, 0, 0, 0, 1; projection ortho * proj * projection; return projection; }这里实现了透视投影的经典两步法先透视变换再正交规范化。2.2 视口变换与光栅化MVP变换后顶点还需要经过视口变换才能映射到屏幕空间for (auto vec : v) { vec / vec.w(); // 透视除法 vert.x() 0.5*width*(vert.x()1.0); vert.y() 0.5*height*(vert.y()1.0); vert.z() vert.z() * f1 f2; }这个简单的视口变换完成了从NDC空间到屏幕坐标的映射。之后框架调用rasterize_wireframe进行线框渲染void rasterize_wireframe(const Triangle t) { draw_line(t.c(), t.a()); draw_line(t.c(), t.b()); draw_line(t.b(), t.a()); }3. 框架中的关键算法实现3.1 中点画线算法框架使用中点算法进行线段光栅化这是经典的Bresenham算法的一种实现void draw_line(Eigen::Vector3f begin, Eigen::Vector3f end) { auto x1 begin.x(); auto y1 begin.y(); auto x2 end.x(); auto y2 end.y(); int x,y,dx,dy,dx1,dy1,px,py,xe,ye,i; dxx2-x1; dyy2-y1; dx1fabs(dx); dy1fabs(dy); px2*dy1-dx1; py2*dx1-dy1; if(dy1dx1) { // 斜率绝对值小于等于1的情况 if(dx0) { xx1; yy1; xex2; } else { xx2; yy2; xex1; } Eigen::Vector3f point Eigen::Vector3f(x, y, 1.0f); set_pixel(point,line_color); for(i0;xxe;i) { xx1; if(px0) { pxpx2*dy1; } else { if((dx0 dy0) || (dx0 dy0)) { yy1; } else { yy-1; } pxpx2*(dy1-dx1); } Eigen::Vector3f point Eigen::Vector3f(x, y, 1.0f); set_pixel(point,line_color); } } else { // 斜率绝对值大于1的情况 // 类似处理以y为基准 } }这个算法通过整数运算高效地确定了线段经过的像素位置避免了浮点运算的开销。3.2 任意轴旋转的实现提高部分要求实现绕任意轴的旋转这需要用到罗德里格斯旋转公式Eigen::Matrix4f get_rotation(Vector3f axis, float angle) { double fangle angle / 180 * MY_PI; Eigen::Matrix4f I, N, Rod; Eigen::Vector4f axi; axi axis.x(), axis.y(), axis.z(), 0; I.Identity(); N 0, -axis.z(), axis.y(), 0, axis.z(), 0, -axis.x(), 0, -axis.y(), axis.x(), 0, 0, 0, 0, 0, 1; Rod cos(fangle) * I (1 - cos(fangle)) * axi * axi.transpose() sin(fangle) * N; Rod(3, 3) 1; // 修正齐次坐标的w分量 return Rod; }这个实现展示了如何将数学公式转化为可执行的矩阵运算是理解3D旋转本质的绝佳案例。4. 框架与现代GPU管线的对比虽然这个教学框架非常简化但与现代GPU渲染管线相比我们可以发现许多相似之处教学框架组件现代GPU对应阶段主要差异get_model_matrix顶点着色器中的模型变换GPU通常将这些变换合并到统一着色器get_projection_matrix投影变换阶段现代API通常提供内置投影矩阵draw_line光栅化阶段GPU使用更高效的硬件光栅化frame_buf帧缓冲区GPU有更复杂的多缓冲和混合机制这个简单框架省略了许多现代GPU特性如顶点着色器和片段着色器的可编程性深度测试和模板测试纹理采样和混合几何着色器和曲面细分然而它完美地展示了图形管线最基础的数据流动和变换过程。理解这个框架将为学习更复杂的渲染技术打下坚实基础。