Direct3D
景深,重叠,光照,阴影,
3D物体可以通过三角形网格近似地模拟表示,三角形是构成物体模型的基本单位。
网格的三角形密度越大,模拟出来的效果就越好。
通过,可以将在32位颜色和128位颜色相互转换。
渲染管线:在给定一个3D场景的几何描述及一架已确定位置和方向的虚拟摄像机时,根据虚拟摄像机的视角生成2D图像的一系列步骤。
输入装配(Input Assembler,简称IA)阶段:从内存读取几何数据(顶点和索引)并将这些数据组合为几何图元(例如,三角形、直线)。
Direct3D中的顶点由空间位置和各种附加属性组成;
通过不同的标志指定图元拓扑决定连接顶点的方式,以组成几何图元(例如把顶点缓冲区中的每两个顶点解释为一条直线,或者每三个顶点解释为一个三角形)
构成3D物体的三角形会共享许多相同的顶点。当模型细节和复杂性的提高时,为了避免复制顶点,创建顶点列表和索引列表。
- 顶点列表包含所有唯一的顶点
- 索引列表包含指向顶点列表的索引值,定义了顶点以何种方式组成三角形。
//四边形的顶点列表
Vertex v[4] = {v0, v1, v2, v3};
//索引列表
UINT indexList[6] = {0, 1, 2, // Triangle0
0, 2, 3}; // Triangle 1
将顶点数组放置在称为缓冲(buffer)的容器中,供GPU访问
- 静态缓冲(static buffer):
顶点的内容是在初始化时固定下来的。 - 动态缓冲(dynamic buffer):
顶点的内容可以在每一帧中进行修改。
例如模拟一个水波效果。函数f(x ,z ,t)描述水波方程,计算当时间为t时,xz平面上的每个点的高度。将每个网格点代入f(x, z , t)函数得到相应的水波高度。由于该函数依赖于时间t(即,水面会随着时间而变化),必须在很短的时间内重新计算这些网格点,以得到较为平滑的动画。所以使用动态顶点缓冲区来实时更新三角形网格顶点的高度。
顶点着色器(vertex shader)阶段:
- 每个将要绘制的顶点都会通过顶点着色器推送至硬件
- 开发者负责编写顶点着色器函数实现变换,光照等效果
- 在该阶段可以访问顶点数据和内存中的纹理等数据
曲面细分(Tessellation)阶段:可选
- 通过添加三角形的方式对网格的三角形进行细分,这些新添加的三角形可以偏移到一个新的位置,让网格的细节更加丰富。
- 使靠近相机的三角形通过细分产生更多细节,而那些远离相机的三角形则保持不变。
几何着色器阶段(geometry shader stage):可选
裁剪阶段:
- 完全丢弃在平截头体之外的几何体
- 裁剪与平截头体边界相交的几何体,只留下平截头体内的部分;
在裁剪之后,硬件会自动执行透视除法,将顶点从齐次裁剪空间变换到规范化设备空间(NDC)。
平截头体(frustum)描述了摄像机所能看到的空间范围。
水平视域角β由垂直视域角α和横纵比r决定
通过投影矩阵对顶点进行投影,映射后的x、y坐标称为规范化设备坐标(normalized device coordinates,简称NDC)。虽然2D投影窗口不需要z坐标,但是深度缓存算法需要3D深度信息。
光栅化(rasterization)阶段:
为投影后的3D三角形计算像素颜色。
背面消隐:
一个2D图形有两个面。规定带有法线向量的面为正面,而另一个面为背面。当某个面的法向量指向远平面,则该平面不会被看到
顶点属性插值:
- 在视口变换之后,通过顶点包含的颜色、法线向量和纹理坐标等属性插值计算三角形表面上的每个像素。
- 在3D空间中执行线性插值,在屏幕空间需要执行非线性插值。
像素着色器(Pixel shader):开发者编写在GPU执行的程序。
- 处理每个像素片段(pixel fragment)
- 输入是插值后的顶点属性,由此计算出一个颜色。可以实现逐像素光照、反射和阴影等效果。
输出合并(output merger,简称OM)阶段:
- 某些像素片段会被丢弃(例如,未能通过深度测试或模板测试)。未丢弃的像素片段会被写入后台缓冲区。
-
一个像素可以与后台缓冲区中的当前像素进行混合,并以混合后的值作为该像素的最终颜色。某些特殊效果,比如透明度,就是通过混合来实现的。
视觉能力完全取决于光照及光照与材质之间的相互作用。
光照模型:光照算法的数学公式
灯光与材质通过光照模型计算顶点颜色。
材质是决定光照如何与物体表面相互作用的属性。包括表面反射的灯光颜色、吸收的灯光颜色、反射率、透明度和光泽度等参数。
平面法线(face normal):
平面法线与平面上的所有点相互垂直,或与物体表面上的点的正切平面相互垂直。
为每个网格顶点指定表面法线。在光栅化阶段,顶点法线会在三角形表面上进行线性插值,使三角形表面上的每个点都获得一个表面法线
当进行光照计算时,通过三角形网格表面上的每个点的表面法线,确定光线与网格表面在该点位置上的入射角度。
对于区域dA。法线向量n与光照向量L之间的夹角θ逐渐增大,dA受到的光线照射量会越来越少
所以光照强度由顶点法线和光照向量之间的夹角决定。(兰伯特余弦定理)
漫反射(diffuse reflection)光:
当光线照射在一个粗糙表面上时,会在不同的随机方向上散开。
设ld入射光颜色,md为漫反射材质颜色,反射光的总量为:
D =ld ⨂ md
L是光线向量,n是表面法线,
kd = max(L∙n ,0),
则反射回来的漫反射光颜色为:
cd = kd•ld ⨂ md = kdD
例如渲染一个由沙滩构成的地形时,地形的每个部分反射和吸收的线数量是不一样的,所以材质颜色必须根据地形表面而变化。为每个顶点指定一个不同的漫反射材质颜色。在光栅化阶段中,这些顶点属性会在三角形表面上的进行插值。三角形网格表面上的每个点都会得到一个漫反射材质颜色。
环境光:模拟间接光
比如在一条通往房间的走廊里,光线会照射在墙壁上,把一部分线反弹到走廊中,间接地把走廊照亮。
A=la⨂ma
颜色la指定了表面从一个光源收到的间接(环境)光的总量。
环境材质颜色ma指定了表面反射和吸收的入射环境光的总量。
镜面反射:
当灯照射在光滑表面上时,光线会在一个由反射系数描述的圆锥体区域内形成锐利的反射。
反射光的强度可由反射向量r和观察向量v(从表面点P到观察点E的单位向量)之间的夹角ϕ来决定。
总结:
光源可以发射3种不同类型的线:
1.环境光(ambient light):模拟间接光照。
2.漫反射光(diffuse light):模拟对粗糙表面的直接照。
3.高光(specular light):模拟对光滑表面的直接光照。
同样,物体表面有以下材质属性与其对应:
1.环境材质:平面反射和吸收的环境光的总量。
2.漫反射材质:平面反射和吸收的漫反射光的总量。
3.高光材质:平面反射和吸收的高光的总量。
4.高光指数:通过上述的圆锥体区域来控制表面的光滑程度。圆锥体越小,表面越平滑/光亮。
美术师可以调整光照的3个部分得到不同的渲染结果。(a)只有环境光的球体颜色,环境光只是均匀地提高物体的亮度。(b)环境和漫反射光的组合。兰伯特余弦定理使球体表面形成了从亮到暗的平滑过渡。(c)环境光、漫反射和高光的组合。高光在球体的受光面形成了一小块高亮区域。
表面上不同的点可能会有不同的材质值。例如一个轿车模型的车身、窗户、灯和轮胎反射和吸收光线的能力是不一样的,所以轿车表面的材质值也应该不一样。
要模拟材质值的不同,一种方法是在顶点级别上定义材质值。这些材质值会在三角形表面进行线性插值,使三角形网格的每个表面点都拥有材质值。在顶点级别定义材质颜色模拟出的效果还是太粗糙。更普遍的方法是使用纹理映射。
纹理贴图映射(texture mapping):将图像数据映射到三角形表面,可以显著提高场景的细节和真实感。
Direct3D的纹理坐标系:图像水平方向的u轴和表示图像垂直方向的v轴。
纹理元素:坐标(u,v)指定了纹理上的一个元素
将规范化坐标区间设为[0,1],使得纹理独立于尺寸
通过线性插值的方式,3D表面的每个点都可以对应一个纹理坐标。
可以将几张毫不相关的图像合并在一张纹理贴图上(纹理贴图集texture atlas),只将纹理的一部分映射到几何体上,纹理坐标决定了将那一部分纹理映射到三角形上。
纹理数据通常存储在磁盘上。纹理资源通过创建着色器资源视图绑定到渲染管线上的。
纹理资源可以由任何着色器(顶点、几何或像素)使用。
线性插值:在两个最匹配的多级渐近纹理层之间进行线性插值。
常量插值:只为纹理映射挑选一个最接近的多级渐近纹理层。
倍增:用1个纹理元素覆盖多个像素
使用插值方法(常量插值和线性插值)来估算纹理元素之间的颜色。
缩减:多个纹理元素映射为1像素。
多级渐近贴图映射(mipmapping):使纹理元素均匀地缩减。
通过对图像进行降阶采样生成纹理的多个缩略版本来创建多级渐近纹理链
运行时,图形硬件会根据指定的参数执行两种不同的操作:
1.点过滤(point filtering):为纹理映射挑选一个与屏幕几何体分辨率最匹配的多级渐近纹理层,根据需要在多级渐近纹理层上使用常量插值或线性插值。
2.线性过滤(linear filtering):为纹理映射挑选两个与屏幕几何体分辨率最匹配的多级渐近纹理层(其中一个大一些,一个小一些)。然后在这两个多级渐近纹理层上使用常量插值或线性插值,分别取出一个纹理颜色。在这两个纹理颜色之间进行插值。
各向异性过滤(anisotropic filter):当多边形的法线向量与摄像机的观察向量夹角过大时(例如多边形垂直于观察窗口),可以有效缓解图像的失真。
对于纹理坐标(u,v)∈[0,1]2时,纹理函数T返回颜色(r,g,b,a)。对于多边形表面过大,重复使用纹理进行贴图
寻址模式:扩展纹理函数的值域
- 重复(wrap)
- 边框颜色(border color)
- 截取(clamp)
-
镜像(mirror)
对纹理进行变换:对纹理坐标进行平移、旋转和缩放
1.沿着墙体拉伸一幅砖块纹理。该墙体顶点的纹理坐标在[0,1]区间内。将每个纹理坐标乘以4,使区间扩大为[0,4],让纹理在墙体上重复4×4次。
2.通过时间函数控制白云纹理坐标的平移,形成白云在天上飘动的效果。
3.随着时间的推移旋转一幅火球纹理。
混合:
将当前的光栅化像素(也称为源像素)与后台缓冲区中的像素(也称为目标像素)融合在一起。通常用于渲染半透明物体。
例如使水体像素和后台缓冲区中的地形、板条箱像素融为一体。可以透过水体看到地形和板条箱。
Csrc为当前正在进行光栅化处理的第ij个像素(源像素)的颜色,来自于像素着色器。
Cdst为后台缓冲区中的第ij个像素(目标像素)的颜色。
当不使用混合时,Csrc会覆盖Cdst的值并成为后台缓冲区中的第ij个像素的新颜色;
当使用混合时,Csrc和Cdst会被组合为一个新颜色C并覆盖Cdst的值。
处理颜色RGB分量的混合方程
C = Csrc ⊗ Fsrc ⊞ Cdst ⊗ Fdst
处理alpha分量:A表示不透明度
A = AsrcFsrc ⊞ AdstFdst
Fsrc(源混合系数)和Fdst(目标混合系数)按照各种不同的方式调整源像素和目标像素的比例,实现各种不同的混合效果。
运算符⊞可以是加,减,最大,最小。
例如使用加法混合来渲染一个粒子系统S时,每个粒子是否相互遮挡并不重要;只需要把粒子的颜色简单地累加起来 。
当S中的某个粒子被另一个粒子遮挡时,该粒子将无法通过深度测试,它的像素片段将被丢弃。
所以应该在渲染S时禁用深度写入功能,使粒子的深度信息不写入深度缓冲区。在禁用深度写入功能之后,由加法混合生成的粒子深度信息不会写入到深度缓冲区;所以当S中的一个粒子被其他粒子遮挡时,该粒子依然可以通过深度测试并绘制到后台缓冲区中。
以水面绘制为例
使用源alpha分量as控制源像素的不透明度(例如,当alpha为0.0时表示0%不透明,为0.4时表示40%不透明,为1.0时表示100%不透明)
此时
C = Csrc ⊗ (as, as, as) + Cdst ⊗ (1 - as, 1 - as,1 - as)
C = asCsrc + (1 - as)Cdst
当as = 0.25时,表示源像素的透明度为75%。最终得到的颜色应该是由25%的源像素和75%的目标像素组成(即源像素将“挡住”一部分目标像素)。此时的混合方程为:
C = 0.25Cdst + 0.75Csrc
在像素着色器中,使用HLSL的clip(x)函数完全丢弃某个源像素。
例如在带有alpha通道的铁丝网纹理中,clip函数将丢弃那些带有黑色alpha值的像素;只有铁丝网部分会保留下来。
蹿出(popping):由于摄像机的移动,使原本在远平面后面的物体突然进入平截头体内。
通过在一定距离内加入雾效,可以掩盖这一问题。因为就算是晴天,远处的物体(比如山岳)也会看上去有些模糊,就像是有一层薄薄的雾笼罩在上面一样。
当顶点与观察点之间的距离小于fogStart时,雾不会影响顶点颜色。当表面点与观察点之间的距离大于等于fogEnd时,雾将完全取代表面点本身的光照颜色。
foggedColor = litColor + s (fogColor − litColor) = (1 − s ) ∙ litColor + s ∙ fogColor
参数s的取值范围是从0到1,它是一个以表面点和观察点之间的距离为自变量的函数。随着表面点和观察点之间的距离增大,雾在表面点颜色中所占的比例会越来越大。
模板缓冲区(stencil buffer):用来实现特殊效果的离屏(off-screen)
- 模板缓冲的第ij个像素对应于后台缓冲和深度缓冲第ij个像素。
- 可以挡住某些像素片段,不让它们存入后台缓冲。(就像印章)
-
深度/模板缓冲区是一个纹理
当实现一个镜像效果时,镜像只显示在镜子里面。可以使用模板缓冲区来控制镜像范围,阻止镜像绘制到镜子之外的区域。
模板测试(stencil test)
- 判断像素是否可以写入后台缓冲区
- 在像素光栅化时(即输出合并阶段)进行
顶点着色器无法创建或销毁顶点,
几何着色器可以创建或销毁几何体。例如将输入图元扩展为一个或多个其他图元,或者根据一些条件屏蔽某些图元的输出。几何着色器的常见用途是将一个点扩展为一个四边形(即,两个三角形)。
几何着色器的输出图元由一个顶点列表来描述。在顶点离开几何着色器之前,顶点坐标必须变换到齐次裁剪空间。与往常一样,这些顶点会被投影(齐次除法),随后进行光栅化处理。
当物体与观察点的距离很远时,可以通过广告牌(billboard)技术来提高渲染效率。
只在一个四边形上绘制树的3D图片,而不是渲染一个完整的3D树模型。必须确保广告牌始终面对摄像机。
从屏幕空间变换回3D空间:
判断是否拾取物体
计算出一条拾取射线,遍历场景中的每个物体是否与该射线相交。射线可能会与场景中的多个物体相交,它们具有不同的深度值。将与摄像机距离最近的相交物体作为最终的拾取物体。
立方体贴图(cube map):由6幅纹理组成的、按特殊方式解释的纹理数组。
从原点引出一个向量v。与v相交的纹理元素就是所要采样的纹理元素。
主要用于实现环境贴图映射(environment mapping)。
之前法线向量定义在顶点级别上,在更高分辨率下指定表面法线,可以增加光照的细节,但是网格的几何细节仍然没有得到提高。
例如圆柱体的高光出现在凹凸不平的砖块纹理上。由于网格柱体表面的细化程度不足以模拟凹凸不平的砖块纹理。而光照计算是根据网格几何体(尤其是顶点法线插值)来实现的,没有考虑到纹理图像的内容。所以光照和纹理表现出来的质感不完全一致。
一种方法是硬件曲面细分可以实现这一目的;
为了使物体表面即能体现自身的纹理细节,又能表现正确的光照质感。
法线贴图(normal map):每个纹理元素存储的不是RGB数据,而是表示法线向量的x、y、z坐标。
纹理空间(texture space)/正切空间(tangent space):
区别正切空间,物体空间,世界空间
法线贴图映射的一般处理过程:
2.为每个三角形计算切线向量T。在网格中,顶点v的切线向量等于共享该顶点的每个三角形的切线向量的平均值。
3.在顶点着色器中将顶点的法线向量和切线向量变换到世界空间,然后将结果输出到像素着色器。
4.使用插值后的切线向量和法线向量,为三角形表面上的每个像素点生成TBN基。使用TBN基,将从法线贴图采样得到的法线向量从切线空间变换到世界空间。最后将法线向量用于光照计算。
地形渲染:以一个平面网格为基础,通过调整网格顶点的高度(即y坐标)模拟地形起伏
高度图是一个矩阵,每个元素指定了地形网格中的一个特定顶点的高度。
使用点来存储粒子,在几何着色器中将它们扩展为面对摄像机的图形。
例如使用直线列表(line list)渲染雨景
下一篇: Direct3D 光照练习