Unity Shader 入门精要 第2章 学习记录

本文深入介绍了Unity中的渲染流水线,分为应用阶段、集合阶段和光栅化阶段。详细讲解了CPU与GPU的交互,包括数据加载、渲染状态设置和Draw Call调用。在GPU流水线中,重点阐述了顶点着色器、几何阶段和逐片元操作等关键步骤,解释了OpenGL和DirectX中屏幕坐标系的差异,并探讨了Draw Call对性能的影响以及如何减少Draw Call。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

第二章 渲染流水线

        2.1.1  理想情况下,如果把非流水线系统分出流水线阶段,且每个阶段耗费时间相同的话,回事整个系统得到n倍的速度提升。

         2.1.2 什么是渲染流水线?

         渲染流程可分为3个阶段:应用阶段、集合阶段、光栅化阶段

         应用阶段:通常由CPU负责实现,其流水线化是由开发者决定的。

         几何阶段:用于处理所有和要绘制的几何相关的事情,例如,需要决定绘制的图元是什么,怎么绘制,在哪里绘制。通常在GPU上进行

         光栅化阶段:使用上个阶段传递的数据来产生屏幕上的像素,并渲染出最终的图像。主要在GPU上运行。光栅化主要是决定每个渲染图元中的哪些像素应该被绘制在屏幕上。需要对上阶段得到的逐顶点数据(如纹理坐标、顶点颜色等)进行插值,再进行逐像素处理。

2.2 CPU和GPU之间的通信

        渲染流水线的起点是CPU,即应用阶段。应用阶段大致可分为下面3个阶段:

                (1)把数据加载到显存中。

                (2)设置渲染状态。

                (3)调用Draw Call(在本章的最后我们还会继续讨论它)。

        2.2.1 把数据加载到显存中

        2.2.2 设置渲染状态

                这些状态定义了场景中的网络怎样被渲染。

         2.2.3 调用Draw Call

2.3  GPU流水线

        当GPU 从 CPU那里得到渲染命令后,就会进行一系列流水线操作,最终把图元渲染到屏幕上。
        2.3.1 概述

         从图中可以看出,GPU的渲染流水线接收顶点数据作为输入。这些顶点数据是由应用阶段加载到显存中,再由 Draw Call指定的。这些数据随后被传递给顶点着色器。

顶点着色器(Vertex Shader)是完全可编程的,它通常用于实现顶点的空间变换、顶点着色
等功能。

曲面细分着色器(Tessellation Shader)是一个可选的着色器,它用于细分图元。

几何着色器(Geometry Shader)同样是一个可选的着色器,它可以被用于执行逐图元(Per-Primitive)的着色操作,或者被用于产生更多的图元。

裁剪(Clipping),这一阶段的目的是将那些不在摄像机视野内的顶点裁剪掉,并剔除某些三角图元的面片。这个阶段是可配置的。例如,我们可以使用自定义的裁剪平面来配置裁剪区域,也可以通过指令控制裁剪三角图元的正面还是背面。

屏幕映射(Screen Mapping)。这一阶段是不可配置和编程的,它负责把每个图元的坐标转换到屏幕坐标系中。

