Games202-02渲染管线简述

前言

  • 实时渲染:30fps(每秒渲染30幅画面)
  • 实时渲染主要应用于游戏,离线渲染(难度更大)主要应用于电影
  • 编辑器:可以使用VS Code,有插件glsl-canvas可以直接预览shader

渲染管线

什么是图形渲染管线?

  • 主要功能:根据给定的虚拟相机、三维对象、光源、照明模式、纹理等诸多条件的情况下,生成一个二维图像的过程。
  • 是实时渲染的底层工具
  • 物体外观的决定因素:三维对象本身的材质属性、光源、着色方程(照明模式)、纹理(贴图)

渲染管线架构是怎样的?

  • 管线是并行执行的

  • 管线中最慢的阶段会影响整个管线的执行的速度

  • 计算机实时宣染管线四大主要阶段:应用、几何处理、光栅化、像素处理

  • 功能阶段有一个特定的任务要执行,但没有指定任务在管线中执行的方式

  • 渲染速度表示

    • 每秒帧数(FPS),每秒渲染的图像数量。FPS用于表示特定帧速率,或者在使用期间的平均性能。
    • 赫兹(Hz),渲染一帧所需要的时间,即更新的频率,赫兹用于硬件,被设置为固定速率
  • 渲染时间的单位:毫秒(ms)

  • 渲染管线四个阶段概述

    • 应用程序阶段,几何阶段,光栅化阶段,像素处理阶段(加上”后处理阶段“)
    • 应用程序阶段:传统上在CPU上执行的一些任务包括碰撞检测[collision detection]、全局加速算法[global acceleration algorithms]、动画[animation]、物理模拟[physics simulation]和许多其他任务,这取决于应用程序的类型。
    • 几何阶段:这一阶段涉及变换[transforms]、投影[projections]和所有其他类型的几何处理。这个阶段计算要绘制的内容、应该怎么画,以及它应该画在哪里。几何阶段通常执行在包含许多可编程核心[programmable cores]以及固定操作硬件[fixed-operation hardware]的图形处理单元[graphics processing unit]。
    • 光栅化[rasterization]阶段:通常接受三个顶点作为输入,形成一个三角形,并找到三角形内的所有像素,然后将它们转发到下一阶段
    • 像素处理[pixel processing]阶段:对每个像素执行一个程序,以确定其颜色,并可能执行深度测试[depth testing],以确定其是否可见[visible]。还可以执行逐像素操作[per-pixel operations],例如将新计算的颜色与以前的颜色混合[blending]。
  • 除了应用阶段在CPU上执行,其他阶段都在GPU上执行

渲染管线的4个阶段详述

一、应用阶段

  1. 准备基本场景数据
    1. 场景物体数据:物体变换数据(位置、旋转、缩放等),物体网格数据(顶点位置、UV贴图等)
    2. 光源信息:光源类型(方向光、点光、聚光等),光源的位置、方向、角度等其他参数
    3. 摄像机参数:位置、方向、远近裁剪平面、正交/透视(FOV)、视口比例/尺寸等
    4. 阴影设置:是否需要阴影(判断该光源可见范围内是否有可投射阴影的物体),阴影参数(对应光源序号、阴影强度、级联参数、深度偏移、近平面偏移等)
    5. 逐光源绘制阴影贴图
      1. 近平面偏移
      2. 逐级联:计算当前光源+级联对应的观察矩阵、投影矩阵,以及对应到阴影贴图里的视口区,绘制到阴影贴图
  2. 加速算法与粗粒度剔除:碰撞检测、加速算法(八叉树、KD树、BVH)、遮挡剔除、其他算法
  3. 渲染设置
    1. 绘制设置:合批方式
    2. 物体绘制顺序:相对摄像机的距离、材质RenderQueue、UICanvas、其他方式等
    3. 渲染目标:FrameBuffer、RenderTexture、MRT
  4. 调用DrawCall输出到显存
    1. 顶点数据:位置、颜色、法线、纹理坐标等
    2. 其它数据:MVP变换矩阵、纹理贴图、阴影贴图、预计算数据等

二、几何阶段

