在计算机图形学中,渲染是根据模型描述在显示器上生成图像的过程。3D图形渲染管线输入根据图元顶点(如三角形、点、线和四边形)对3D模型的描述,并为显示器上的像素生成颜色值。
如下图所示的是3D图形渲染管线的流程。
3D图形渲染管线主要包含以下几个主要阶段:
在现代GPU中,Vertex Processing和Fragment Processing是可编程的,我们可以编写顶点着色器和片元着色器的程序来执行对顶点和片元的自定义变换。而Rasterization和Output Merging是不可编程的,是通过向GPU发出配置命令进行配置。
先讲一下顶点、图元、片元和像素这四个概念以及之间的关系。
这四者的生成是有先后顺序的:顶点>图元>片元>像素,生成顺序示意图如下图所示。
在计算机图形学中,顶点具有以下属性:
在OpenGL中,它是支持三类几何图元:点、线段和闭合多边形。 它们是通过顶点指定的,每个顶点都与其属性相关联,例如位置、颜色、法线和纹理。 如图所示,OpenGL 提供了 10 个图元。
在由顶点组成的图元中,其通常以浮点值表示的顶点不一定与显示器的像素网格对齐。因此在光栅化阶段,对由一个或多个顶点定义的每个图元进行光栅扫描,得到一组包含在图元中的片元,片元可以被视为与像素网格对齐的3D像素,从顶点插值的3D片段具有与顶点相同的属性,例如位置、颜色、法线和纹理。
光栅化阶段包括视口变换、裁剪、透视划分、背面剔除和扫描转换。 光栅器不是可编程的,但可以通过指令进行配置。
光栅化阶段完成后,还需要对片元进行处理。
下面分别讲述光栅化阶段中的几个重要步骤和片元处理。
视口是应用程序窗口上的一个矩形显示区域,以屏幕坐标(以像素为单位,原点在左上角)测量。 视口定义显示区域的大小和形状,以将相机捕获的投影场景映射到应用程序窗口。 它可能会或可能不会占据整个屏幕。
在OpenGL中,视口默认设置为覆盖整个应用程序窗口,我们可以使用 glViewport() 函数来选择较小的区域(例如,用于分屏或多屏应用程序)。
void glViewport(GLint xTopLeft, GLint yTopLeft, GLsizei width, GLsizei height)
我们还可以通过 glDepthRange() 设置视口的 z 范围
glDepthRange(GLint minZ, GLint maxZ)
视口变换,即将裁剪体积(2x2x1 长方体)映射到 3D 视口,如下图所示。它是由一系列仿射(y 轴)、缩放(x、y 和 z 轴)和平移(从裁剪体积的近平面中心到左上角的原点)组成 的 3D 视口。
如果视口覆盖整个屏幕,则 minX=minY=minZ=0,w=screenWidth 和 h=screenHeight。
Note:如果视口的纵横比和投影平面的纵横比不一样,形状就会发生变形。因此需要重新配置投影平面的纵横比以匹配视口的纵横比。
// Callback when the OpenGL's window is re-sized.
void reshape(GLsizei width, GLsizei height) { // GLsizei for non-negative integer
if (height == 0) height = 1; // To prevent divide by 0
GLfloat aspect = (GLfloat)width / (GLfloat)height; // Compute aspect ratio
// Set the viewport (display area on the window) to cover the whole application window
glViewport(0, 0, width, height);
// Adjust the aspect ratio of projection's clipping volume to match the viewport
glMatrixMode(GL_PROJECTION); // Select Projection matrix
glLoadIdentity(); // Reset the Projection matrix
// Either "perspective projection" or "orthographic projection", NOT both
// 3D Perspective Projection (fovy, aspect, zNear, zFar), relative to camera's eye position
gluPerspective(45.0, aspect, 0.1, 100.0);
// OR
// 3D Orthographic Projection (xLeft, xRight, yBottom, yTop, zNear, zFar),
// relative to camera's eye position.
if (width <= height) {
glOrtho(-1.0, 1.0, -1.0 / aspect, 1.0 / aspect, -1.0, 1.0); // aspect <= 1
} else {
glOrtho(-1.0 * aspect, 1.0 * aspect, -1.0, 1.0, -1.0, 1.0); // aspect > 1
}
// Reset the Model-View matrix
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
视锥剔除丢弃视锥外的对象,而背面剔除则丢弃不面向相机的图元。
可以根据法向量和连接表面和相机的向量来声明背面。
如果对象是透明的并且启用了 alpha 混合,则不应启用背面剔除。
光栅化后,每个图元都有一组片元,一个片有一个位置,它与像素网格对齐,具有深度、颜色、法线和纹理坐标,这些坐标是从顶点插值得到的。
片元处理侧重于纹理和光照,这两者对图形的质量影响最大。片元处理器涉及的操作有纹理化、结合原色和二次色应用雾计算。
点操作对应前言部分图片中的Vertex Processing,点操作可细分为3个变换,如下图所示,输入原始的点,经过3次坐标变换后,每个顶点连同它们的顶点法线一起变换并定位在 裁剪体积长方体空间中,其中x和y坐标(在-1到+1的范围内)代表它在屏幕上的位置,z值(在0到1的范围内)代表它的深度,即离近平面有多远;再通过光栅化阶段的视口变换最终得到屏幕空间下的点,显示在显示器上。
以上坐标变换涉及到4个转换:
3D 场景中的每个对象/模型通常在自己的坐标系中绘制,称为模型空间(或局部空间,或对象空间)。当我们组装对象时,我们需要将顶点从它们的局部空间转换到所有对象共有的世界空间,称为世界变换。 世界变换由一系列缩放(缩放对象以匹配世界的维度)、旋转(对齐轴)和平移(移动原点)组成。
旋转和缩放属于一类称为线性变换的变换(根据定义,线性变换保留向量加法和标量乘法)。 线性变换和平移形成所谓的仿射变换, 在仿射变换下,直线仍然是直线并且点之间的距离比率保持不变。
世界变换后,所有的物体都被转换到世界空间中, 我们现在将放置相机以捕捉视图。
定位相机:
在 3D 图形中,我们通过指定三个视图参数将相机定位到世界空间:世界空间中的 EYE、AT 和 UP。
在OpenGL中,在默认参数下,相机位于原点 (0, 0, 0),对准屏幕(负 z 轴),并面朝上(正 y 轴)。 如果使用默认参数,则必须将对象/物体放置在负 z 值处。
Note:在计算机图形学中,相对于固定相机移动对象(模型变换)和相对于固定对象移动相机(视图变换)产生相同的图像,因此是等价的。 因此,OpenGL 在所谓的模型-视图矩阵上以相同的方式管理模型变换和视图变换,投影变换(后面会讲)通过投影矩阵进行管理。
相机定位和定向后,我们需要决定它可以看到什么(类似于通过调整焦距和缩放系数来选择相机的视野),以及如何将对象/物体投影到屏幕上。 这是通过选择投影模式(透视或正交)并指定查看体积或裁剪体积来完成的。 裁剪体积之外的对象被裁剪出场景并且看不到。
在透视图中查看视锥体,相机的视野有限,呈现视锥体(截头金字塔),并由四个参数指定:fovy、aspect、zNear 和 zFar。
除了常用的透视投影外,还有一种所谓的正交投影(或平行投影),这是一种特殊情况,相机放置在离世界很远的地方(类似于使用望远镜镜头)。 正交投影的视体积是平行六面体(而不是透视投影中的平截头体)。
OpenGL渲染实际上就是将3D坐标进行系列转换变为2D坐标,整个流程如下:
除此之外,为了让最终结果和实际物体更接近, 会进行打光和贴纹理等操作。