三角形设置(Triangle Setup)和三角形遍历(Triangle 'Traversal)阶段也都是固定函数(Fixed-Function)的阶段。

片元着色器(Fragment Shader),则是完全可编程的,它用于实现逐片元(Per-Fragment)的着色操作。

逐片元操作(Per-FragmentOperations)阶段负责执行很多重要的操作,例如修改颜色、深度缓冲、进行混合等,它不是可编程的,但具有很高的可配置性。

        2.3.2 顶点着色器

 

        顶点着色器需要完成的工作主要有:坐标变换和逐顶点光照。当然,除了这两个主要任务外顶点着色器还可以输出后续阶段所需的数据。图2.7展示了在顶点着色器中对顶点位置进行坐变换并计算顶点颜色的过程。其输入主要来自CPU。

        坐标变换。顾名思义,就是对顶点的坐标(即位置)进行某种变换。顶点着色器可以在这一步中改变顶点的位置,这在顶点动画中是非常有用的。例如,我们可以通过改变顶点位置来模拟水面、布料等。但需要注意的是,无论我们在顶点着色器中怎样改变顶点的位置,一个最基本的顶点着色器必须完成的一个工作是,把顶点坐标从模型空间转换到齐次裁剪空间。想想看,我们在顶点着色器中是不是会看到类似下面的代码:

o.pos = mul(UNITY_MVP, v.position);

类似上面这句代码的功能,就是把顶点坐标转换到齐次裁剪坐标系下,接着通常再由硬件做透视除法后,最终得到归一化的设备坐标(Normalized Device Coordinates , NDC)。
 

        需要注意的是,图2.8给出的坐标范围是OpenGL同时也是Unity使用的NDC,它的z分量范围在[-1,1]之间,而在 DirectX中,NDC的z分量范围是[0,1]。顶点着色器可以有不同的输出方式。最常见的输出路径是经光栅化后交给片元着色器进行处理。而在现代的Shader Model中,它还可以把数据发送给曲面细分着色器或几何着色器,感兴趣的读者可以自行了解。

        2.3.3 裁剪

        

         2.3.4 屏幕映射

            屏幕映射的任务是把每个图元的x和y坐标转换到屏幕坐标系(Screen Coordinates)下。屏幕坐标系是一个二维坐标系,它和我们用于显示画面的分辨率有很大关系。

        屏幕映射得到的屏幕坐标决定了这个顶点对应屏幕上哪个像素以及距离这个像素有多远。有一个需要引起注意的地方是,屏幕坐标系在OpenGL和 DirectX之间的差异问题。OpenGI把屏幕的左下角当成最小的窗口坐标值,而DirectX则定义了屏幕的左上角为最小的窗口坐标值。

         2.3.5 三角形设置

        光栅化的第一个流水线阶段是三角形设置(Triangle Setup)。这个阶段会计算光栅化一个三角网格所需的信息。具体来说,上一个阶段输出的都是三角网格的顶点,即我们得到的是三角网格每条边的两个端点。但如果要得到整个三角网格对像素的覆盖情况,我们就必须计算每条边上的像素坐标。为了能够计算边界像素的坐标信息,我们就需要得到三角形边界的表示方式。这样一个计算三角网格表示数据的过程就叫做三角形设置。它的输出是为了给下一个阶段做准备。

        2.3.6 三角形遍历

         这一步的输出就是得到一个片元序列。需要注意的是,一个片元并不是真正意义上的像素,而是包含了很多状态的集合,这些状态用于计算每个像素的最终颜色。这些状态包括了(但不限于)它的屏幕坐标、深度信息,以及其他从几何阶段输出的顶点信息,例如法线、纹理坐标等。

        2.3.7 片元着色器

 

        2.3.8 逐片元操作

        这一阶段有几个主要任务。
        (1)决定每个片元的可见性。这涉及了很多测试工作,例如深度测试、模板测试等。
        (2)如果一个片元通过了所有的测试,就需要把这个片元的颜色值和已经存储在颜色缓冲区中的颜色进行合并,或者说是混合。

 

        如果开启了模板测试,GPU 会首先读取(使用读取掩码)模板缓冲区中该片元位置的模板值,然后将该值和读取(使用读取掩码)到的参考值(reference value)进行比较,这个比较函数可以是由开发者指定的,例如小于时舍弃该片元,或者大于等于时舍弃该片元。如果这个片元没有通过这个测试,该片元就会被舍弃。不管一个片元有没有通过模板测试,我们都可以根据模板测试和下面的深度测试结果来修改模板缓冲区,这个修改操作也是由开发者指定的。开发者可以设置不同结果下的修改操作,例如,在失败时模板缓冲区保持不变,通过时将模板缓冲区中对应位置的值加1等。模板测试通常用于限制渲染的区域。另外,模板测试还有一些更高级的用法,如渲染阴影、轮廓渲染等。
        如果一个片元幸运地通过了模板测试,那么它会进行下一个测试——深度测试(Depth Test)。开启了深度测试,GPU会把该片元的深度值和已经存在于深度缓冲区中的深度值进行比较。这个比较函数也是可由开发者设置的,例如小于时舍弃该片元,或者大于等于时舍弃该片元。通常这个比较函数是小于等于的关系,即如果这个片元的深度值大于等于当前深度缓冲区中的值,那么就会舍弃它。这是因为,我们总想只显示出离摄像机最近的物体,而那些被其他物体遮挡的就不需要出现在屏幕上。如果这个片元没有通过这个测试,该片元就会被舍弃。和模板测试有些不同的是,如果一个片元没有通过深度测试,它就没有权利更改深度缓冲区中的值。而如果它通过了测试,开发者还可以指定是否要用这个片元的深度值覆盖掉原有的深度值,这是通过开启/关闭深度写入来做到的。我们在后面的学习中会发现,透明效果和深度测试以及深度写入的关系非常密切。
如果一个幸运的片元通过了上面的所有测试,它就可以自豪地来到合并功能的面前。
为什么需要合并?我们要知道,这里所讨论的渲染过程是一个物体接着一个物体画到屏幕上的。而每个像素的颜色信息被存储在一个名为颜色缓冲的地方。因此,当我们执行这次渲染时,颜色缓冲中往往已经有了上次渲染之后的颜色结果,那么,我们是使用这次渲染得到的颜色完全覆盖掉之前的结果,还是进行其他处理?这就是合并需要解决的问题。
对于不透明物体,开发者可以关闭混合(Blend)操作。这样片元着色器计算得到的颜色值就会直接覆盖掉颜色缓冲区中的像素值。但对于半透明物体,我们就需要使用混合操作来让这个物体看起来是透明的。图2.16展示了一个简化版的混合操作的流程图。

 

        作为一个想充分提高性能的GPU,它会希望尽可能早地知道哪些片元是会被舍弃的,对于这些片元就不需要再使用片元着色器来计算它们的颜色。在Unity 给出的渲染流水线中,我们也可以发现它给出的深度测试是在片元着色器之前。这种将深度测试提前执行的技术通常也被称为 Early-Z技术。

        当模型的图元经过了上面层层计算和测试后,就会显示到我们的屏幕上。我们的屏幕显示的就是颜色缓冲区中的颜色值。但是,为了避免我们看到那些正在进行光栅化的图元,GPU会使用双重缓冲(Double Buffering)的策略。这意味着,对场景的渲染是在幕后发生的,即在后置缓冲(Back Buffer)中。一旦场景已经被渲染到了后置缓冲中,GPU就会交换后置缓冲区和前置缓冲(Front Buffer)中的内容,而前置缓冲区是之前显示在屏幕上的图像。由此,保证了我们看到的图像总是连续的。

2.4 一些困惑

        2.4.1 什么是 OpenGL/DirectX

         2.4.2 什么是HLSL/GLSL/CG

        GLSL:GLSL 的优点在于它的跨平台性,它可以在 Windows、Linux、Mac甚至移动平台等多种平台上工作,但这种跨平台性是由于OpenGL没有提供着色器编译器,而是由显卡驱动来完成着色器的编译工作。

        HLSL:而对于HLSL,是由微软控制着色器的编译,就算使用了不同的硬件,同一个着色器的编译结果也是一样的(前提是版本相同)。但也因此支持HLSL的平台相对比较有限,几乎完全是微软自已的产品,如 Windows、Xbox 360等。这是因为在其他平台上没有可以编译HLSL的编译器。

        CG:CG则是真正意义上的跨平台。它会根据平台的不同,编译成相应的中间语言。CG语言的跨平台性很大原因取决于与微软的合作,这也导致CG 语言的语法和 HLSL非常相像,CG语言可以无缝移植成HLSL代码。但缺点是可能无法完全发挥出OpenGL 的最新特性。

        2.4.3 什么是Draw Call

        CPU 和 GPU是如何实现并行工作的?
 

        为什么 Draw Call多了会影响帧率?
 

         如何减少 Draw Call?

 2.5 什么是shader

        GPU流水线上一些可高度编程的阶段,而由着色器编译出来的最终代码是会在GPU上运行的(对于固定管线的渲染来说,着色器有时等同于一些特定的渲染设置);
        有一些特定类型的着色器,如顶点着色器、片元着色器等;
        依靠着色器我们可以控制流水线中的渲染细节,例如用顶点着色器来进行顶点变换以及传递数据,用片元着色器来进行逐像素的渲染。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值