把输入的3D数据转换成2D数据。包括:

  • 顶点着色

    • 任务:计算一个顶点的位置,计算程序员想要的顶点输出数据(法线、纹理坐标)

    • 着色:确定光在材料上的效果的操作。计算物体上不同点的着色方程;各种各样的材料数据可以存储在每个顶点;顶点着色结果发送到光栅化和像素处理阶段插值和用于计算表面的着色

    • 通常,着色计算通常认为是在世界空间中进行的。在实践中,有时需要将相关实体(诸如相机和光源)转换到一些其它空间(诸如模型或观察空间)并在那里执行计算,也可以得到正确的结果。

    • 模型和视图变换 Model and View Transform

      • 在屏幕上的显示过程中,模型通常需要变换到若干不同的空间或坐标系中。模型变换的变换对象一般是模型的顶点和法线。物体的坐标称 为模型坐标。世界空间是唯一的,所有的模型经过变换后都位于同一个空间中。
      • 为了便于投影和裁剪,必须对相机和所有的模型进行视点变换。变换的目的就是要把相机放在原点,然后进行视点校准,使其朝向 Z 轴负方向, y 轴指向上方 ,x 轴指向右边。在视点变换后,实际位置和方向就依赖于当前的 API 。我们称上述空间为相机空间或者观察空间。

  • 可选顶点处理(它们的使用既取决于硬件的性能(不是所有GPU都有),也取决于程序员的需求)

    • 曲面细分:通过曲面细分,一个曲面可以生成一个适当数量的三角形。由一系列阶段构成——外壳着色器[hull shader]、曲面细分[tessellator]和域着色器[domain shader]。靠近的时候面数很多,远离的时候很少
    • 几何着色器:接受各种类型的图元,并可以产生新的顶点。几何着色器有几个用途,其中最流行的是粒子生成
    • 流输出:使用GPU作为一个几何引擎;可以选择将它们输出到数组中以进行进一步处理;这些数据可以被CPU或GPU本身在以后使用;通常用于粒子模拟
  • 投影 Projection。 将视体变换到一个对角顶点分别是(-1,-1,-1)和(1,1,1)单位立方体(unit cube)内,这个单位立方体通常也被称为规范立方体(Canonical View Volume,CVV)。(虽然这些矩阵变换是从一个可视体变换到另一个,但它们仍被称为投影,因为在完成显示后,Z坐标将不会再保存于的得到的投影图片中。通过这样的投影方法,就将模型从三维空间投影到了二维的空间中。)

  • 裁剪 Clipping。 裁剪阶段的目的,就是对部分位于视体内部的图元进行裁剪操作。可见范围裁剪体内的图元被传递到屏幕映射阶段。

  • 屏幕映射 Screen Mapping。 屏幕映射主要目的就是将上一步传过来的图元找到屏幕上对应的坐标。

    • 整数和浮点值与像素(和纹理坐标)的关系:给定一个水平像素数组并使用笛卡尔坐标,最左边的像素的左边缘在浮点坐标中为0.0。这个像素的中心是0.5。因此,像素范围[0,9]覆盖的浮点数范围从[0.0,10.0)
    • 所有API的像素位置值都是从左到右递增的,但是在OpenGL和DirectX某些情况下,顶部边缘和底部边缘零的位置是不一致的。从一种API迁移到另一种API时需要来考虑这种差异。
      • OpenGL将左下角作为最小值元素
      • DirectX将左上角作为最小值元素

三、光栅化阶段

光栅(栅格化或者像素化)把图元映射为最终屏幕上显示的颜色。

  • 三角形设定阶段: 这一个阶段也称作primitive assembly,也就是将一个个图元组装起来,会计算图元的边方程和一些其他信息,说白了就是将各个点连接起来,组成真正的三角形(或者连成线)。 然后下一阶段会利用这些信息进行三角形的遍历,也就是检查有哪些像素位于该三角形图元内,对于点、线图元,就看他们覆盖了哪些像素。
  • 三角形遍历: 找到哪些采样点或像素在三角形中的过程通常叫三角形遍历。

四、像素处理阶段

像素处理是对图元内的像素或采样点进行逐像素或逐采样点计算和操作的阶段。

  • 像素着色阶段: 这个阶段的目的是给输入每一个Pixel赋予正确的颜色,这些颜色的来源可以是纹理、图元的顶点信息和光照信息等。
    • 首先对于纹理信息,前面的阶段我们已经得到了每个Vertex Shader的纹理坐标,那么我们就可以拿着这个这个坐标去纹理单元中裁剪出这个图元对应的纹理,然后再给图元中的每个像素赋值。
    • 之前在Vertex Shading阶段提到过,可以给每个Vertex加上对应的光照、阴影信息,那么这些信息怎么作用到图元中的每个像素呢?在GPU中是通过插值(Interpolate)来实现的,具体就是用三角形三个顶点的值,来得到三角形中某个点的相应信息。
    • 以上是逐顶点计算,如果三角形比较大,插值出来的光照效果可能不理想,于是可以使用Per pixel lighting,顾名思义,就是对每个像素进行光照处理。具体实现方法是用光源向量、射线向量、法线向量来计算每个像素的光照信息
  • 融合阶段:融合阶段的主要任务是合成当前储存于缓冲器中的由之前的像素着色阶段产生的片段颜色,说白了就是输入的每个Pixel都有相应的颜色信息,被存储在Color buffer,这一阶段就是要整合这些Pixel信息,以得到具体一帧的图像信息,这一阶段也被称作ROP。此外,融合阶段还负责透明度测试、深度测试、模板测试的处理。

