图形管道概述
不同的渲染系统,有不同的偏向和优化技巧,但多数渲染系统有极大共性。一般的图形管道中的数据概况如下:
- 建立场景:设定对整个场景有效的一些选项
- 建立摄像机位置(设置观察场景的方式)
- 视点:渲染的出发点
- 视图:渲染的输出
- 光照、雾化选项
- z-缓冲
- 建立摄像机位置(设置观察场景的方式)
- 可见性检测
- 场景中哪些物体可见。
- 对实时渲染来说极其重要(性能)
- 设置物体级渲染状态
- 每个(潜在)可见物体的渲染设置可能不同。
- 最常见:纹理映射。
- 几何体的生成与提交
- 向API提交几何体
- 大多数几何体:三角形,如索引三角网格、三角带、独立的三角形。
- 少数几何体:LOD,渐进式生成几何体
- 变换与光照
- 坐标变换:模型空间向摄像机空间的顶点坐标变换
- 顶点光照计算
- 背面剔除与裁剪
- 背面剔除:去除背对摄像机的三角形。
- 裁剪:去除三角形在视锥外的部分(产生多边形)
- 投影到屏幕空间
- 在3D裁剪空间中经裁剪产生的多边形,被投影到输出窗口的2D屏幕空间里。
- 光栅化
- 计算出绘制三角形所需要的那些像素。
- 为像素着色提供插值参数(光照、纹理映射坐标)。
- 像素着色
- 计算三角形色彩,把颜色写至屏幕
- 可能需要alpha混合、z-缓冲。 ```cpp
setupTheCamera(); //建立摄像机位置 clearZBuffer(); //清除z-buffer setGlobalLightingAndFog(); //设置环境光源和雾化 potentiallyVisibleObjectList = highLevelVisibilityDetermination(scene); //得到可见物体列表
for (all objects in potentiallyVisibleObjectList){ // 渲染它们
if (!object.isBoundingVolumeVisible()) continue; // 使用包围体方法执行低级别VSD检测
triMesh = object.getGeometry(); // 提取几何体(三角网格等)或者渐进式生成几何体
for (each triangle in the geometry){ // 裁剪和渲染面
clipSpaceTriangle = transformAndLighting(triangle); // 变换顶点到裁剪空间(摄像机空间),执行顶点级别光照
if (clipSpaceTriangle.isBackFacing()) continue; // 背面剔除:三角形为背向的?
clippedTriangle = clipToViewVolume(clipSpaceTriangle); // 裁剪:对视锥裁剪三角形
if (clippedTriangle.isEmpty()) continue;
clippedTriangle.projectToScreenspace(); // 投影&光栅化:三角形投影至屏幕空间,并且光栅化
for (each pixel in the triangle){ // 插值颜色,z-缓冲值和纹理映射坐标
if (!zbufferTest() || !alphaTest()) continue //执行 zbuffering和 alpha检测
color = shadePixel(); //像素着色
writePixel(color, interpolatedZ); // 写内容到帧缓冲和z-缓冲
}
}
}
<a name="sxMQn"></a>
# 一、设定视图参数
渲染场景之前,首先必须做的是:
- **建立摄像机**
- 从哪个位置观察渲染:视点位置、方向、缩放。
- **建立(输出)窗口**
- 把渲染结果送到哪里。
- 屏幕上的目标矩形区域,输出窗口不一定是充满整个屏幕,可能只是屏幕的一块。
<a name="eoQiR"></a>
## (一)指定输出窗口
[点击查看【processon】](https://www.processon.com/view/link/5fd730c363768906e6dcc382)<br />不一定要把图像渲染到整个屏幕,比如分屏多人游戏,每个玩家占据屏幕的一部分。<br />输出窗口指输出设备中图像将要渲染到的那部分。<br />渲染的最终输出也不一定是窗口,也可能保存到TGA文件、AVI的一帧。帧缓冲(frame buffer)指保存正在渲染图像的内存。
<a name="fQxua"></a>
## (二)像素纵横比
即像素的宽高比,一般是1,也就是“方形像素”,计算公式:

<a name="x0IWz"></a>
## (三)视锥
视锥是摄像机可见的空间体积,看上去像截掉顶部的金字塔(平截头体,frustum)<br />
- **视锥**:6个裁剪面构成
- 上、下、左、右裁剪面
- 近裁剪面:near clip plane,防止物体离摄像机过近
- 远裁剪面:far clip plane,太远的物体,基本不可见,完全可以裁剪掉。
<a name="gm2fl"></a>
## (四)视场与缩放
- **视场**
- 视锥所截的角,
- 需两个角:水平视场角、垂直视场角。
- **缩放**
- 物体实际大小与在90°视场中显示大小的比。2.0表示物体在屏幕上比在90°场时大两倍。
- 需要两个值:水平和垂直,不然图像会被拉伸。
<br />缩放、视场角之间的转换公式:
<br />水平/垂直缩放需要和输出窗口的尺寸对应,否则图像将被拉伸。<br /><br />如果输出为正常比例,一般引擎只需要输入一个视场角(zoom值)来设定摄像机,引擎会自动计算另一个视场角(zoom值)。
<a name="DKWAn"></a>
# 二、坐标空间
<a name="x5rpZ"></a>
## (一)物体空间
物体最开始在物体空间(和物体相连的坐标空间)中描述,包含:顶点信息、表面法向量。<br />也称模型空间、局部空间。
<a name="1rrdi"></a>
## (二)世界空间
- 模型空间转换到世界空间,称为模型变换。
<a name="Pdqz3"></a>
## (三)摄像机空间
也称眼睛空间。
- 是这样的3D坐标空间:
- 原点
- 在投影中心
- 一个轴
- 平行于拍摄方向垂直于投影平面。
- 一个轴
- 上下裁剪面相交得到。
- 一个轴
- 左右裁剪面相交得到。

- 左手坐标系
- +z方向:摄像机朝向
- +y:上方向
- +x:右方向
- 右手坐标系
- +z:朝向摄像机
<a name="tJGBV"></a>
## (四)裁剪空间
又名:标准视体空间(the canonicalview volume space)。<br />**裁剪矩阵**:从摄像机空间变换到裁剪空间的矩阵。
在这之前,顶点还是“纯粹”的3D向量,即三个坐标值(或者有第四个分量)。裁剪矩阵将有用信息加入中,主要有两个目的:
- **为透视投影准备向量**
- 由除以来实现,见透视投影知识。
- **裁剪**
- 标准化,将它们与比较来判断是否在裁剪区域内。
<a name="sngmr"></a>
# 三、光照与雾化
标准光照模型:处理一个物体时,不考虑其他物体的影响。
<a name="iBLmi"></a>
## 1、光照_色彩数学
计算机色彩常用RGB模型,R:red,G:green,B:blue,值一般在。<br /><br />用黑体的小写罗马字母符号表示,如<br /><br />
<a name="3UkD4"></a>
## 2、光照_光源
为进行渲染,必须向图形API描述场景中的光。
- **点光源**
- 向四面八方发射光线的点,也叫**全向光、球状光**
- **平行光**
- 从无限远处射来的点光源的光线
- **聚光灯**
- 从特定光源向特定方向射出的光。
- **环境光**
- 不属于任何光源而照亮整个场景的光。
- 不考虑环境光,影子将是完全黑的。
<a name="nZcDa"></a>
### 点光源
向四面八方发射光线的点,也叫**全向光、球状光**<br />有方向和色彩,色彩(色调与亮度)。<br />辐射衰减半径(Falloff radius),控制照亮的范围。<br />常见有:灯泡、点灯、火把。<br />
<a name="TEyYb"></a>
### 平行光
从无限远处射来的点光源的光线。<br />场景中所有光线皆为平行。<br />没有位置概念,无衰减。<br />最典型的就是太阳光。
<a name="KHUqB"></a>
### 聚光灯
从特定光源向特定方向射出的光。<br />有位置、方向,辐射距离,照亮区域为圆锥形、金字塔形。<br />如信号灯、车头灯。<br />**圆形聚光灯**<br /><br />**方形聚光灯**<br />金字塔形状,不是圆锥形状,常用于投影图像,如电影放映。
<a name="GmXh6"></a>
## 3、光照_标准光照方程

:打开光照情况下计算的颜色值。<br />:镜面反射分量<br />:散射分量(漫反射分量)<br />:环境光分量
物体外观主要取决于以下四点因素
- 物体表面的性质,即材质属性。
- 表面的方位与朝向,朝向常用单位法向量表示。
- 照射来的各光源性质。
- 观察者位置。
方程中的每个部分分别考虑了上述因子的不同组合。
<a name="iuBq9"></a>
### 镜面反射分量
由光源直接经物体表面反射入眼睛的光线。<br />镜面反射使物体看上去有光泽。粗糙表面因反射率不高,所以缺乏此类效果。镜面反射的强度取决于:**物体**,**光源**和**观察者。**
<a name="p9rf4"></a>
#### Phong模型
****<br /><br />****<br />****<br /><br />为材料的光泽度,也称作Phong指数。它控制“亮斑”的范围(大小)<br />小的值带来大而平滑的光斑<br />大的值带来小而亮的光斑。<br />完全的反射面,如玻璃,有非常大的,只有反射光进入眼睛;<br />不完全的光反射面,如苹果的表面,有较大的亮斑。<br /><br />材料的反射颜色,对整个材料是一个不变的灰度值。控制“光斑”的强度。<br />强反射面,值越大,粗糙一些,值越小。<br />可以用“光泽图”控制物体的反射,犹如纹理控制物体颜色。<br /><br />光源的镜面反射颜色,控制光本身的色彩与强度。常等于光的漫反射颜色<br /><br />观察者离物体的距离远大于物体的尺寸时,可以仅计算v一次,然后对于整个物体是常量。
<a name="CqkGn"></a>
#### Blinn模型
<br /><br />

<br />为材料的光泽度,也称作Phong指数。它控制“亮斑”的范围(大小)<br />小的值带来大而平滑的光斑<br />大的值带来小而亮的光斑。<br />完全的反射面,如玻璃,有非常大的,只有反射光进入眼睛;<br />不完全的光反射面,如苹果的表面,有较大的亮斑。<br /><br />材料的反射颜色,对整个材料是一个不变的灰度值。控制“光斑”的强度。<br />强反射面,值越大,粗糙一些,值越小。<br />可以用“光泽图”控制物体的反射,犹如纹理控制物体颜色。<br /><br />光源的镜面反射颜色,控制光本身的色彩与强度。常等于光的漫反射颜色
此方程便于硬件实现,特别是当光源与观察者均远离物体时,此时被视为常数仅需计算1次。<br />有时cosθ小于零,此时简单地令镜面反射为0即可。
<a name="tg3sY"></a>
### 漫反射分量
<br />漫反射光服从法则,反射光强正比于法向量与光线夹角的余弦。

为表面法向量<br />为指向光源的单位向量<br />为材料的散射色,即多数人认同的物体颜色。材质多来自纹理图.。<br />为光源散射色,一般和光源镜面色一致。<br />与镜面反射类似,这里也要防止点积出现负值,免得物体从背后透光。
<a name="T6gnv"></a>
### 环境光分量
镜面反射和漫反射都是刻画光源经物体反射后直接进入眼中的光线,但现实世界中,光线也经常在经历多于一次的反射后进入眼睛。好比您在黑暗的厨房打开冰箱,整个房间都会变得亮些,尽管箱门(您的身体)挡住了大部分直线光。<br />描述这类光,我们可用环境光。<br />环境光取决于材质和全局环境光,没有涉及任何光源。
环境光计算公式如下:<br />
:材质的环境光分量,它总是等于漫反射分量—— 由纹理图定义。<br />为整个场景的环境光值。
<a name="uNQwX"></a>
## 4、光照_光的衰减
光随距离衰减,远离光源会暗一些。现实世界里,光强度反比于物体和光源距离的平方。

实际操作中并不方便,我们常用下面这个模型代替:

光射出后:<br />在内,光强不会衰减,<br />在间,光强衰减至0,<br />在外,光强一律为0。
适用于点光源,聚光灯。环境光,平行光没有衰减。
<a name="IQ3LF"></a>
## 5、光照_光照方程
<a name="qOjEX"></a>
### 单光源标准光照方程

:打开光照情况下计算的颜色值。<br />:镜面反射分量<br />:散射分量(漫反射分量)<br />:环境光分量<br /><br />为材料的光泽度,也称作Phong指数。它控制“亮斑”的范围(大小)<br />小的值带来大而平滑的光斑<br />大的值带来小而亮的光斑。<br />完全的反射面,如玻璃,有非常大的,只有反射光进入眼睛;<br />不完全的光反射面,如苹果的表面,有较大的亮斑。<br /><br />材料的反射颜色,对整个材料是一个不变的灰度值。控制“光斑”的强度。<br />强反射面,值越大,粗糙一些,值越小。<br />可以用“光泽图”控制物体的反射,犹如纹理控制物体颜色。<br /><br />光源的镜面反射颜色,控制光本身的色彩与强度。常等于光的漫反射颜色<br />:表面法向量<br />:指向光源的单位向量<br />:材料的散射色,即多数人认同的物体颜色。材质多来自纹理图.。<br />:光源散射色,一般和光源镜面色一致。与镜面反射类似,这里也要防止点积出现负值,免得物体从背后透光。<br />:材质的环境光分量,它总是等于漫反射分量—— 由纹理图定义。<br />:整个场景的环境光值。<br /><br />耳朵与鼻子一样亮,其实它本应该在头部的影子中。这是采用局部光照的结果,要计算阴影,必须考虑其他高级技术,影子涉及全局光照。<br />前两幅图中,因为没有环境光,头背向光源的部分为全黑。若想照亮物体的“背向”部分,必须使用环境光;或者在场景中设置更多的光源,使得所有面都能被直接照亮。<br />当只有环境光时,只能看出轮廓。光照是使物体呈现“ 3D” 外观的重要武器,为了避免这种“卡通”效果,我们可以使用足够多的光源使场景中的所有表面都能被直接照亮。
<a name="zDgAA"></a>
### 多光源标准光照方程

环境光只有一个,所以不做求和。
:打开光照情况下计算的颜色值。<br />:镜面反射分量<br />:散射分量(漫反射分量)<br />:环境光分量<br /><br />为材料的光泽度,也称作Phong指数。它控制“亮斑”的范围(大小)<br />小的值带来大而平滑的光斑<br />大的值带来小而亮的光斑。<br />完全的反射面,如玻璃,有非常大的,只有反射光进入眼睛;<br />不完全的光反射面,如苹果的表面,有较大的亮斑。<br /><br />材料的反射颜色,对整个材料是一个不变的灰度值。控制“光斑”的强度。<br />强反射面,值越大,粗糙一些,值越小。<br />可以用“光泽图”控制物体的反射,犹如纹理控制物体颜色。<br /><br />光源的镜面反射颜色,控制光本身的色彩与强度。常等于光的漫反射颜色<br />:表面法向量<br />:指向光源的单位向量<br />:材料的散射色,即多数人认同的物体颜色。材质多来自纹理图.。<br />:光源散射色,一般和光源镜面色一致。与镜面反射类似,这里也要防止点积出现负值,免得物体从背后透光。<br />:材质的环境光分量,它总是等于漫反射分量——由纹理图定义。<br />:整个场景的环境光值。
<a name="khD9a"></a>
## 6、顶点级光照计算方程
在当前图硬件渲染中,一般采用Gourand着色,即逐顶点计算光照。
上面多光源标准光照方程进行修改,材质的环境光分量可以由纹理图定义,所以可以假设:

可得逐顶点光照计算方程:<br /><br />
计算光照的坐标系不做限制,只要是在同一个坐标系中即可。
可以在世界空间中执行,将顶点、法向量变换到世界空间进行光照计算,然后将顶点变换到裁剪空间。
可以考虑将光照放在模型空间中计算,因为光总比顶点数少,将光变换到模型空间中,总比顶点变换的计算量少。
还可以在摄像机空间中计算,如果不通过顶点着色直接控制通道,API会默认做这个选择。
<a name="G1QcG"></a>
## 7、雾化
现实中,光线被空气中无数粒子反射与折射。如果单位体积内粒子的浓度足够,则它们是可见的,例如烟、灰尘、雾等。计算机图形学中,上述现象都是通过雾化技术加以模拟的。<br />想像我们正在注视远处的物体,眼睛与物体间的光线受到大晴粒子的扰动。一些原来直线传播无法进入眼睛的光线,被那些粒子反射而进入眼睛,这就是我们“看到”空气中粒子的原因。最后的视觉效果上,物体的颜色向雾的颜色偏移,粒子越多,偏移越大。<br />雾浓度:,0无雾化,1完全雾化,这是只有雾的颜色。最终颜色由雾颜色和物体颜色线性插值。
雾浓度计算公式如下:<br /><br />
**注意**:
- 上述公式假设雾是均匀的,实践中一般都不是,一般下方的雾更浓。
- 距离的定义是可变,当然,可以用欧式距离,得到球状雾效果,但需要做开方运算。有一种简化是以摄像机空间深度Z为距离,从而得到线性雾。它的优点是速度快,但有一个恼人的副作用,某一点的雾浓度可能因摄像机朝向的不同而改变,现实世界中这是不可能的。
得到雾浓度,就可以通过与物体颜色线性插值,计算最终颜色:

- 为计算光照后的物体表面颜色
- 为上面公式计算出的雾浓度
- 为全局雾颜色
- 为最终结果
必须向API提供以下说明:
- 开启雾化
- 雾的颜色
- 雾化距离,
<a name="w17dW"></a>
## 8、flat着色与Gourand着色
- **Phong着色**
- 若渲染速度并不重要,我们可以逐像素地计算光照和雾化。
- 和镜面反射的Phong模型不是一回事。
- 计算量巨大,非常慢。
- **flat着色**
- 逐多边形着色
- 对整个三角形只计算一次光照值。计算位置是“三角形中心”
- **Gourand着色**
- 逐顶点着色,插值着色
- 在顶点级计算光照和雾,然后这些值被线性插值用于整个多边形面。
- 最常用的方法
<br />显然Gourand效果更好。
<a name="f9Xfl"></a>
# 四、缓存
渲染涉及大量的缓存,这里指的是存有像素数据的矩形内存块。最重要的缓存是:
- **帧缓存**,frame buffer
- **深度缓存**,z-buffer
<a name="MGYMo"></a>
## 帧缓存
存储渲染后的图像,一般在显存中,显卡不断读取该内存,将二进制数据转化为CRT接收的合适信号。<br />**
- **双缓存技术**
- **显示缓存**
- 存储当前显示的图像
- **离线缓存**
- 存储正在渲染的图像
- **目的**
- 是为了防止图像在未完全渲染好之前就被显示。
- **切换技术**
- 页切换:离线缓存的图像渲染完成,即对调这个两个缓存的身份。
- 拷贝复制:将离线缓存复制到显示缓存。
以下是页切换技术工作原理:<br />
<a name="tyiRg"></a>
## 深度缓存
存储每个像素的深度信息,该信息有很多不同变体,但基本都是反映物体到摄像机的距离。实际应用中,一般保存裁剪空间中的z坐标,因此叫z-buffer。<br />作用是用于计算物体间的遮挡关系,
// —————— 起始阶段 for (all pixel in zBuffer){ pixel = 1.0 ; \ 裁剪坐标中,1.0表示无穷远点。第一批像素才能通过深度测试 }
// —————— 循环渲染阶段 for{all pixelFrame in frameBuffer}{
GLInt newZ = calcZ(pixelFrame); // 计算像素的插值深度
GLInt oldZ = getZ(pixelFrame, zBuffer); // 获取该像素对应的深度缓存中的深度值
if(oldZ > newZ){ // 新的深度比现有值离摄像机更近,替换现有值 updateZ(newZ, pixelFrame, zBuffer); }
}
<a name="1PcLW"></a>
# 五、纹理映射
物体外观不限于形状,还有表面的颜色图案(纹理),实现方法就是纹理映射(texture mapping)。
**纹理图**:铺在物体表面的位图。<br />**纹理像素**:纹理图上的一个像素,简称纹素,texel。
纹理映射是在纹素级别控制颜色,优于在顶点/三角形级别控制物体颜色。<br />
- 如何将纹理图包裹在物体上?
- 技术
- 平面映射:将纹理投射到三角网格。
- 球形映射
- 柱形映射
- 立方体映射
- 实际应用
- 3D Studio Max完成建模工作。
纹理映射坐标<br />位图上的2D笛卡尔坐标。<br />,防止与渲染坐标混淆。<br /><br />上图纹理包裹了头部的一半(鼻子到后脑中线),另一半就是镜像。<br />图中的纹理坐标的原点在左上角,类似于硬件访问的方式,原点也可以在左下角。
纹理不需要“连续”包裹几何体,因为每个三角形都独立映射,纹理的任意部分可以映射到物体任意部分。<br />如果不使用“连续”映射,且映射坐标存在顶点级,纹理交界处对应的顶点坐标是双份的 。
每个顶点都有纹理坐标,纹理被“钉”在三角网上,要渲染三角形内的某个像素,先插值计算该点的坐标,然后读取对应的纹理像素值。
<a name="XjNz9"></a>
# 六、几何体的生成与提交
一旦知道哪些物体可见(至少潜在可见),即可将其生成并提交到图形处理器。该阶段完成以下任务:
- 细节层次(LOD)选择
- 渐进式生成几何体
- 向图形API提交数据
<a name="VNrxU"></a>
## LOD选择、渐进生成
一般我们希望以最大可能的三角形数量描绘物体以求得最佳的视觉效果,但不幸的是,较多的三角形一般意味着低帧率。我们必须在可接受的外表和帧率间做出折衷选择。LOD在一定程度上可两全其美,基本思路是离摄像机远的物体只使用较少的多边形,此时并不降低视觉效果。
- 如何得到三角形数量较少的三角网?
- 美工制作(人工)
- 然后根据物体离摄像机的远近(或屏幕分辨率大小)选用合适的LOD,问题是就在它由远及近改变的那一刻,这种方法会有一种“跳动效果”。当然,我们希望把这种视觉上的不连续降低到最低限度—— 好的三角网也许会有更大帮助。
- 一种克服“跳动”的方法是引入连续LOD。这种系统中,不同级别LOD包含的三角形数目几乎是连续的:我们可以产生任意多三角形的网格。渐进式网格技术就是一种这样的“网格消减''技术。但需要注意生成连续LOD的开支可能会很显著。而使用离散LOD,网格是现成可用的,渲染时可立即投送;我们所要做的就是决定用哪个网格。所以,也许即使实际的网格是用网格消减技术生成的,离散LOD还是在实践中经常使用。
- 计算机建模
- 有时候几何体并非由美工创建,而是由计算机生成,这称为程序建模。分形地形图是程序建模的好例子,植物也可以自动创建。有时LOD也用在此类建模算法中。
<a name="jmbCl"></a>
## 向API投送几何体
不论几何数据从哪儿来,最后还是要送往渲染API处理,那应该用哪种格式?
多数API希望三角网格输入(三个三角形、索引三角网格、三角带、三角扇)。
API本质上是需要顶点数据,三角形是顶点的合适连接方式,不需要超过三角形级别的数据。
根据API的不同,顶点的数据一般有:
- **位置**
- 2D屏幕坐标+深度信息
- 3D坐标(3D向量)
- 需要用模型、视图变换向屏幕映射。
- 若干“骨头”组成
- 骨骼动画中的高级技术**skining**
- **光照和雾化**
- 光照
- 顶点本身带有色彩值,插值计算三角形内个点的颜色(RGB+alpha)。
- 可手动指定值,API计算值(需要顶点法向量)。
- 雾化
- 指定个点的雾化程度,可以手动指定或API计算。
- **纹理映射坐标**
- 每个顶点必须要有。
- 单一纹理:2D坐标。
- 多重纹理:每个纹理对应一个坐标。
- 阶段式生成纹理(向表面投射一道光线)。
- “阶段性”拷贝纹理坐标。
综上,顶点数据并没有一个固定格式。
```cpp
// 最常见的3D坐标:表面法向量 + 纹理映射坐标。
// 未变换,没有光照的顶点。
struct RenderVertex{
Vector3 p; // 坐标
Vector3 n: // 法向量,API计算光照值时需要。
float u, v; // 纹理映射坐标
}
// 用来显示2D物体/HUD(head up display)
// 屏幕坐标 + 预定义光照
// 变换后,有光照顶点
struct RendervertexTL{ // T:transfrom变换
// L:lighting光照
Vector3 p; // 屏幕坐标和深度
float w; // 1/z
unsigned argb; // 漫反射
unsigned spec; // 镜面反射
float u,v; // 纹理坐标
}
// 3D坐标,自带预定义光照,常见于特效(爆炸、火焰、发光物)
// 未变换,有光照的顶点
struct RenderVertexL{
Vector3 p; // 坐标
unsigned argb; // 漫反射
unsigned spec; // 镜面反射
float u, v; // 纹理映射坐标
}
七、变换和光照
变换
模型空间坐标变换到裁剪空间坐标,通过矩阵乘法进行变换。
矩阵乘法结果可提前计算好,无需每次循环计算。
如果能够访问T&L硬件(顶点着色),则可直接施加精确的控制。
顶点光照
八、背面剔除与裁剪
背面剔除(culling)
去除背对摄像机的三角形(屏幕上看不到的物体的部分),节省性能,提高效率。
理论上有一半的背向三角形,实践中有少于一半的三角形被剔除。
探测背向三角形方法有两种:
方法一:法向量
一般用于软件渲染,在三角级信息中存储计算好的法向量,问题在于加大了向图形硬件传输数据量(法向量数据),在现在的图形硬件上,一般不使用这种方法,而是判断三角形顶点顺序。
方法二:顶点顺序
在这里我们约定,从前面看,三角形顶点顺序为顺时针,于是可剔除逆时针顶点顺序的三角形。
我们可以通过API控制背面剔除规则来灵活应用,比如有些不要剔除物体背面,有些物体反转了,剔除规则也要反过来。
裁剪(cliping)
去除三角形不在视锥内的部分的过程,由硬件完成。
标准裁剪多边形算法:。大概过程就是,先将多边形分裂成小多边形,裁剪完,再组合起来。
对平面做多边形裁剪时,沿边遍历多边形,依次对每边进行裁剪。边的两个顶点要么在或不在平面内,所以总共有四种情况。每种情况产生0、1、2个输出点,如下图:
九、光栅化
在视锥内部的顶点投影到屏幕的坐标转化成屏幕上离散的像素的过程,算法非常复杂。好消息是,图形硬件会搞定这个过程。虽然我们不知道也不想知道硬件如何决定哪个像素属于哪个三角形,但是我们要了解输出的像素点会做什么事情:
- 着色
- 为像素计算颜色。先光照再雾化。颜色值有RGB和alpha值,后者用于混合(blending)。
- 测试
- 裁剪测试
- 去除渲染窗口外的像素
- 深度测试
- alpha测试
- 以alpha值为基准去除像素,比如“过于透明”的像素。
- 裁剪测试
- 写入
- 通过测试,要写入帧缓存和更新深度缓存。
- 深度缓存
- 新深度值代替旧值。
- 帧缓存
- 没开启混合(blending),新值代替旧值
- 开启混合,根据alpha值将新旧值混合。