新建网页 1
图形管道概述
我们将讨论渲染一幅带有基本光照的单个图像的大体过程,这里不考虑动画和全局光照,如阴影和辐射度。
此外,注意这里只从概念上讲解通过图形管道的数据流,其顺序并不是固定的。实践中,我们也许会为了性能的优化而并行或乱序执行一些任务。比如,考虑到不同的渲染API,我们可能首先变换和照明所有顶点,然后才进一步的处理(进行裁剪和剔除),或者会并行处理二者,也可能在背面剔除之后再进行光照会得到更高效率。
还有一个我们将不详细讨论的要点,即工作负担如何在CPU与渲染硬件间分配。正确地组织渲染任务,以求得最大的并行效果对高效渲染是至关重要的。
考虑上述简化,就得到了图形管道中数据流的概况,如下所示:
(1)建立场景:开始渲染之前,需要预先设定对整个场景有效的一些选项。比如,要建立摄像机位置,或者更具体些,要选择进行渲染的出发点---视点,渲染的输出---视图。还需要设定光照与雾化选项,同时准备z缓冲。
(2)可见性检测:选好了摄像机,就必须检测场景中哪些物体是可见的。可见性检测对实时渲染极为重要,因为我们不愿意浪费时间去渲染那些根本看不到的东西。
(3)设置物体级的渲染状态:一旦发现某物体潜在可见,就到了把它实际绘制出来的时候。每个物体的渲染设置可能是不同的,在渲染该物体的任何片元之前,首先要设置上述选项,最常见的此类选项是纹理映射。
(4)几何体的生成与提交:接着实际向API提交几何体,通常提交的数据是种种形式的三角形,或是独立的三角形,或是索引三角网格与三角带。此阶段,我们可能会应用LOD,或者渐进式生成几何体。
(5)变换与光照:一旦渲染API得到了三角形数据,由模型空间向摄像机空间的顶点坐标转换与顶点光照计算即开始。
(6)背面剔除与裁剪:然后,那些背对摄像机的三角形被去除("背面剔除");三角形在视椎外的部分也被去除,称作裁剪---这可能导致产生多于三个边的多边形。
(7)投影到屏幕空间:在3D裁剪空间中经裁剪产生的多边形,被投影到输出窗口的2D屏幕空间里。
(8)光栅化:当把裁剪后的多边形转换到屏幕空间后,就到了光栅化阶段。光栅化指计算应绘制三角形上的哪些像素的过程,并为接下来的像素着色阶段提供合理的插值参数(如光照和纹理映射坐标)。
(9)像素着色:最后,在管道的最后阶段计算三角形的色彩,此过程称作"着色"。接着把这些颜色写至屏幕,这是可能需要alpha混合与z缓冲。
下面的伪代码描述了渲染管道,为了达到概观的目的,大量细节被省去了。同时,由于渲染平台和API的不同,实践中会有许多不同的形式。
Listing 15.1: Pseudocode for the graphics pipeline
// First, figure how to view the scene
setupTheCamera();
// Clear the zbuffer
clearZBuffer();
// Setup environmental lighting and fog
setGlobalLightingAndFog();
// Get a list of objects that are potentially visible
potentiallyVisibleObjectList = highLevelVisibilityDetermination(scene);
// Render everything we found to be potentially visible
for (all objects in potentiallyVisibleObjectList)
{
// Perform lower-level VSD using bounding volume test
if (!object.isBoundingVolumeVisible())
continue;
// Fetch or procedurally generate the geometry
triMesh = object.getGeometry()
// Clip and render the faces
for (each triangle in the geometry)
{
// Transform the vertices to clip space, and perform vertex-level lighting
clipSpaceTriangle = transformAndLighting(triangle);
// Is the triangle backfacing?
if (clipSpaceTriangle.isBackFacing()) continue;
// Clip the triangle to the view volume
clippedTriangle = clipToViewVolume(clipSpaceTriangle);
if (clippedTriangle.isEmpty())
continue;
// Project the triangle onto screen space and rasterize
clippedTriangle.projectToScreenSpace();
for (each pixel in the triangle)
{
// Interpolate color, zbuffer value, and texture mapping coords
// Perform zbuffering and alpha test
if (!zbufferTest())
continue;
if (!alphaTest())
continue;
// Shade the pixel.
color = shadePixel();
// Write to the frame buffer and zbuffer
writePixel(color, interpolatedZ);
}
}
}
设定视图参数
渲染场景之前,首先必须建立摄像机和输出窗口。即必须决定从哪个位置进行观察渲染(视点位置、方向、缩放)以及把渲染结果送到哪里(屏幕上的目标矩形区域)。上述二者中,输出窗口较为简单,故先讨论输出窗口。
指定输出窗口
我们不一定要把图像渲染到整个屏幕。比如,一个分屏的多人游戏,每个玩家只占据显示屏幕的一部分。输出窗口即指输出设备中图像将要渲染到的那部分,如图15.1所示:
窗口位置由左上角像素(winPosx,winPosy)给出,整数winResx、winResy是以像素为单位的窗口大小,如此定义,使用窗口大小而不是右下角的坐标,可避免整数像素坐标系带来一些麻烦。同时要注意窗口的实际物体大小和像素大小的区别。
要知道我们不一定在屏幕上渲染,也许只是将渲染结果保存到一个TGA文件里,或是AVI的一帧,也许只是渲染到一个纹理上---作为主渲染器的一个子过程而已,因此,名词"帧缓冲"一般指用来保存我们正渲染图像的那块内存。
像素纵横比
不管是渲染到屏幕还是缓冲区,我们必须知道像素的纵横比。它是像素高对宽的比值,一般为1("方形"像素),不过并非总是如此。下面给出其计算公式(公式5.1):
pixPhys指像素物理尺寸。一般来说,度量单位并无关系,比例才是重要的。devPhys是显示设备的物理高与宽比,尺寸可能是英寸、英尺、picas等,但也只有比例才是重要的。比如,标准的桌面显示器,尺寸各异但却拥有相同的比值4:3---视区宽大于高约33%。另一个常见比例是高清晰电视和DVD上的16:9。整数devResx和devResy是x、y方向的像素比,如640 x 480指devResx=640,devResy=480。
如前所述,比值为1的方形像素最为常见。如标准桌面显示器,有4:3的物理纵横比,而许多常见解析度:320 x 240,640 x 480,800 x 600,1024x 768,1600 x 1200也都是4:3,因此像素是方形的。
注意计算中未用到窗口的尺寸及位置,这是合理的,窗口性质不影响像素的物理属性。但是,窗口尺寸在视场问题中十分重要,而位置对摄像机到屏幕的映射是关键。
视锥
视锥是摄像机可见的空间体积,看上去像截掉顶部的金字塔,如图15.2所示:
视锥是由6个裁剪面围成的。构成视锥的4个侧面称为上、左、下、右面,它们对应着输出窗口的四边。为防止物体离摄像机过近,设置近剪面,从而去除金字塔形的顶端。同理,也设置了视野的远端,因为太远的物体实际上太小而不可见,故可有效而安全地去掉。
视场与缩放
摄像机同其他物体一样有位置和朝向,同时它还具有"视场"这一额外的属性。另一名词"缩放"你也许已经很熟悉,直观上,你早就知道放大和缩小。但拉近时,物体显大;拉远时,物体显小,这太常见了。
视场是视锥所截的角。实际上需要两个角:分别对应水平视场和垂直视场。这里只在2D中讨论其中一个,图15.3从上方显示了视锥,精确的展示了水平视场角,坐标轴的标记用的是摄像机空间。
缩放表示物体实际大小和物体在90。视场中显示大小的比。所以大比值表示放大,小比值表示缩小。比如,2.0的缩放表示物体在屏幕上比用90。视场时大两倍。缩放的几何解释如图15.4所示:
应用基本三角知识,就能推导出缩放和视场角之间的转换公式:
在3D中,需要两个缩放值,一个水平的,一个垂直的。可以随意给值,但如果二者比例不恰当,图像便像被拉伸过似的(好比宽银幕电影在电视上播出)。为了维持恰当的比例,缩放要和输出窗口的尺寸对应:
假设输出为正常比例,许多渲染引擎允许仅用一个视场角(或zoom值)设定摄像机,然后自动计算另一个。例如,可以指定水平视场角,自动计算垂直视场角,反之亦然;或者指定视场角中较大的一个,自动计算较小的。