3D数学 学习笔记(6) 图形管道(渲染流水线)
参考书籍:
《3D数学基础:图形与游戏开发》
《Unity Shader 入门精要》
浅墨_毛星云:【《Real-Time Rendering 3rd》 提炼总结】(二) 第二章 · 图形渲染管线 The Graphics Rendering Pipeline
概念性三个阶段
概念性是指按照渲染流程进行的功能划分提出的。GPU流水线才是硬件真正用于实现这个概念的流水线。
- 应用阶段(Application Stage):(CPU。输出渲染所需几何信息:渲染图元(点、线、三角面))
- 几何阶段(Geometry Stage):(GPU。绘制几何相关图元。)
- 光栅化阶段(Rasterizer Stage):(GPU。决定每个渲染图元哪些像素绘制在屏幕。)
应用阶段
即通过软件实现的阶段。最后输出渲染图元(点线面等)到几何阶段。
划分几个阶段:
- 把数据加载到显存中:场景信息(相机位置、视锥体、场景物体、光照与雾化、z-缓冲。
- 设置模型渲染状态:材质(漫反射颜色、高光反射颜色)、纹理、shader等。
- 调用Draw Call:告诉GPU开始渲染,指向本次需要渲染的图元列表。
超标量体系结构(superscalar):多个并行处理器同时执行。
硬盘(Hard Disk Drive, HDD) -> 系统内存(Random Access Memory, RAM) -> 显存(Video Random Access Memory, VRAM)。
Draw Call:
几何阶段
决定绘制什么图元、怎么绘制、在哪绘制。最后输出屏幕空间的二维顶点坐标、每个顶点对应的深度值、着色等信息到光栅化阶段。
划分几个阶段:
- 顶点着色器:顶点空间变化、逐顶点光照。顶点坐标从模型空间转换到其次裁剪空间,接着硬件做透视除法,得到归一化的设备坐标(Normalized Device Coordinates, NDC)。
- 曲面细分着色器:可选。细分图元。
- 几何着色器:可选。逐图元着色、产生图元。
- 投影:正交投影或透视投影。
- 裁剪:剔除不再屏幕的顶点,添加必要顶点。
- 屏幕映射:将图元坐标转到屏幕坐标。
光栅化阶段
将上一阶段传来的数据,产生屏幕上的像素,并渲染出最终图像。
划分为几个阶段:
- 三角形设置:计算三角形表面数据、边界像素坐标等。
- 三角形遍历(扫面变换):找到哪些像素在三角形中,生成片元(包含了屏幕坐标、深度信息、顶点信息、法线、纹理坐标等)。
- 片元着色器(像素着色器):从顶点着色器输出的数据插值得到颜色值。通过顶点对应的纹理坐标,用纹理采样进行插值,可以得到覆盖片元的纹理坐标。
- 逐片元操作(输出合并阶段):决定片元的可见性(模版测试、深度测试),然后将通过测试的片元颜色值和已经在颜色缓冲区的颜色混合(合并)。
模版测试(Stencil Test)
不透明物体可以开启混合操作:
为了避免看到正在光栅化的图元,GPU会使用双重缓冲(Double Buffering)的策略。就是在渲染在背后执行(后置缓冲),渲染好了之后切换另一个缓冲(前置缓冲)内容,保证看到的图像是连续的。
图形管道伪代码
// 设置场景的视图
setupTheCamera();
// 清除z-缓冲
clearZBuffer();
// 设置光源和雾化
setGlobalLightingAndFog();
// 得到场景视图中可见物体列表
potentiallyVisibleObjectList = highLevelVisibilityDetermination(scene);
// 渲染所有可见物体
for (all objects in potentiallyVisibleObjectList)
{
// 包尾盒检测,跳过包围盒内不可见物体。执行低级VSD(visible surface determination)检测。
if (!object.isBoundingVolumeVisible()) continue;
// 提取或程序化生成几何体
triMesh = object.getGeometry();
// 裁切和渲染面
for (each triangle in the geometry)
{
// 转换顶点到裁切空间,执行顶点级光照
clipSpaceTriangle = transformAndLighting(triangle);
// 裁切三角形(如果在边界)。
clippedTriangle = clipToViewVolume(clipSpaceTriangle);
if (clippedTriangle.isEmpty()) continue;
// 投影三角形到屏幕空间
screenSpaceTriangle = clippedTriangle.projectToScreenSpace();
// 背面剔除
if (screenSpaceTriangle.isBackFacing()) continue;
// 光栅化每个三角形
for (each pixel in the triangle)
{
// 跳过在屏幕后面的
if (pixel is off−screen) continue;
// 插值颜色,z-缓冲值和纹理映射坐标
// 执行zbuffering
if (!zbufferTest()) continue;
// 执行透明度检测
if (!alphaTest()) continue;
// 写入帧缓存和z-缓冲
writePixel(color, interpolatedZ);
}
}
}