当图元通过光栅化阶段之后,从相机视点处看到的东西就可以在荧幕上显示出来。为了避免观察者体验到对图元进行处理并发送到屏幕的过程,图形系统一般使用了双缓冲(double buffering)机制,这意味着屏幕绘制是在一个后置缓冲器(backbuffer)中以离屏的方式进行的。一旦屏幕已在后置缓冲器中绘制,后置缓冲器中的内容就不断与已经在屏幕上显示过的前置缓冲器中的内容进行交换。注意,只有当不影响显示的时候,才进行交换。

GPU管线(实现了上述的概念管线):

  • 从Vertex Shader到Screen Mapping都属于Geometry Processing,其中前三个阶段属于Vertex Shading阶段。
  • 然后通过Projection、Clipping,得到unit-cube(也被称作canonical view volume),再进行Screen Mapping,转换到对应的屏幕坐标系。
  • 接下来就到了光栅化阶段,即通过Triangle Setup & Travelsal,得到各个图元对应的像素,拿着这些像素,就来到了最后的Pixel Processing阶段。
  • 首先通过Pixel Shader得到每个像素的颜色信息,再通过Merging进行各种测试、混合颜色,以得到最终的画面信息。到这里,就完成了整条Rendering Pipeline,下一步就可以将画面输出到显示器上了。

概念补充

垂直同步与双重缓冲、三重缓冲

开启垂直同步会带来两个问题:

第一,帧数下降。一个显而易见的原因是,如果刷新率是60HZ,那么显卡如果需要等待垂直同步信号才生成下一帧,那么帧数就不会超过60帧/s。但是这还不是问题的全部,如果你的显卡性能并不足以保证每秒60帧是速度呢?那么就会出现在一个垂直同步信号周期内,渲染不出一帧画面的情况,那么显卡就会将上一帧画面输出,而延迟1个周期才输出下一帧画面。如果不开垂直同步你的帧数有50帧的话,开了垂直同步,可能就会只有40帧左右,甚至更少;

第二,操作延迟。原因和帧数下降是基本一样的。你的操作决定了显卡如何生成下一帧画面,而如果这一帧画面需要2、3个垂直同步信号周期才能输出到显示器上,延迟感就会非常的明显。但是和帧数下降问题不同,即便你显卡速度很快,能够在开启垂直同步的情况下依然保持60FPS,这个操作延迟问题也依然存在。原因在于显卡在等待垂直同步过程中,不生成新帧,而你的操作却是连续的。这样连续的操作却不能生成连续的帧,操作就会出现延迟。

解决方案:三重缓冲

所谓的三重缓冲,说白了,就是我们有3个帧缓冲器!除去我们正常使用的,向显示器输出信号的缓冲区(A区),我们另外再开辟2个缓冲区(B1、B2区),当A区已满,等待垂直同步信号的时候,显卡生成的下一帧画面,写入到B1区;如果还需要再生成下一帧,则写入B2区;如果再再需要下一帧,则写入B1区。总之,在得到垂直同步信号之前,帧数据总是写入B1或者B2区内,当获得垂直同步信号之后,将最新的一帧写入A1区,输出到显示器。当然,这只是原理,实际上并没有固定用处的缓冲区,哪个缓冲区是最新生成的一帧,哪个就可以作为A区输出帧画面数据。

这样,我们首先解决了帧数下降的问题,由于显卡不需要暂停等待信号才生成新帧,所以理论上帧率就和你不开垂直同步是一样的。帧率不会降低,操作的延迟也就不会有那么明显。虽然最终得到的还是和刷新率一样的画面帧数,但由于画面和操作都是连续的,因此也不会造成额外的操作延迟。

三重缓冲的问题: 显存占用非常大。这很好理解,原本只需要1个的缓冲区,现在变成三个,显存占用率自然也就是三倍。如果你再开个高分辨率,抗锯齿什么的,那显存占用率那是噌噌往上涨的。

参考


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!