精华内容
参与话题
问答
  • 渲染管线是指在显示器上为了显示出图像而经过的一系列必要操作; 》GPU的渲染管线就是告诉GPU一堆数据,最后得出来一副二维图像,而这些数据就包括了”视点、三维物体、光源、照明模型、纹理”等元素。  》渲染...



    》GPU的渲染管线就是告诉GPU一堆数据,最后得出来一副二维图像,而这些数据就包括了”视点、三维物体、光源、照明模型、纹理”等元素。 


    》渲染管线主要分为三个阶段:
    1.应用程序阶段:主要是CPU与内存打交道,例如碰撞检测,计算好的数据(顶点坐标、法向量、纹理坐标、纹理)就会通过数据总线传给图形硬件 
    2.几何阶段:主要负责顶点坐标变换、光照、裁剪、投影以及屏幕映射,在该阶段的末端得到了经过变换和投影之后的顶点坐标、颜色、以及纹理坐标。简而言之,几何阶段的主要工作就是“变换三维顶点坐标”和“光照计算”。 
    》顶点变换(Vertex Transformation)或坐标系变换,包括:
    Object space,模型坐标;
    World space,世界坐标;
    Eye space,视点坐标(这里进行视锥裁剪);
    Clip and Project space裁剪空间坐标(也叫屏幕坐标,在这里进行 投影、剪裁、映射的过程)。
    eye space坐标转换到project and clip space坐标的过程其实就是一个投影、剪裁、映射的过程。

    (1),用透视变换矩阵把顶点从视锥体变换到CVV中; 
    (2),在CVV内进行剪裁; 
    (3),屏幕映射:将经过前两步得到的坐标映射到屏幕坐标系上。


    3.光栅阶段:

    》图元装配(Primitive Assembly)。

    》光栅化和插值(Rasterization and Interpolation)。

    (三维模型经过了顶点变换已经投影到了二维平面上,而且被装配成图源了,但是屏幕是一个一个细细小小的像素格子组成的,对于一个图片,电脑是怎么知道应该把它搞到哪几个像素上去显示的呢?这就是光栅化。说白了就是,把想看见的图片,搞成像素能显示的。光栅化工作告诉了显卡,哪个格子是你这个图形应该画上的,哪个不是)

    》像素操作(Pixel Operation)也叫光栅操作,包括:像素所有权测试->裁剪测试->alpha测试->模板测试->深度测试->混合->抖动显示->逻辑操作

    》帧缓冲区(Frame Buffer



    展开全文
  • 固定渲染管线与可编程渲染管线

    千次阅读 2013-12-03 17:40:33
    1.固定渲染管线与可编程渲染管线的区别:  1)、固定渲染管线 ——这是标准的几何&光照(T&L)管线,功能是固定的,它控制着世界、视、投影变换及固定光照控制和纹理混合。T&L管线可以被渲染状态控制,矩阵,光照和...
    1.固定渲染管线与可编程渲染管线的区别:
    
      1)、固定渲染管线 ——这是标准的几何&光照(T&L)管线,功能是固定的,它控制着世界、视、投影变换及固定光照控制和纹理混合。T&L管线可以被渲染状态控制,矩阵,光照和采制参数。
      2)、顶点着色器——图形开发人员可以对渲染管线中的顶点运算和像素运算分别进行编程处理了,而无须象以前那样套用一些固定函数,取代设置参数来控制管线,最早出现与DX8,包括PS和VS两部分。

    2.为了解决D3D或者OpenGL对不同硬件厂商的支持,解决移植性的问题,可以通过将加速卡功能抽象出来,统一定义接口的形式来实现。于是,人们采用了典型的分层模式(参阅:设计模式),将一套应用程序分为3个层次:
      应用程序层 -> 硬件抽象层 -> 硬件层其中
          应用层就是游戏和应用软件开发人员的开发主体,他们调用统一的加速卡API来进行上层开发,而不用考虑移植性问题;
          硬件抽象层则抽象出硬件的加速功能,进行有利于应用层开发的封装,并向应用层开放API;
          硬件层将硬件驱动提供给抽象层,以实现抽象层加速功能的有效性。
      这个结构有效的将游戏和应用程序 与硬件加速卡隔离开,这就很好的提升了程序的移植能力。并且,还有一个好处就是,开发
      人员的知识复用率得到提高,从而降低了这类软件的开发门槛。

    3.3D加速卡的主要功能就是协助CPU,负责将内存中的矢量图像数据(顶点集合)进行变换、光照计算、裁剪等操作,最后经过光栅化将图像呈现给人眼。这个过程就叫做渲染
      D3D把整个渲染分为9个步骤,9个步骤的组合,就叫做流水线,或者叫 管线 (参阅 设计模式 之流水线模式)。
      D3D的渲染管线(Rendering Pipeline):局部坐标变换 -> 世界坐标变换 ->观察坐标变换->背面消除->光照->裁剪->投影->视口计算->光栅化。
      无论是固定渲染管线还是可编程管线,都需要经过这九个步骤:
      可编程管线,顾名思义,就是说管线中的某些环节是可以被控制的。人们可以通过对GPU中的着色器进行编程的方式,来控制、管理加速卡的渲染效果。
      着色器分为顶点着色器和像素着色器。
          顶点着色器是在进行坐标变换和光照计算时工作
          像素着色器是在光栅化环节工作。
      人们对着色器进行自定义编程时,这个流水线就叫做可编程管线。同时,D3D还提供默认的着色器程序,当游戏或应用程序完全使用默认着色器程序时,这个流水线就叫做固定管线。

    4.变换步骤:
      在固定管线中,变换分成2个步骤:
          局部坐标系到世界坐标系 和世界坐标系到观察坐标系。
              局部坐标系:就是建模坐标系,它是在建模时由3DMAX之类的工具定义的
              世界坐标系:用来统一场景中各个object的位置、尺寸等规格
              观察坐标系:也就是摄像机的坐标系。
          局部坐标系到世界坐标系变换:
              这个变换是为了把在不同建模工具或者有用不同规格的建模尺寸下设计的模型,都统一到一个通用的坐标系下面。这个动作的作用,就像秦始皇统一度量衡一样。从局部坐标系到世界坐标系这个变换的动作,通常是在游戏设计时,由游戏工具(如:场景编辑器)来预先计算,并且为每个模型都计算出一个变换矩阵(即记录模型在游戏场景中的大小、朝向、位置),叫做世界坐标变换矩阵。在渲染时,实时的应用这些矩阵来参与运算写法:
              D3DXMATRIX worldMatrix; //这个矩阵就从文件中读进来
              Device->SetTransform(D3DTS_WORLD, &worldMatrix); 注意,使用宏 D3DTS_WORLD
          从世界坐标系到观察坐标系变换:
              这个变换动作,实际上是为了简化运算而做的。这样变换之后,所有的顶点也就可以直接转换为向量,非常的便于某些计算。这个动作通常是,将摄像机平移到世界坐标系原点,再旋转摄像师,使它的光轴与世界坐标系z轴方向一致。与此同时,空间中的所有几何体都要随摄像机一同变换,以确保摄像机的视场不变。这个变换叫取景变换,变换后得到观察坐标系。写法如下:
              D3DXMATRIX ViewMatrix; D3DXVECTOR3 position, targetPoint, worldUp;
              D3DXMatrixLookAtLH(&ViewMatrix, //[OUT], 计算出的变换矩阵 &position, //摄像机位置 &targetPoint, //摄像机的观察朝向 &worldUp);//摄像机的y朝向,也就是头的朝向
              Device->SetTransform(D3DTS_VIEW, &ViewMatrix); 注意宏,D3DTS_VIEW
          其实,D3DXMatrixLookAtLH()函数只是类似功能函数中的一个,注意后缀 LH 这表示左手坐标系,对应的还有一个右手坐标系,它们的区别在于:左手系 其z轴向里为正;右手系向外为正。
     
    相关参考:
    OPENGL固定图形渲染管线操作细节
           
    展开全文
  • 渲染管线

    千次阅读 2017-02-07 15:00:36
    2.1为什么要介绍渲染管线?  在微软DirectX10.0规范的统一渲染架构发布以前,渲染管线曾经是选购显卡的一项重要指标。然而采用流处理器渲染架构,由于硬件工作效率更高,目前已经逐渐取代了采用渲染管线的传统架构...

    2.1为什么要介绍渲染管线?

      在微软DirectX10.0规范的统一渲染架构发布以前,渲染管线曾经是选购显卡的一项重要指标。然而采用流处理器渲染架构,由于硬件工作效率更高,目前已经逐渐取代了采用渲染管线的传统架构,在消费领域渲染管线的概念慢慢被淡化了。但是在计算机图形学领域,渲染管线却依然有着举足轻重的地位,因为它依然是实时渲染的基本原理。虽然在上一章提到了渲染管线这一概念,但并没有给出详细的解释,在这一章我要对渲染管线进行一个深入的分析。

    2.2什么是渲染管线

      渲染就是把3D场景转换到屏幕上2D图像的一个处理过程。

    这里写图片描述
    3D渲染过程

      有了渲染概念,渲染管线的概念就更加清晰了,它是显示芯片内部处理图形信号相互独立的的并行处理单元。每个阶段从上一个阶段接收输入,处理完成后输出到下一个阶段,整个过程就是为了完成一帧画面渲染。在某种程度上可以把渲染管线比喻为工厂里面常见的各种生产流水线,工厂里的生产流水线是为了提高产品的生产能力和效率,而渲染管线则是提高显卡的工作能力和效率。
      在这里还得解释一下渲染管线和GPU渲染管线的关系。在GPU问世的初期,处理能力非常有限,渲染管线很大程度上依赖于CPU,整个管线的任务是由CPU和GPU共同协作完成的。当然随着GPU日益强大,GPU承担了管线中所有的任务,所以现在渲染管线这一概念就等同于GPU渲染管线了。

    2.3渲染管线的结构

      在Real-Time Rendering一书中,作者把渲染管线分成了三个阶段:应用程序阶段、几何阶段以及光栅阶段。
      应用程序阶段,使用高级编程语言进行开发,主要和CPU 、内存打交道,诸如碰撞检测、场景图建立、空间八叉树更新、视锥裁剪等经典算法都在此阶段执行。在该阶段的末端,几何体数据(顶点坐标、法向量、纹理坐标、纹理等)通过数据总线传送到图形硬件。
      几何阶段,主要负责顶点坐标变换、光照、裁剪、投影以及屏幕映射,该阶段基于 GPU 进行运算,在该阶段的末端得到了经过变换和投影之后的顶点坐标、颜色、以及纹理坐标。
      光栅阶段,基于几何阶段的输出数据,为像素正确配色,以便绘制完整图像,该阶段进行的都是单个像素的操作,每个像素的信息存储在帧缓存中。
      但是我并不完全认同Real-Time Rendering中的阶段划分,目前主流的阶段划分方式只有后面两个阶段。尽管应用程序阶段是图形渲染的重要一环,但是它处理的任务只是优化渲染过程的输入数据以及相关逻辑处理,并不是真正的核心的渲染过程,而且该过程主要在CPU中完成。所以在接下来我将详细地阐述这两个阶段的过程。
      渲染管线根据硬件架构的不同分为固定渲染管线和可编程渲染管线,焦点在于GPU是否具有可编程性。

    2.4固定渲染管线

      在可编程渲染管线出现之前,渲染管线就是指固定渲染管线,后来为了便于区分才添加了“固定”二字。固定渲染管线具备了渲染管线流水线作业的优势,将复杂的过程分解成小的阶段,几何数据按工作流程依次经过各个阶段的处理,大大增强了渲染作业的处理效率。同时几何阶段和光栅阶段算法都是固化在GPU中,编程人员在开发时无须关注几何阶段的各种变换算法以及光栅阶段的着色算法,开发效率大幅提高。图2- 2展示了固定渲染管线的整个流程。

    这里写图片描述
    固定渲染管线流程

    2.4.1几何阶段

      几何阶段的主要工作就是三维顶点坐标变换和光照计算,由显卡中的“T&L”硬件来完成。
      什么是“T&L”硬件?“T&L”英文全称是“Transform&Lingting”,中文意思几何变换和光照。“T&L”硬件其实一个硬件级别的几何与光照转换引擎,它在GPU中的出现,使得CPU从复杂运算中解脱出来。这样一来,一个场景中可以添加更多的物体和几何细节,最终渲染效果更加细腻,同时渲染效率也大幅提升。
      那么为什么需要进行三维顶点坐标的变换?原因很简单,我们的真实世界是一个三维的空间,而显示屏幕是二维的平面,为了将三维的数据更加真实地绘制到屏幕上,并达到“跃然纸上”的效果,我们就需要顶点变换。通过一系列顶点变换,物体在世界空间被转换到屏幕空间。
      根据顶点坐标变换的先后顺序,主要有这样几个坐标空间:物体空间、世界空间、观视空间以及屏幕空间。

    物体空间到世界空间

      物体空间的是一个相对独立的局部空间,与其他物体没有任何参照关系。物体空间中的坐标都是在3DS MAX这类建模工具中生成的。
      而世界空间与物体空间的关键区别就在于前者中所有的物体把需要坐标原点作为参考点。物体导入场景中之后,需要为物体指定一个位置,这个位置就是物体在世界空间的一个全局坐标。物体空间到世界空间的变换由一个一个四阶矩阵来完成,称作世界矩阵。
      光照计算通常是在世界坐标空间中进行的,这也符合人类的生活常识。当然,也可以在观视空间中得到相同的光照效果,因为,在同一观察空间中物体之间的相对关系是保存不变的。
    在这里有一点非常重要。就是物体顶点的法相量是在物体空间中生成的,它也是需要变换到世界空间才能正常使用,和顶点坐标类似。但是二者的转换矩阵却是不一样的,法向量变换的矩阵是世界矩阵转置的逆矩阵。在固定管线中,虽然这一变换不需要我们手动完成,但是理解清楚对于后面的可编程管线非常重要。

    世界空间到观视空间

      每个人都是从各自的视点出发观察这个世界,无论是主观世界还是客观世界。同样,在计算机中每次只能从唯一的视角出发渲染物体。在游戏中,都会提供视点漫游的功能,屏幕显示的内容随着视点的变化而变化。这是因为GPU 将物体顶点坐标从世界空间转换到了观视空间。
      所谓观视空间,即以视点为原点,由视线方向、视角和远近平面,共同组成一个梯形体的三维空间,称之为视锥,如图所示。近平面,是梯形体较小的矩形面,作为投影平面,远平面是梯形体较大的矩形,在这个梯形体中的所有顶点数据是可见的,而超出这个梯形体之外的场景数据,会被视锥裁剪去掉。

    这里写图片描述
    视锥

    观视空间到投影空间

      理论上讲视锥剔除应该在投影之前完成,事实上这样也是可以的,但是有一个问题就是视锥是一个不规则的几何体,在其中完成多边形剔除并不是一件很容易的事。所以目前采用的方式是先投影,后剔除。
      但是这个投影并非简简单单投影到一个二维平面上,而是将观视空间中的场景投影到一个单位立方体中,俗称CCV(Canonical view volume)[5]。三维场景投影到三维空间,听起来也许很诧异。其实本质上还是投影到了二维平面上,因为CCV近平面的X、Y坐标与屏幕坐标相对应,Z坐标表示像素的深度值。我们常用的投影方式有两种:正投影和透视投影。从人眼观察世界的物理原理上来看,透视投影更加符合人类的视觉习惯。
    从数学上来讲,这个投影过程就是观视空间的顶点坐标乘以投影矩阵,顶点就变换到了投影空间CVV中。
      视锥剔除过程现在变得简单一些了,因为视体由视锥变成了CVV,位于CVV外面的图元全部被裁剔除,部分位于CVV之外的需要进行裁剪。这一过程在下面的图元装配时完成。

    图元装配

      顶点流经过变换后,按照管线的顺序,下一个流程就是图元装配。所谓图元装配,就是根据之前的图元分类信息把顶点装配成图元。在这个过程中,将生成一些三角形、线段和点。之前是对顶点处理,现在就是对图元就行裁剪,对于超出屏幕之外的图元进行裁剪。对于游戏来讲,一般装配的图元都是三角形,如果三角形的一部分在屏幕外面,那么经过裁剪之后就变成了四边形,这个四边形又需要分割成两个小三角形。到现在这个阶段,就只剩下基本的图元,不存在复杂的几何图形。
      到此为止,渲染管线的几何阶段就完成了,几何阶段的工作比较简单明晰,就是顶点变换和光照。接下来就是非常重要的光栅阶段。

    2.4.2光栅阶段

    光栅化

      图元装配后生成的图元要显示在屏幕上必须经过光栅化。光栅化就是决定哪些像素被几何图元覆盖的过程。每种图元根据指定的规则分别被光栅化,涉及一些填充算法如扫描线多边形填充算法、边界填充算法等,这里不做讨论。光栅化的结果就是片段位置的集合。当光栅化之后,一个图元拥有的顶点数目和产生的片段数目之间没有任何关系。比如,一个由三个顶点组成的三角形,占据整个屏幕需要上百万的片段。
      这个有必要解释一下片段的概念。说到片段,不得不提到像素。像素是图像元素的简称。一个像素代表帧缓存中某个指定位置的信息,比如颜色、深度和其他与这个位置关联的值。而片段就是像素形成之前的一个状态,可以算作潜在的像素。在完成最后的光栅操作,更新片段信息至帧缓存后,片段就成了名副其实的像素。

    插值、贴图以及着色

      当图元被光栅化成一个或者多个片段后,就会根据需要对片段进行插值、执行一系列的贴图以及数学操作,然后为每个片段确定最终颜色。除了确定最终的颜色意外,还需要确定最终的深度值,或者丢弃这个片段以避免更新帧缓存中对应像素。

    光栅操作

      光栅操作是在更新帧缓存之前,执行的最后的一系列片段操作。这些操作是Direct3D和OpenGL的一个标准组成部分。
    光栅操作会根据许多测试来检查每一个片段,这些测试包括裁剪测试、alpha测试、模板测试以及深度测试。如果其中一项测试不通过,片段就会被丢弃。如果所有测试都通过,执行完后续操作后,就会更新帧缓存中的对应像素值。
    这里写图片描述
    标准OpenGL和Direct3D的光栅操作流程

    2.5可编程渲染管线

      固定渲染管线看似很完美,预制好的算法,操作简单,整个流程各环节分工明确,配合紧密。但是这样经典的模型同样存在着诸多短板,导致整个渲染的效率和质量很难再大幅提升。曾经的优势随着科技的进步反倒成了劣势,算法都固化在硬件内部,所有开发人员只能采用同一种固定的渲染方式,这样极大的束缚了开发人员的常造型思维。固定渲染管线已经完全不能满足时代对于渲染的要求,所以一种新的渲染模型——可编程渲染管线诞生了。
    可编程渲染管线顾名思义,它的关键就在于GPU内部提供了可编程性。这种可编程性的出现,对开发人员来说是一种思想的解放。

    2.5.1可编程渲染管线VS固定渲染管线

      对比一下可编程渲染管线与固定渲染管线的流程图,可以很明显的发现他们之间的共性和差异。
      关于共性,整个渲染流程大体上都是一致的,都是分为几何阶段和光栅阶段,其中流水线中顶点变换、图元装配、光栅化、插值、光栅操作等关键操作都是一致的。只是在具体操作中采用的方式不同而已。
      重点就在于二者的差异。可以看到图中的可编程渲染管线多了两条分支——可编程顶点处理器和可编程片段处理器。在几何阶段没有了一系列坐标变换的过程,这缘于“T&L”硬件被移除了,取而代之的是可编程顶点处理器,顶点变换和光照计算都由它来完成。在光栅阶段,颜色计算过程由可编程片段处理器取代了。
      接下来将要详细介绍可编程处理器。

    2.5.2可编程处理器

      可编程处理器是内嵌在GPU上的一种可编程单元,拥有非常强大的并行计算能力,并且擅长不高于4阶的矩阵运算。而且随着技术的发展,对浮点运算能力越来越强大。
      可编程处理器还有个别名——着色器,使用的比较广泛。所以上面的两种可编程处理器也叫做顶点着色器和片段着色器(或者像素着色器)。
      可编程处理器只是一个单纯的硬件,还需要有运行在处理器上的程序来驱动,而这种程序叫做着色器程序,也叫shader程序。程序和处理器之间有一一对应关系,顶点shader程序运行在顶点着色器上,片段shader程序运行片段着色器上。前面已经提到顶点着色器接管了“T&L”硬件的任务,片段着色器负责颜色计算。那么shader程序也有各自分工,顶点shader程序负责顶点变换和光照计算,片段shader程序负责颜色计算,而且前者的输出是后者的输入。
      由于每一代可编程处理器的架构有一定差异,没有一个绝对统一的标准。本文将以遵循Shader Model 2.0规范的可编程处理器为例来介绍。

    可编程顶点处理器

      在渲染3D场景的时候,顶点信息是通过Direct3D或者OpenGL这类3D渲染API传递给GPU。当GPU接收到顶点信息,它会为应用程序提交的每一个顶点调用一次顶点着色器程序。图2- 8就是可编程顶点处理器的架构图。
    这里写图片描述
    可编程顶点处理器架构

      从图中我们可以看到顶点信息是以流的形式传递进来,这些信息包括顶点坐标、纹理坐标、颜色等等。信息传递完毕后,会被存放顶点数据寄存器(也叫输入寄存器)中。输入寄存器是一种只读寄存器,从图中可以看到输入寄存器有16个,v0到v15,说明最多可以存储16个顶点属性。然后,顶点处理器会通过访问其他的一些寄存器来完成几何阶段的任务。
      常量寄存器也是一种只读寄存器,存储一些预先设定的静态参数。
      临时寄存器可以进行读写操作,存放计算的中间结果。注意其中的a0和aL寄存器,他们主要用于循环计数和按索引寻址。
      由于顶点处理器可以随意访问以上三种寄存器,所以编程人员可以按照自己的方式处理操作和处理寄存器中的数据。这在固定渲染管线时代是不可想象的事情。计算完毕,定点处理器要把计算结果存入输出寄存器。这里必须把屏幕空间的顶点坐标放入oPos寄存器中,当然还可以传递其他的信息比如颜色、纹理坐标等等到其他寄存器中。结果存放完毕后,接下来就进入了图元装配阶段和光栅化阶段,这个与固定渲染管线一致,不具备可编程性。

    可编程片段处理器

      光栅化操作完后,生成了一系列片段,这时候片段处理器会为每个片段调用片段着色程序。
    这里写图片描述
    可编程片段着色器架构图

      片段处理器内部架构和顶点处理器基本框架是差不多的,只是有些细节上的差异。这里是输入寄存器变成了颜色寄存器和纹理寄存器,因为现在操作的对象是片段。寄存器v0和v1分别表示插值过以后的漫反射和镜面反射分量。t0到tN这一系列寄存是用于存储纹理查找坐标。在片段处理阶段,采样寄存器指向需要使用的纹理。常量寄存器和临时寄存器基本一致。
      计算完毕后,输出寄存器要把计算出的最终颜色存入寄存器。这里的寄存器除了oC0以外,还有oDepth,它里面的深度值是在光栅操作中做深度测试用的。
      两种可编程处理器介绍完了,在这里有两点需要说明一下。第一点就是在本章最开始提到的统一渲染架构,已经取消了专门的可编程处理器,shader程序与处理器之间没有对应关系。取而代之的是统一的流处理器,能运行所有类型的shader程序,这样的好处是在各类场景渲染时能保持负载均衡。第二点是逻辑上的可编程处理器还有一类可编程几何处理器,目前只有较新的硬件支持,应用不广泛,本文不做介绍。

    2.6本章小结

      本章介绍了GPU 图形渲染管线,并对相关的图形硬件进行了阐述。图形渲染管线是GPU 编程的基础,事实上顶点着色程序和片段着色程序正是按照图渲染制管线而划分的。

    展开全文
  • 至2018.1版本,Unity中除了默认渲染管线,还提供了轻量级渲染管线(Lightweight Pipeline)和高清晰渲染管线(HD Pipleline)二个渲染管线。当然也支持自定义渲染管线。与高清晰渲染管线相比,...

     

    前言

    Unity2018中引入了可编程渲染管线(Scriptable Render Pipeline,简称SRP),是一种在Unity中通过C#脚本配置和执行渲染的方式。至2018.1版本,Unity中除了默认渲染管线,还提供了轻量级渲染管线(Lightweight Pipeline)和高清晰渲染管线(HD Pipleline)二个渲染管线。当然也支持自定义渲染管线。与高清晰渲染管线相比,轻量级渲染管线的开发已经比较成熟。

    这篇文章主要是分析轻量级渲染管线的C#代码都做了哪些工作。

     

    可编程渲染管线能做什么

    为了解决仅有一个默认渲染管线,造成的可配置型、可发现性、灵活性等问题。Unity在管线设计的概念上做了转移,决定在C++端保留一个非常小的渲染内核,让C#端可以通过API暴露出更多的选择性,也就是说,Unity会提供一系列的C# API以及内置渲染管线的C#实现;这样一来,一方面可以保证C++端的代码都能严格通过各种白盒测试,另一方面C#端代码就可以在实际项目中调整,有任何问题也可以方便地进行调试。

    新的管线对用户而言主要是C# 端的API以及由这些API编写的一系列定制化的内置渲染管线。而在内部实现上,引擎C++端会负责多线程实现性能关键的部分,如上图所示,而C#端负责更高层的渲染指令调度。[1]

    可编程渲染管线的使用层设计

    用户可以直接使用开源的内置管线,或者在内置管线的基础上进行修改,甚至直接编写定制化的管线。具体使用上渲染管线在工程中会生成特定的Asset,如下图所示,这个Asset序列化了这条管线的一些公共设置变量,并负责在运行时创建实际的渲染上下文;当这个Asset的设置变量在运行时发生变化,引擎会销毁当前上下文然后重新创建管线(这个操作在现有固定管线中无法做到)。

     

    简单的渲染管线示例

    可编程渲染管线的使用层设计中,最少需要两个类,一个是渲染管线资源,一个是渲染管线实例。

    下面是一个不透明渲染管线的C#代码查看详细

    using UnityEngine;
    using UnityEngine.Experimental.Rendering;
    using UnityEngine.Rendering;
    [ExecuteInEditMode]
    public class OpaqueAssetPipe : RenderPipelineAsset
    {
    #if UNITY_EDITOR
        [UnityEditor.MenuItem("SRP-Demo/02 - Create Opaque Asset Pipeline")]
        static void CreateBasicAssetPipeline()
        {
            var instance = ScriptableObject.CreateInstance<OpaqueAssetPipe>();
            UnityEditor.AssetDatabase.CreateAsset(instance, "Assets/SRP-Demo/2-OpaqueAssetPipe/OpaqueAssetPipe.asset");
        }
    #endif
    
        protected override IRenderPipeline InternalCreatePipeline()
        {
            return new OpaqueAssetPipeInstance();
        }
    }
    
    public class OpaqueAssetPipeInstance : RenderPipeline
    {
        public override void Render(ScriptableRenderContext context, Camera[] cameras)
        {
            base.Render(context, cameras);
            foreach (var camera in cameras)
            {
                ScriptableCullingParameters cullingParams;
                if (!CullResults.GetCullingParameters(camera, out cullingParams))
                    continue;
                CullResults cull = CullResults.Cull(ref cullingParams, context);
                context.SetupCameraProperties(camera);
    
                var cmd = new CommandBuffer();
                cmd.ClearRenderTarget(true, false, Color.black);
                context.ExecuteCommandBuffer(cmd);
                cmd.Release();
    
                var settings = new DrawRendererSettings(camera, new ShaderPassName("BasicPass"));
                settings.sorting.flags = SortFlags.CommonOpaque;
                var filterSettings = new FilterRenderersSettings(true) { renderQueueRange = RenderQueueRange.opaque };
                context.DrawRenderers(cull.visibleRenderers, ref settings, filterSettings);
                context.DrawSkybox(camera);
                context.Submit();
            }
        }
    }

    这里定义了一个继承自RenderPipelineAsset的类,通过这个类创建一个渲染管线实例:继承自RenderPipeline的类。

    渲染管线实例中的Render方法是渲染入口点,它需要两个参数,渲染上下文以及一个需要渲染的摄像机列表。

    在Render方法中依次进行了剔除、绘制、过滤等操作,通过渲染命令缓冲向渲染上下文发送命令来实现绘制。

    轻量级渲染管线

    以下内容来自Unity官方在GitHub中的项目,本文将在代码中通过注释来解释渲染管线的功能。

    渲染管线资源

    渲染管线资源是项目中实际使用的资源,它包含一系列的可调整设置,并通过这些设置创建渲染管线实例,在设置发生变更时重新创建。

    #if UNITY_EDITOR
    using System;
    using UnityEditor;
    using UnityEditor.ProjectWindowCallback;
    #endif
    
    namespace UnityEngine.Experimental.Rendering.LightweightPipeline
    {
        /// <summary>
        /// 阴影级联
        /// </summary>
        public enum ShadowCascades
        {
            NO_CASCADES = 0,
            TWO_CASCADES,
            FOUR_CASCADES,
        }
        /// <summary>
        /// 阴影类型
        /// </summary>
        public enum ShadowType
        {
            NO_SHADOW = 0,
            HARD_SHADOWS,
            SOFT_SHADOWS,
        }
        /// <summary>
        /// 阴影分辨率
        /// </summary>
        public enum ShadowResolution
        {
            _256 = 256,
            _512 = 512,
            _1024 = 1024,
            _2048 = 2048,
            _4096 = 4096
        }
        /// <summary>
        /// MSAA质量
        /// MSAA是“多重采样抗锯齿”,可以使画面更加平滑。
        /// 超级采样抗锯齿(Super Sampling Anti-Aliasing)的原理是把当前分辨率成倍提高,然后再把画缩放到当前的显示器上。
        /// 这样的做法实际上就是在显示尺寸不变的情况提高分辨率,让单个像素变得极小,这样就能够大幅减轻画面的锯齿感了。
        /// 不过是由于对整个显示画面的放大,因此它消耗的显示资源也是非常大的。
        /// 不过MSAA是寻找出物体边缘部分的像素,然后对它们进行缩放处理。
        /// 由于只是物体的外层像素进行缩放处理,忽略掉了不会产生锯齿的内部像素,
        /// 所以显卡不会像处理SSAA(超级采样抗锯齿)那样需要庞大的计算量,因此MSAA比起SSAA来更有效。
        /// </summary>
        public enum MSAAQuality
        {
            Disabled = 1,
            _2x = 2,
            _4x = 4,
            _8x = 8
        }
        /// <summary>
        /// 降采样,在ForwardLitPass中使用
        /// </summary>
        public enum Downsampling
        {
            None = 0,
            _2xBilinear,
            _4xBox,
            _4xBilinear
        }
        /// <summary>
        /// 默认材质类型
        /// LightweightPipelineEditorResources的资源中获得
        /// </summary>
        public enum DefaultMaterialType
        {
            Standard = 0,
            Particle,
            Terrain,
            UnityBuiltinDefault
        }
    
        public class LightweightPipelineAsset : RenderPipelineAsset, ISerializationCallbackReceiver
        {
            // 这两个路径用于查找ScriptableObject,优先在"Assets"中查找,若不存在直接取默认
            public static readonly string s_SearchPathProject = "Assets";
            public static readonly string s_SearchPathPackage = "Packages/com.unity.render-pipelines.lightweight";
    
            /// <summary>
            /// GetDefaultShader()方法调用默认shader的私有变量
            /// </summary>
            Shader m_DefaultShader;
    
            // Default values set when a new LightweightPipeli ne asset is created
            /// <summary>
            /// 版本号,用于开发过程的迭代,在ISerializationCallbackReceiver接口的OnAfterDeserialize()中可以将旧版数据转换为新版使用的数据
            /// </summary>
            [SerializeField] int k_AssetVersion = 3;
            #region Editor中包含的元素
            /// <summary>
            /// 最大逐像素光源个数
            /// </summary>
            [SerializeField] int m_MaxPixelLights = 4;
            /// <summary>
            /// 支持顶点光照
            /// </summary>
            [SerializeField] bool m_SupportsVertexLight = false;
            /// <summary>
            /// 需要深度纹理
            /// </summary>
            [SerializeField] bool m_RequireDepthTexture = false;
            /// <summary>
            /// 需要软粒子
            /// </summary>
            [SerializeField] bool m_RequireSoftParticles = false;
            /// <summary>
            /// 需要不透明贴图
            /// </summary>
            [SerializeField] bool m_RequireOpaqueTexture = false;
            /// <summary>
            /// 不透明降采样
            /// </summary>
            [SerializeField] Downsampling m_OpaqueDownsampling = Downsampling._2xBilinear;
            /// <summary>
            /// 支持HDR
            /// </summary>
            [SerializeField] bool m_SupportsHDR = false;
            /// <summary>
            /// MSAA
            /// </summary>
            [SerializeField] MSAAQuality m_MSAA = MSAAQuality._4x;
            /// <summary>
            /// 渲染比例
            /// </summary>
            [SerializeField] float m_RenderScale = 1.0f;
            /// <summary>
            /// 支持动态批处理
            /// </summary>
            [SerializeField] bool m_SupportsDynamicBatching = true;
            /// <summary>
            /// 支持定向阴影
            /// </summary>
            [SerializeField] bool m_DirectionalShadowsSupported = true;
            /// <summary>
            /// 阴影图集分辨率
            /// </summary>
            [SerializeField] ShadowResolution m_ShadowAtlasResolution = ShadowResolution._2048;
            /// <summary>
            /// 阴影距离
            /// </summary>
            [SerializeField] float m_ShadowDistance = 50.0f;
            /// <summary>
            /// 阴影级联
            /// </summary>
            [SerializeField] ShadowCascades m_ShadowCascades = ShadowCascades.FOUR_CASCADES;
            /// <summary>
            /// 二级级联分界
            /// </summary>
            [SerializeField] float m_Cascade2Split = 0.25f;
            /// <summary>
            /// 四级级联分界
            /// </summary>
            [SerializeField] Vector3 m_Cascade4Split = new Vector3(0.067f, 0.2f, 0.467f);
            /// <summary>
            /// 支持非平行光阴影
            /// </summary>
            [SerializeField] bool m_LocalShadowsSupported = true;
            /// <summary>
            /// 非平行光阴影图集分辨率
            /// </summary>
            [SerializeField] ShadowResolution m_LocalShadowsAtlasResolution = ShadowResolution._512;
            /// <summary>
            /// 支持软阴影
            /// </summary>
            [SerializeField] bool m_SoftShadowsSupported = false; 
            #endregion
            //以下内容,关系到LightweightPipelineCore中的 static PipelineCapabilities s_PipelineCapabilities
            [SerializeField] bool m_KeepAdditionalLightVariants = true;
            [SerializeField] bool m_KeepVertexLightVariants = true;
            [SerializeField] bool m_KeepDirectionalShadowVariants = true;
            [SerializeField] bool m_KeepLocalShadowVariants = true;
            [SerializeField] bool m_KeepSoftShadowVariants = true;
            /// <summary>
            /// 4个shader :BlitShader;CopyDepthShader;ScreenSpaceShadowShader;SamplingShader;
            /// </summary>
            [SerializeField] LightweightPipelineResources m_ResourcesAsset;
    
            // Deprecated
            [SerializeField] ShadowType m_ShadowType = ShadowType.HARD_SHADOWS;
    
    #if UNITY_EDITOR
            /// <summary>
            /// 三个材质
            /// </summary>
            [NonSerialized]
            LightweightPipelineEditorResources m_EditorResourcesAsset;
    
            [MenuItem("Assets/Create/Rendering/Lightweight Pipeline Asset", priority = CoreUtils.assetCreateMenuPriority1)]
            static void CreateLightweightPipeline()
            {
                ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0, CreateInstance<CreateLightweightPipelineAsset>(),
                    "LightweightAsset.asset", null, null);
            }
    
            //[MenuItem("Assets/Create/Rendering/Lightweight Pipeline Resources", priority = CoreUtils.assetCreateMenuPriority1)]
            static void CreateLightweightPipelineResources()
            {
                var instance = CreateInstance<LightweightPipelineResources>();
                AssetDatabase.CreateAsset(instance, string.Format("Assets/{0}.asset", typeof(LightweightPipelineResources).Name));
            }
    
            //[MenuItem("Assets/Create/Rendering/Lightweight Pipeline Editor Resources", priority = CoreUtils.assetCreateMenuPriority1)]
            static void CreateLightweightPipelineEditorResources()
            {
                var instance = CreateInstance<LightweightPipelineEditorResources>();
                AssetDatabase.CreateAsset(instance, string.Format("Assets/{0}.asset", typeof(LightweightPipelineEditorResources).Name));
            }
    
            /// <summary>
            /// 创建带初始化的窗口
            /// </summary>
            class CreateLightweightPipelineAsset : EndNameEditAction
            {
                public override void Action(int instanceId, string pathName, string resourceFile)
                {
                    var instance = CreateInstance<LightweightPipelineAsset>();
                    instance.m_EditorResourcesAsset = LoadResourceFile<LightweightPipelineEditorResources>();
                    instance.m_ResourcesAsset = LoadResourceFile<LightweightPipelineResources>();
                    AssetDatabase.CreateAsset(instance, pathName);
                }
            }
    
            /// <summary>
            /// 加载ScriptableObject资源,先查找Asset文件夹,若不存在使用默认
            /// </summary>
            /// <typeparam name="T"></typeparam>
            /// <returns></returns>
            static T LoadResourceFile<T>() where T : ScriptableObject
            {
                T resourceAsset = null;
                var guids = AssetDatabase.FindAssets(typeof(T).Name + " t:scriptableobject", new[] {s_SearchPathProject});
                foreach (string guid in guids)
                {
                    string path = AssetDatabase.GUIDToAssetPath(guid);
                    resourceAsset = AssetDatabase.LoadAssetAtPath<T>(path);
                    if (resourceAsset != null)
                        break;
                }
    
                // There's currently an issue that prevents FindAssets from find resources withing the package folder.
                if (resourceAsset == null)
                {
                    string path = s_SearchPathPackage + "/LWRP/Data/" + typeof(T).Name + ".asset";
                    resourceAsset = AssetDatabase.LoadAssetAtPath<T>(path);
                }
                return resourceAsset;
            }
    
            LightweightPipelineEditorResources editorResources
            {
                get
                {
                    if (m_EditorResourcesAsset == null)
                        m_EditorResourcesAsset = LoadResourceFile<LightweightPipelineEditorResources>();
    
                    return m_EditorResourcesAsset;
                }
            }
    #endif
            LightweightPipelineResources resources
            {
                get
                {
    #if UNITY_EDITOR
                    if (m_ResourcesAsset == null)
                        m_ResourcesAsset = LoadResourceFile<LightweightPipelineResources>();
    #endif
                    return m_ResourcesAsset;
                }
            }
            /// <summary>
            /// 创建渲染管线的方法
            /// </summary>
            /// <returns></returns>
            protected override IRenderPipeline InternalCreatePipeline()
            {
                return new LightweightPipeline(this);
            }
    
            /// <summary>
            /// 获得ScriptableObject中的材质资源
            /// </summary>
            /// <param name="materialType"></param>
            /// <returns></returns>
            Material GetMaterial(DefaultMaterialType materialType)
            {
    #if UNITY_EDITOR
                if (editorResources == null)
                    return null;
    
                switch (materialType)
                {
                    case DefaultMaterialType.Standard:
                        return editorResources.DefaultMaterial;
    
                    case DefaultMaterialType.Particle:
                        return editorResources.DefaultParticleMaterial;
    
                    case DefaultMaterialType.Terrain:
                        return editorResources.DefaultTerrainMaterial;
    
                    // Unity Builtin Default
                    default:
                        return null;
                }
    #else
                return null;
    #endif
            }
            /// <summary>
            /// 获得版本号
            /// </summary>
            /// <returns></returns>
            public int GetAssetVersion()
            {
                return k_AssetVersion;
            }
            /// <summary>
            /// 最大逐像素光源数量
            /// </summary>
            public int maxPixelLights
            {
                get { return m_MaxPixelLights; }
            }
            /// <summary>
            /// 支持顶点光照
            /// </summary>
            public bool supportsVertexLight
            {
                get { return m_SupportsVertexLight; }
            }
            /// <summary>
            /// 支持深度纹理
            /// </summary>
            public bool supportsCameraDepthTexture
            {
                get { return m_RequireDepthTexture; }
            }
            /// <summary>
            /// 支持软粒子
            /// </summary>
            public bool supportsSoftParticles
            {
                get { return m_RequireSoftParticles; }
            }
            /// <summary>
            /// 支持不透明贴图
            /// </summary>
            public bool supportsCameraOpaqueTexture
            {
                get { return m_RequireOpaqueTexture; }
            }
            /// <summary>
            /// 不透明降采样 Downsampling._2xBilinear
            /// </summary>
            public Downsampling opaqueDownsampling
            {
                get { return m_OpaqueDownsampling; }
            }
            /// <summary>
            /// 支持HDR
            /// </summary>
            public bool supportsHDR
            {
                get { return m_SupportsHDR; }
            }
            /// <summary>
            /// MSAA的程度
            /// </summary>
            public int msaaSampleCount
            {
                get { return (int)m_MSAA; }
                set { m_MSAA = (MSAAQuality)value; }
            }
            /// <summary>
            /// 渲染比例
            /// </summary>
            public float renderScale
            {
                get { return m_RenderScale; }
                set { m_RenderScale = value; }
            }
            /// <summary>
            /// 支持动态批处理
            /// </summary>
            public bool supportsDynamicBatching
            {
                get { return m_SupportsDynamicBatching; }
            }
            /// <summary>
            /// 支持定向阴影
            /// </summary>
            public bool supportsDirectionalShadows
            {
                get { return m_DirectionalShadowsSupported; }
            }
            /// <summary>
            /// 定向阴影图集分辨率
            /// </summary>
            public int directionalShadowAtlasResolution
            {
                get { return (int)m_ShadowAtlasResolution; }
            }
            /// <summary>
            /// 阴影距离
            /// </summary>
            public float shadowDistance
            {
                get { return m_ShadowDistance; }
                set { m_ShadowDistance = value; }
            }
            /// <summary>
            /// 阴影级联(1、2、4)
            /// </summary>
            public int cascadeCount
            {
                get
                {
                    switch (m_ShadowCascades)
                    {
                        case ShadowCascades.TWO_CASCADES:
                            return 2;
                        case ShadowCascades.FOUR_CASCADES:
                            return 4;
                        default:
                            return 1;
                    }
                }
            }
            /// <summary>
            /// 阴影级联2级
            /// </summary>
            public float cascade2Split
            {
                get { return m_Cascade2Split; }
            }
            /// <summary>
            /// 阴影级联4级
            /// </summary>
            public Vector3 cascade4Split
            {
                get { return m_Cascade4Split; }
            }
            /// <summary>
            /// 支持非平行光阴影
            /// </summary>
            public bool supportsLocalShadows
            {
                get { return m_LocalShadowsSupported; }
            }
            /// <summary>
            /// 非平行光阴影分辨率
            /// </summary>
            public int localShadowAtlasResolution
            {
                get { return (int)m_LocalShadowsAtlasResolution; }
            }
            /// <summary>
            /// 支持软阴影
            /// </summary>
            public bool supportsSoftShadows
            {
                get { return m_SoftShadowsSupported; }
            }
            /// <summary>
            /// 自定义Shader变量分离,False
            /// </summary>
            public bool customShaderVariantStripping
            {
                get { return false; }
            }
            //以下内容,关系到LightweightPipelineCore中的 static PipelineCapabilities s_PipelineCapabilities
            public bool keepAdditionalLightVariants
            {
                get { return m_KeepAdditionalLightVariants; }
            }
    
            public bool keepVertexLightVariants
            {
                get { return m_KeepVertexLightVariants; }
            }
    
            public bool keepDirectionalShadowVariants
            {
                get { return m_KeepDirectionalShadowVariants; }
            }
    
            public bool keepLocalShadowVariants
            {
                get { return m_KeepLocalShadowVariants; }
            }
    
            public bool keepSoftShadowVariants
            {
                get { return m_KeepSoftShadowVariants; }
            }
            /// <summary>
            /// Lightweight-Default.mat
            /// </summary>
            /// <returns></returns>
            public override Material GetDefaultMaterial()
            {
                return GetMaterial(DefaultMaterialType.Standard);
            }
            /// <summary>
            /// Lightweight-DefaultParticle.mat
            /// </summary>
            /// <returns></returns>
            public override Material GetDefaultParticleMaterial()
            {
                return GetMaterial(DefaultMaterialType.Particle);
            }
            /// <summary>
            /// null
            /// </summary>
            /// <returns></returns>
            public override Material GetDefaultLineMaterial()
            {
                return GetMaterial(DefaultMaterialType.UnityBuiltinDefault);
            }
            /// <summary>
            /// Lightweight-DefaultTerrain.mat
            /// </summary>
            /// <returns></returns>
            public override Material GetDefaultTerrainMaterial()
            {
                return GetMaterial(DefaultMaterialType.Terrain);
            }
            /// <summary>
            /// null
            /// </summary>
            /// <returns></returns>
            public override Material GetDefaultUIMaterial()
            {
                return GetMaterial(DefaultMaterialType.UnityBuiltinDefault);
            }
            /// <summary>
            /// null
            /// </summary>
            /// <returns></returns>
            public override Material GetDefaultUIOverdrawMaterial()
            {
                return GetMaterial(DefaultMaterialType.UnityBuiltinDefault);
            }
            /// <summary>
            /// null
            /// </summary>
            /// <returns></returns>
            public override Material GetDefaultUIETC1SupportedMaterial()
            {
                return GetMaterial(DefaultMaterialType.UnityBuiltinDefault);
            }
            /// <summary>
            /// null
            /// </summary>
            /// <returns></returns>
            public override Material GetDefault2DMaterial()
            {
                return GetMaterial(DefaultMaterialType.UnityBuiltinDefault);
            }
            /// <summary>
            /// 获取默认shader
            /// LightweightShaderUtils内有静态数据
            /// 实际字符串为"LightweightPipeline/Standard (Physically Based)"
            /// Lightweight-Default.mat也使用该Shader
            /// </summary>
            /// <returns></returns>
            public override Shader GetDefaultShader()
            {
                if (m_DefaultShader == null)
                    m_DefaultShader = Shader.Find(LightweightShaderUtils.GetShaderPath(ShaderPathID.STANDARD_PBS));
                return m_DefaultShader;
            }
    
            /// <summary>
            /// "Hidden/LightweightPipeline/BlitShader"
            /// </summary>
            public Shader blitShader
            {
                get { return resources != null ? resources.BlitShader : null; }
            }
            /// <summary>
            /// "Hidden/LightweightPipeline/CopyDepthShader"
            /// </summary>
            public Shader copyDepthShader
            {
                get { return resources != null ? resources.CopyDepthShader : null; }
            }
            /// <summary>
            /// "Hidden/LightweightPipeline/ScreenSpaceShadows"
            /// </summary>
            public Shader screenSpaceShadowShader
            {
                get { return resources != null ? resources.ScreenSpaceShadowShader : null; }
            }
            /// <summary>
            /// "Hidden/LightweightPipeline/SamplingShader"
            /// </summary>
            public Shader samplingShader
            {
                get { return resources != null ? resources.SamplingShader : null; }
            }
    
            public void OnBeforeSerialize()
            {
            }
    
            /// <summary>
            /// m_ShadowType已弃用,将旧版资源的m_ShadowType转为m_SoftShadowsSupported字段
            /// </summary>
            public void OnAfterDeserialize()
            {
                if (k_AssetVersion < 3)
                {
                    k_AssetVersion = 3;
                    m_SoftShadowsSupported = (m_ShadowType == ShadowType.SOFT_SHADOWS);
                }
            }
        }
    }

    下面是两个上文引用到的,用来存储资源的脚本:

    using UnityEngine;
    
    public class LightweightPipelineResources : ScriptableObject
    {
        public Shader BlitShader;
        public Shader CopyDepthShader;
        public Shader ScreenSpaceShadowShader;
        public Shader SamplingShader;
    }
    
    using System;
    using UnityEngine;
    
    public class LightweightPipelineEditorResources : ScriptableObject
    {
        public Material DefaultMaterial;
        public Material DefaultParticleMaterial;
        public Material DefaultTerrainMaterial;
    }

    渲染管线实例

     

    渲染管线实例包含运行时逻辑、静态信息(RenderTexture、Buffer等),是渲染指令的设置处。

    using System;
    using System.Collections.Generic;
    #if UNITY_EDITOR
    using UnityEditor.Experimental.Rendering.LightweightPipeline;
    #endif
    using UnityEngine.Rendering;
    using UnityEngine.Rendering.PostProcessing;
    using UnityEngine.XR;
    
    namespace UnityEngine.Experimental.Rendering.LightweightPipeline
    {
        public partial class LightweightPipeline : RenderPipeline
        {
            /// <summary>
            /// 渲染管线资源
            /// </summary>
            public LightweightPipelineAsset pipelineAsset { get; private set; }
            /// <summary>
            /// 摄像机按深度排序
            /// </summary>
            CameraComparer m_CameraComparer = new CameraComparer();
            /// <summary>
            /// 前向渲染类
            /// </summary>
            LightweightForwardRenderer m_Renderer;
            /// <summary>
            /// 剔除结果
            /// </summary>
            CullResults m_CullResults;
            /// <summary>
            /// 非平行光源编号索引,用于阴影生成
            /// </summary>
            List<int> m_LocalLightIndices = new List<int>();
            /// <summary>
            /// 标记逐相机渲染进行过程
            /// </summary>
            bool m_IsCameraRendering;
    
            public LightweightPipeline(LightweightPipelineAsset asset)
            {
                pipelineAsset = asset;
    
                SetSupportedRenderingFeatures();
                SetPipelineCapabilities(asset);
    
                PerFrameBuffer._GlossyEnvironmentColor = Shader.PropertyToID("_GlossyEnvironmentColor");
                PerFrameBuffer._SubtractiveShadowColor = Shader.PropertyToID("_SubtractiveShadowColor");
    
                PerCameraBuffer._ScaledScreenParams = Shader.PropertyToID("_ScaledScreenParams");
                m_Renderer = new LightweightForwardRenderer(asset);
    
                // Let engine know we have MSAA on for cases where we support MSAA backbuffer
                if (QualitySettings.antiAliasing != pipelineAsset.msaaSampleCount)
                    QualitySettings.antiAliasing = pipelineAsset.msaaSampleCount;
    
                Shader.globalRenderPipeline = "LightweightPipeline";
                m_IsCameraRendering = false;
            }
    
            public override void Dispose()
            {
                base.Dispose();
                Shader.globalRenderPipeline = "";
                SupportedRenderingFeatures.active = new SupportedRenderingFeatures();
    
    #if UNITY_EDITOR
                SceneViewDrawMode.ResetDrawMode();
    #endif
    
                m_Renderer.Dispose();
            }
    
            public override void Render(ScriptableRenderContext context, Camera[] cameras)
            {
                if (m_IsCameraRendering)
                {
                    Debug.LogWarning("Nested camera rendering is forbidden. If you are calling camera.Render inside OnWillRenderObject callback, use BeginCameraRender callback instead.");
                    return;
                }
    
                //初始化每帧数据
                base.Render(context, cameras);
                BeginFrameRendering(cameras);
    
                GraphicsSettings.lightsUseLinearIntensity = true;
                SetupPerFrameShaderConstants();
    
                // Sort cameras array by camera depth
                Array.Sort(cameras, m_CameraComparer);
    
                foreach (Camera camera in cameras)
                {
                    BeginCameraRendering(camera);
                    string renderCameraTag = "Render " + camera.name;
                    CommandBuffer cmd = CommandBufferPool.Get(renderCameraTag);
                    //using 结束或中途中断会调用()中类的Dispose()方法
                    using (new ProfilingSample(cmd, renderCameraTag))
                    {
                        //初始化逐相机的数据
                        CameraData cameraData;
                        InitializeCameraData(camera, out cameraData);
                        SetupPerCameraShaderConstants(cameraData);
                        //剔除
                        ScriptableCullingParameters cullingParameters;
                        if (!CullResults.GetCullingParameters(camera, cameraData.isStereoEnabled, out cullingParameters))
                        {
                            CommandBufferPool.Release(cmd);
                            continue;
                        }
    
                        cullingParameters.shadowDistance = Mathf.Min(cameraData.maxShadowDistance, camera.farClipPlane);
                        //这里执行的new ProfilingSample(cmd, renderCameraTag)中设置的cmd.BeginSample(name);命令
                        context.ExecuteCommandBuffer(cmd);
                        cmd.Clear();
    
    #if UNITY_EDITOR
                        try
    #endif
                        {
                            m_IsCameraRendering = true;
    #if UNITY_EDITOR
                            // Emit scene view UI
                            if (cameraData.isSceneViewCamera)
                                ScriptableRenderContext.EmitWorldGeometryForSceneView(camera);
    #endif
                            //剔除结果
                            CullResults.Cull(ref cullingParameters, context, ref m_CullResults);
                            List<VisibleLight> visibleLights = m_CullResults.visibleLights;
    
                            RenderingData renderingData;
                            InitializeRenderingData(ref cameraData, visibleLights,
                                m_Renderer.maxSupportedLocalLightsPerPass, m_Renderer.maxSupportedVertexLights,
                                out renderingData);
                            m_Renderer.Setup(ref context, ref m_CullResults, ref renderingData);
                            m_Renderer.Execute(ref context, ref m_CullResults, ref renderingData);
                        }
    #if UNITY_EDITOR
                        catch (Exception)
                        {
                            CommandBufferPool.Release(cmd);
                            throw;
                        }
                        finally
    #endif
                        {
                            m_IsCameraRendering = false;
                        }
                    }
                    //这里执行的ProfilingSample.Dispose()中设置的m_Cmd.EndSample(m_Name);命令
                    context.ExecuteCommandBuffer(cmd);
                    CommandBufferPool.Release(cmd);
                    context.Submit();
                }
            }
    
            public static void RenderPostProcess(CommandBuffer cmd, PostProcessRenderContext context, ref CameraData cameraData, RenderTextureFormat colorFormat, RenderTargetIdentifier source, RenderTargetIdentifier dest, bool opaqueOnly)
            {
                context.Reset();
                context.camera = cameraData.camera;
                context.source = source;
                context.sourceFormat = colorFormat;
                context.destination = dest;
                context.command = cmd;
                context.flip = cameraData.camera.targetTexture == null;
    
                if (opaqueOnly)
                    cameraData.postProcessLayer.RenderOpaqueOnly(context);
                else
                    cameraData.postProcessLayer.Render(context);
            }
            /// <summary>
            /// 设置支持的渲染特征,active是静态的
            /// </summary>
            void SetSupportedRenderingFeatures()
            {
    #if UNITY_EDITOR
                SupportedRenderingFeatures.active = new SupportedRenderingFeatures()
                {
                    //反射探针
                    reflectionProbeSupportFlags = SupportedRenderingFeatures.ReflectionProbeSupportFlags.None,
                    //默认的光照贴图的混合烘焙模式
                    defaultMixedLightingMode = SupportedRenderingFeatures.LightmapMixedBakeMode.Subtractive,
                    //支持的光照贴图的混合烘焙模式
                    supportedMixedLightingModes = SupportedRenderingFeatures.LightmapMixedBakeMode.Subtractive,
                    //支持的光照贴图烘焙模式
                    supportedLightmapBakeTypes = LightmapBakeType.Baked | LightmapBakeType.Mixed,
                    //支持的光照贴图类型
                    supportedLightmapsModes = LightmapsMode.CombinedDirectional | LightmapsMode.NonDirectional,
                    //支持光照探针代理
                    rendererSupportsLightProbeProxyVolumes = false,
                    //支持运动矢量
                    rendererSupportsMotionVectors = false,
                    //接受阴影
                    rendererSupportsReceiveShadows = true,
                    //反射探针
                    rendererSupportsReflectionProbes = true
                };
                SceneViewDrawMode.SetupDrawMode();
    #endif
            }
            /// <summary>
            /// 初始化摄像机数据
            /// </summary>
            /// <param name="camera"></param>
            /// <param name="cameraData"></param>
            void InitializeCameraData(Camera camera, out CameraData cameraData)
            {
                //接近1的pipelineAsset.renderScale(或者XRSettings.eyeTextureResolutionScale),置为1
                const float kRenderScaleThreshold = 0.05f;
                cameraData.camera = camera;
    
                bool msaaEnabled = camera.allowMSAA && pipelineAsset.msaaSampleCount > 1;
                if (msaaEnabled)
                    cameraData.msaaSamples = (camera.targetTexture != null) ? camera.targetTexture.antiAliasing : pipelineAsset.msaaSampleCount;
                else
                    cameraData.msaaSamples = 1;
                //场景相机
                cameraData.isSceneViewCamera = camera.cameraType == CameraType.SceneView;
                //存在RT且不是场景相机
                cameraData.isOffscreenRender = camera.targetTexture != null && !cameraData.isSceneViewCamera;
                cameraData.isStereoEnabled = IsStereoEnabled(camera);
                cameraData.isHdrEnabled = camera.allowHDR && pipelineAsset.supportsHDR;
    
                cameraData.postProcessLayer = camera.GetComponent<PostProcessLayer>();
                cameraData.postProcessEnabled = cameraData.postProcessLayer != null && cameraData.postProcessLayer.isActiveAndEnabled;
    
                // PostProcess for VR is not working atm. Disable it for now.
                cameraData.postProcessEnabled &= !cameraData.isStereoEnabled;
    
                Rect cameraRect = camera.rect;
                cameraData.isDefaultViewport = (!(Math.Abs(cameraRect.x) > 0.0f || Math.Abs(cameraRect.y) > 0.0f ||
                                                  Math.Abs(cameraRect.width) < 1.0f || Math.Abs(cameraRect.height) < 1.0f));
    
                // Discard variations lesser than kRenderScaleThreshold.
                // Scale is only enabled for gameview.
                // In XR mode, grab renderScale from XRSettings instead of SRP asset for now.
                // This is just a temporary change pending full integration of XR with SRP
    
                if (camera.cameraType == CameraType.Game)
                {
    #if !UNITY_SWITCH
                    if (cameraData.isStereoEnabled)
                    {
                        cameraData.renderScale = XRSettings.eyeTextureResolutionScale;
                    } else
    #endif
                    {
                        cameraData.renderScale = pipelineAsset.renderScale;
                    }
                }
                else
                {
                    cameraData.renderScale = 1.0f;
                }
    
                cameraData.renderScale = (Mathf.Abs(1.0f - cameraData.renderScale) < kRenderScaleThreshold) ? 1.0f : cameraData.renderScale;
    
                cameraData.requiresDepthTexture = pipelineAsset.supportsCameraDepthTexture || cameraData.isSceneViewCamera;
                cameraData.requiresSoftParticles = pipelineAsset.supportsSoftParticles;
                cameraData.requiresOpaqueTexture = pipelineAsset.supportsCameraOpaqueTexture;
                cameraData.opaqueTextureDownsampling = pipelineAsset.opaqueDownsampling;
    
                bool anyShadowsEnabled = pipelineAsset.supportsDirectionalShadows || pipelineAsset.supportsLocalShadows;
                cameraData.maxShadowDistance = (anyShadowsEnabled) ? pipelineAsset.shadowDistance : 0.0f;
                //这是一个额外添加的脚本
                LightweightAdditionalCameraData additionalCameraData = camera.gameObject.GetComponent<LightweightAdditionalCameraData>();
                if (additionalCameraData != null)
                {
                    cameraData.maxShadowDistance = (additionalCameraData.renderShadows) ? cameraData.maxShadowDistance : 0.0f;
                    cameraData.requiresDepthTexture &= additionalCameraData.requiresDepthTexture;
                    cameraData.requiresOpaqueTexture &= additionalCameraData.requiresColorTexture;
                }
                else if (!cameraData.isSceneViewCamera && camera.cameraType != CameraType.Reflection && camera.cameraType != CameraType.Preview)
                {
                    cameraData.requiresDepthTexture = false;
                    cameraData.requiresOpaqueTexture = false;
                }
    
                cameraData.requiresDepthTexture |= cameraData.postProcessEnabled;
            }
            /// <summary>
            /// 初始化渲染数据
            /// </summary>
            /// <param name="cameraData"></param>
            /// <param name="visibleLights"></param>
            /// <param name="maxSupportedLocalLightsPerPass"></param>
            /// <param name="maxSupportedVertexLights"></param>
            /// <param name="renderingData"></param>
            void InitializeRenderingData(ref CameraData cameraData, List<VisibleLight> visibleLights, int maxSupportedLocalLightsPerPass, int maxSupportedVertexLights, out RenderingData renderingData)
            {
                //用于生成阴影的非平行光源
                m_LocalLightIndices.Clear();
                //有阴影投射平行光源
                bool hasDirectionalShadowCastingLight = false;
                //有阴影投射非平行光源
                bool hasLocalShadowCastingLight = false;
                //初始化阴影的相关变量
                if (cameraData.maxShadowDistance > 0.0f)
                {
                    for (int i = 0; i < visibleLights.Count; ++i)
                    {
                        Light light = visibleLights[i].light;
                        bool castShadows = light != null && light.shadows != LightShadows.None;
                        if (visibleLights[i].lightType == LightType.Directional)
                        {
                            hasDirectionalShadowCastingLight |= castShadows;
                        }
                        else
                        {
                            hasLocalShadowCastingLight |= castShadows;
                            m_LocalLightIndices.Add(i);
                        }
                    }
                }
    
                renderingData.cameraData = cameraData;
                InitializeLightData(visibleLights, maxSupportedLocalLightsPerPass, maxSupportedVertexLights, out renderingData.lightData);
                InitializeShadowData(hasDirectionalShadowCastingLight, hasLocalShadowCastingLight, out renderingData.shadowData);
                renderingData.supportsDynamicBatching = pipelineAsset.supportsDynamicBatching;
            }
            /// <summary>
            /// 初始化阴影数据
            /// </summary>
            /// <param name="hasDirectionalShadowCastingLight"></param>
            /// <param name="hasLocalShadowCastingLight"></param>
            /// <param name="shadowData"></param>
            void InitializeShadowData(bool hasDirectionalShadowCastingLight, bool hasLocalShadowCastingLight, out ShadowData shadowData)
            {
                // Until we can have keyword stripping forcing single cascade hard shadows on gles2
                bool supportsScreenSpaceShadows = SystemInfo.graphicsDeviceType != GraphicsDeviceType.OpenGLES2;
                //渲染平行光阴影
                shadowData.renderDirectionalShadows = pipelineAsset.supportsDirectionalShadows && hasDirectionalShadowCastingLight;
    
                // we resolve shadows in screenspace when cascades are enabled to save ALU as computing cascade index + shadowCoord on fragment is expensive
                shadowData.requiresScreenSpaceShadowResolve = shadowData.renderDirectionalShadows && supportsScreenSpaceShadows && pipelineAsset.cascadeCount > 1;
                //阴影级联
                shadowData.directionalLightCascadeCount = (shadowData.requiresScreenSpaceShadowResolve) ? pipelineAsset.cascadeCount : 1;
                shadowData.directionalShadowAtlasWidth = pipelineAsset.directionalShadowAtlasResolution;
                shadowData.directionalShadowAtlasHeight = pipelineAsset.directionalShadowAtlasResolution;
                //阴影级联统一为Vector3表达
                switch (shadowData.directionalLightCascadeCount)
                {
                    case 1:
                        shadowData.directionalLightCascades = new Vector3(1.0f, 0.0f, 0.0f);
                        break;
    
                    case 2:
                        shadowData.directionalLightCascades = new Vector3(pipelineAsset.cascade2Split, 1.0f, 0.0f);
                        break;
    
                    default:
                        shadowData.directionalLightCascades = pipelineAsset.cascade4Split;
                        break;
                }
                //渲染非平行光阴影
                shadowData.renderLocalShadows = pipelineAsset.supportsLocalShadows && hasLocalShadowCastingLight;
                shadowData.localShadowAtlasWidth = shadowData.localShadowAtlasHeight = pipelineAsset.localShadowAtlasResolution;
                shadowData.supportsSoftShadows = pipelineAsset.supportsSoftShadows;
                shadowData.bufferBitCount = 16;
    
                shadowData.renderedDirectionalShadowQuality = LightShadows.None;
                shadowData.renderedLocalShadowQuality = LightShadows.None;
            }
            /// <summary>
            /// 初始化光源数据
            /// </summary>
            /// <param name="visibleLights"></param>
            /// <param name="maxSupportedLocalLightsPerPass"></param>
            /// <param name="maxSupportedVertexLights"></param>
            /// <param name="lightData"></param>
            void InitializeLightData(List<VisibleLight> visibleLights, int maxSupportedLocalLightsPerPass, int maxSupportedVertexLights, out LightData lightData)
            {
                //控制最大可见光源数量<=pipelineAsset.maxPixelLights
                int visibleLightsCount = Math.Min(visibleLights.Count, pipelineAsset.maxPixelLights);
                lightData.mainLightIndex = GetMainLight(visibleLights);
    
                // If we have a main light we don't shade it in the per-object light loop. We also remove it from the per-object cull list
                int mainLightPresent = (lightData.mainLightIndex >= 0) ? 1 : 0;
                //计算附加光数量,maxSupportedLocalLightsPerPasss是4或16
                int additionalPixelLightsCount = Math.Min(visibleLightsCount - mainLightPresent, maxSupportedLocalLightsPerPass);
                int vertexLightCount = (pipelineAsset.supportsVertexLight) ? Math.Min(visibleLights.Count, maxSupportedLocalLightsPerPass) - additionalPixelLightsCount : 0;
                //计算逐顶点光数量,maxSupportedVertexLights是4
                vertexLightCount = Math.Min(vertexLightCount, maxSupportedVertexLights);
    
                //附加光数量
                lightData.pixelAdditionalLightsCount = additionalPixelLightsCount;
                //不支持顶点光时vertexLightCount是0,最后是additionalPixelLightsCount;
                //支持时最后是 Math.Min(visibleLights.Count, maxSupportedLocalLightsPerPass)
                lightData.totalAdditionalLightsCount = additionalPixelLightsCount + vertexLightCount;
                lightData.visibleLights = visibleLights;
                lightData.visibleLocalLightIndices = m_LocalLightIndices;
            }
            /// <summary>
            /// 查找第一个Direction光源,返回数组中编号,若无返回-1
            /// </summary>
            /// <param name="visibleLights"></param>
            /// <returns></returns>
            // Main Light is always a directional light
            int GetMainLight(List<VisibleLight> visibleLights)
            {
                int totalVisibleLights = visibleLights.Count;
    
                if (totalVisibleLights == 0 || pipelineAsset.maxPixelLights == 0)
                    return -1;
    
                for (int i = 0; i < totalVisibleLights; ++i)
                {
                    VisibleLight currLight = visibleLights[i];
    
                    // Particle system lights have the light property as null. We sort lights so all particles lights
                    // come last. Therefore, if first light is particle light then all lights are particle lights.
                    // In this case we either have no main light or already found it.
                    if (currLight.light == null)
                        break;
    
                    // In case no shadow light is present we will return the brightest directional light
                    if (currLight.lightType == LightType.Directional)
                        return i;
                }
    
                return -1;
            }
            /// <summary>
            /// 设置每帧的Shader常量_GlossyEnvironmentColor、_SubtractiveShadowColor
            /// </summary>
            void SetupPerFrameShaderConstants()
            {
                // When glossy reflections are OFF in the shader we set a constant color to use as indirect specular
                SphericalHarmonicsL2 ambientSH = RenderSettings.ambientProbe;
                Color linearGlossyEnvColor = new Color(ambientSH[0, 0], ambientSH[1, 0], ambientSH[2, 0]) * RenderSettings.reflectionIntensity;
                Color glossyEnvColor = CoreUtils.ConvertLinearToActiveColorSpace(linearGlossyEnvColor);
                Shader.SetGlobalVector(PerFrameBuffer._GlossyEnvironmentColor, glossyEnvColor);
    
                // Used when subtractive mode is selected
                Shader.SetGlobalVector(PerFrameBuffer._SubtractiveShadowColor, CoreUtils.ConvertSRGBToActiveColorSpace(RenderSettings.subtractiveShadowColor));
            }
            /// <summary>
            /// 设置每个相机的Shader常量_ScaledScreenParams
            /// </summary>
            /// <param name="cameraData"></param>
            void SetupPerCameraShaderConstants(CameraData cameraData)
            {
                float cameraWidth = (float)cameraData.camera.pixelWidth * cameraData.renderScale;
                float cameraHeight = (float)cameraData.camera.pixelWidth * cameraData.renderScale;
                Shader.SetGlobalVector(PerCameraBuffer._ScaledScreenParams, new Vector4(cameraWidth, cameraHeight, 1.0f + 1.0f / cameraWidth, 1.0f + 1.0f / cameraHeight));
            }
    
            bool IsStereoEnabled(Camera camera)
            {
    #if !UNITY_SWITCH
                bool isSceneViewCamera = camera.cameraType == CameraType.SceneView;
                return XRSettings.isDeviceActive && !isSceneViewCamera && (camera.stereoTargetEye == StereoTargetEyeMask.Both);
    #else
                return false;
    #endif
            }
        }
    }
    

     

     

     

    参考资料

     

     

     

    [1] Unite 2017 | Unity可编程渲染管线剖析.

    [2] RenderPipeLine.

    [3] 详解可编程脚本渲染管线SRP.

    [4] 轻量级渲染管线:优化实时性.

     

     

    展开全文
  • 这是一些列来自lighthouse3d的GLSL教程,非常适合入门。我将边学习边翻译该教程的内容,同时记录在这里,方便以后查询。 流水线概述 ...下图描述了一个简化的图形处理流水线,虽然简略但仍然可以展示着色器编程...
  • Unity的渲染管线

    千次阅读 2018-09-26 11:01:51
    Unity的渲染管线 英文原文:https://docs.unity3d.com/Manual/SL-RenderPipeline.html 着色器定义了对象的视觉效果(通过材质属性设置)以及对象如何与光线产生反应。因为光照计算必须内置到着色器中,而且光源与...
  • 固定渲染管线Shader

    2017-12-03 16:38:17
    固定渲染管线是标准的几何&光照(T&L)管线,功能是固定的,它控制着世界、视、投影变换及固定光照控制和纹理混合T&L管线可以被渲染状态控制,矩阵,光照及采制参数固定渲染管线一般用于Shader在老显卡...
  • 计算机图形渲染管线

    千次阅读 2009-11-17 05:21:00
    最近在看3D引擎设计,看到了一些关于各种图形硬件接口还有软渲染的体系,忽然发觉自己当初在学OpenGL之前为什么没有学习图形的渲染管线,导致对opengl、d3d这些东西掌握起来要一段时间。因为渲染管线是因,opengl…...
  • 渲染管线入门

    万次阅读 2018-04-01 11:58:00
    渲染管线中一些技术名词大部分是英文直译,光看中文很容易被误导。所以我们必须在了解一个技术的具体作用之后,才能了解它到底是什么。这篇博客我主要翻译自Minh Tri Do Dinh的GPUs - Graphics Processing Units一文...
  • 渲染管线流程

    千次阅读 2016-09-21 10:49:49
    关于渲染管线将什么呢?无非就是在OpenGL的管道当中各个部分的功能以及如何在管道当中形成了我们想要的最终的一幅图.(像素).而管线当中的操作可分为以下几个部分:  阶段1. 指定几何对象.  如:点 线 三角形.等...
  • WebGL 渲染管线

    千次阅读 2018-10-21 22:29:15
    早期的渲染管线是不可编程的,叫做固定渲染管线,现代的GPU所包含的渲染管线为可编程渲染管线。本节所述皆基于可渲染管线 两种渲染管线的区别简单可以理解为前者工作的细节流程已经固定,只需要调整一些参数,后者...
  • Unity 渲染管线

    2020-02-12 10:17:43
    渲染管线1.1 选择一种渲染管线2. 默认管线 Built-in Render Pipeline2.1 渲染通路 Rendering paths2.1.0.1 前向渲染 Forward Rendering2.1.0.2 延迟着色 Deferred Shading2.1.0.3 老版本的延迟着色 Legacy Deferred...
  • 学习OpenGL最重要就是学会这学会套渲染管线的流程了网上各种学习博客文章数不胜数,但是总感觉对于初学者来说很难将其串起来,对整个渲染的流程没有整体的了解。以下是本人根据个人总结绘制的2.0管线...
  • GPU渲染管线

    千次阅读 2016-02-01 00:17:27
    所谓GPU的渲染管线,听起来好像很高深的样子,其实我们可以把它理解为一个流程,就是我们告诉GPU一堆数据,最后得出来一副二维图像,而这些数据就包括了”视点、三维物体、光源、照明模型、纹理”等元素。...
  • OpenGL渲染管线

    万次阅读 2013-01-01 20:54:39
    OpenGL渲染管线 绝大数OpenGL实现都有相似的操作顺序,一系列相关的处理阶段称为OpenGL渲染管线。图1-2显示了这些顺序,虽然并没有严格规定OpenGL必须采用这样的实现,但它提供了一个可靠的指南,可以预测OpenGL将...
  • 固定渲染管线

    千次阅读 2017-04-11 14:36:08
    浅谈 GPU图形固定渲染管线 阅读目录 1. 应用程序阶段(CPU) 1.1 视锥裁剪 1.2 场景图 1.3 四叉树与八叉树 2. 几何阶段(GPU)  2.1 坐标系统局部坐标系世界坐标系观察坐标系视口坐标系(屏幕坐标系...
  • WebGL渲染管线

    2018-11-15 10:47:09
    GPU(图形处理器单元),是显卡的显示核心,之前的渲染管线不可执行着色器程序,称为固定渲染管线;现在能够执行顶点着色器和片元着色器,称之为可编程渲染管线渲染管线就是一条渲染流水线,总是由下一个功能...
  • 渲染管线概述

    2018-04-09 17:13:44
    什么是渲染管线渲染管线也称为渲染流水线,是显示芯片内部处理图形信号相互独立的的并行处理单元。一个流水线是一序列可以并行和按照固定顺序进行的阶段。每个阶段都从它的前一阶段接收输入,然后把输出发给...
  • 渲染管线浅析

    2016-10-30 20:28:41
    这篇文章是描述GPU渲染管线的大致工作流程。 渲染管线 ,也称渲染流水线,是显示芯片内部处理图形信号相互独立的并行处理单元。渲染管线就是要把一系列的顶点数据,纹理等信息,最终转换成一张人眼可以看到的图像...
  • 图形渲染管线

    千次阅读 2015-07-09 12:07:49
    概述图形渲染管线(Graphics Pipeline),简单的说就是把摄像机看到的东西,绘制成一幅2D图像的过程。可以分为3个阶段: 1. 应用程序阶段 2. 几何阶段 3. 光栅化阶段 [图1-1 可编程渲染管线]应用程序阶段这个...

空空如也

1 2 3 4 5 ... 20
收藏数 150,552
精华内容 60,220
关键字:

渲染管线