c++生成光栅条纹程序_程序媛转TA之理论篇四:渲染管线的几个基本阶段介绍

本文深入介绍了C++渲染管线的输入装配阶段,包括DrawInstanced和DrawIndexedInstanced接口,以及图元生成。接着讨论了顶点着色器,阐述了其工作原理和输出信息。光栅化阶段的裁剪、视口变换、深度偏移和插值等关键步骤也被提及,为理解整个渲染流程奠定了基础。
摘要由CSDN通过智能技术生成

44efa74c213b2428e0f607559441c2c0.png

继续补充前面的内容,让渲染流程更加清晰起来。

前面我们基本上看了下绘制一个图形最基本的流程,接下来我们需要对里面的细节进行了解。

首先看一下完整的渲染管线。

2fbf106eafec00b4d5da159dbc5b8a5c.png

可以看到里面有几个是我们前面几篇文章提到的内容,而有几个则是新名词。 整个流程很复杂,如果我们从一般的编程思维去处理,这里面会有太多的参数我们需要传递。所以渲染程序一般都用状态机的思维去处理。在执行任一个命令之前,我们需要维护一个状态集合。当执行下一个命令的时候,我们又要设置新的状态集合。这样就避免了在执行命令的时候去传递数十个参数。当然,我们也要尽可能减少状态设置命令,这样会有更好的性能。

这么复杂的渲染管线,我们不可能一下子全部讲完,我们先看下其中的第一个阶段,输入装配。这有利于我们对后面流程的了解。

1.输入装配

有两个绘制接口,一个是DrawInstanced,一个是DrawIndexedInstanced。两者的区别在于一个是没有提供索引缓冲,一个是提供了索引缓冲。我们先从简单的开始,DrawInstanced。它需要四个参数,第一个参数是每个instance需要的顶点个数。第二个是Instance的数量,后面两个参数和顶点缓冲有关,我们先忽略这两个参数。

DrawInstanced会产生Instance数量*顶点数量个顶点。并且为每个顶点附加两个成员,一个是SV_INSANCEID一个是SV_VERTEXID。

DrawIndexedInstanced有一点不同,除了每个Instance的顶点数需要计算,同时还需要支持所谓的条带。一般来说,我们一个个三角形面片是需要三个顶点组成。出于性能考虑,我们需要公用一些边,一般来说,我们是通过索引缓冲去实现,但支持条带的话,就可以直接支持边的复用。

后面我们再看条带的具体用法,现在我们先了解下图元。

图元一般分为,点,线和三角形。其中最复杂的是三角形。我们主要来讲一下三角形图元的生成。

在有了前面的顶点数量和Instance数量的信息后,我们就可以针对每一个instance,来生成一堆三角形的顶点。在列表模式下,每三个顶点构成一个三角形。

9b598e2afc41b45f91a2160c8a204c3b.png

这里要注意,顶点的顺序决定了三角形的朝向,逆时针就是朝外的,顺时针朝里。而如果是条带模式,那么就是这样:

6244b4c51d5e535140b4fa1f0add4b2e.png

可以看到,边1-2被两个三角形复用了。而为了让两个面均朝向外面,两个的顺序有点不同,第一个三角面是0-1-2,第二个三脚面是1-3-2。这些在生成图元的时候都是需要注意的。

对于后续阶段,我们只需要传递的是图元信息,而对于拓扑信息是无关紧要的,也就是说不论是不是条带,对于后面的阶段,都是一个个三角形。我们的产出只有两个,一个是表示顶点信息的数组,一个是表示图元信息的数组。

2.顶点着色器

顶点着色器是我们自己写的程序,可以将上个阶段传过来的每个顶点都去执行一遍我们的程序。产出的信息也是和顶点个数对应的结构体。结构体里面必须包含SV_POSITION的信息,这个将会用来在光栅化阶段生成对应的顶点的位置。当然我们也可以附加更多的其他信息。另外,图元信息在顶点着色器中并没有被使用,它将直接传递到光栅化阶段。

3.光栅化阶段

光栅化应该是我们最常见的渲染管线的名词了。它主要作用是把一些矢量信息转成像素信息。在D3D12中,我们需要了解这样几个结构体。

D3D12_RASTERIZER_DESC,D3D12_VIEWPORT和D3D12_RECT.

我们依次来看一下。前面我们说了,顶点着色器会产生SV_POSITION的位置信息。这是一个四分量的向量,xyzw。我们听到很多的说法,其中最主要的就是说这是一个齐次坐标系,而有关w的含义,可能更多的理解是当w是1时,代表这是一个位置,当w是0时,这时一个向量。我们先不深究这个四维坐标的含义。我们就照着我们对图形渲染的理解去看。

光栅化需要保证的是w>0, 而且-w<=x<=w, -w<=y<=w。 如果开启了深度clip,那么还要保证0<=z<=w. 我们把这个过程叫做裁剪。而有些地方会说是先进行透视除法,再裁剪,但一般来说,我们要保证w>0,所以先裁剪再进行透视除法可能更加合理。不过这部分的资料说法各异,加上这么多年的发展,可能已经不是我说的这种情况了。不过对于学习流水线过程,这并不是太大的问题。我们就当先进行裁剪。经过裁剪之后,我们就保证了x/w和y/w在[-1,1]之间,而如果开启了深度clip,z就在[0,1]之间。这里要注意,裁剪并不是我们想的那么简单,直接限定一个范围就好了。一条线段,被裁剪后需要计算出一个新的顶点,而三角形被裁剪的情况则更加复杂,可能多出一个三角形,也可能多出两个三角形,需要根据不同情况处理。这里我们就不深究裁剪的具体算法,有兴趣的可以自己去查资料看看。

裁剪完毕之后我们会得到一个归一化的坐标,然后开始进行视口变换。RSSetViewports可以传入一个VIEWPORT数组。一般来说,都使用第一个元素作为视口。视口变换的代码很简单,就是将一个归一化坐标转成具体的视口坐标。这里面一般还会加一些附加处理,例如y轴坐标反向,z轴坐标限定MinDepth和MaxDepth等。

接下来我们需要进行深度偏移。也就是shader里面常见的offset关键字。这主要是用来处理两个共面的物体会出现闪烁的情况。

通过对其中一个物体设置offset,可以防止闪烁。offset会有两个参数,一个和浮点数最小距离相乘。一个和深度在x和y方向的斜率的更大值相乘。一般我们只要学会使用这个功能就行。

接着我们要进行插值。

前面我们已经得到了一个经过顶点着色器处理后的数组,VSOutputArray,以及一个描述图元的数组,PrimitiveArray。

还有一个是经过视口变换后得到的数组,RSCoordArray.

现在,我们希望获得一个插值后的结构体,里面包含一个图元ID,一个数组表示这个像素插值后的位置。

对于点图元,是最简单的,直接进行拷贝,就可以获得最终位置。

对于直线图元,需要进行一些计算。

9b936e913981297f61dbc642fe5c4bf1.png

根据向量共线原理的推论,OP = xOA + yOB,其中 x + y =1.我们对于所有的被直线覆盖的像素点p,去求出x和y,然后用

x * A.Pos + y * B.Pos 就可以得出插值后的位置。

同理对于三角形,也采用类似的算法。

2e18ec4ed07844350d07da7dc0f39ca3.png

三角形还多了一步,在插值前需要根据剔除设置进行剔除。通过变量FrontCounterClockwise,可以确定是顺时针是正面还是逆时针是正面。然后根据剔除设置进行剔除。

接着是像素着色器。和顶点着色器一样,这是用户自定义的程序,会对每个像素执行。它的输入是前面的VSOutput和图元的ID,而RS_Coord并不会输入到像素着色器。但像素着色器可以修改z值。渲染管线最后会检查PSOutput中是否有SV_DEPTH的成员,有的话就会去覆盖原始的z值。

最后就是混合。

各种混合模式我们先不介绍。这里提一下混合阶段会检查PSOutput中是否会有SV_TARGETnum的字段,会根据这个字段把像素写到对应的(num+1)个渲染目标视图中。

接下来我们介绍以下HLSL,它是编写着色器的语言。编译生成的是字节码,这是因为不同的gpu上执行的本地代码差异很大,各个硬件厂家的用户模式下的驱动程序会在运行时将字节码解析成可以本地执行的代码。

D3D12中,引入了一种新的着色器类型,根签名对象。

edc11c53073dd598534223d9e2fbcbc8.png

根签名的作用在于描述了渲染管线需要的各种资源以什么样的方式传入以及在内存和显存中的布局。主要指定的是Gpu上的对应的寄存器。具体来讲,根签名描述了常量,常量缓冲区CBV,资源缓冲区视图SRV,无序访问缓冲视图UAV,采样器等的寄存器规划的一个结构体。而且常量,根描述符,静态采样器等可以在根签名中被赋值。

既然是着色器对象,根签名也要编译之后才能被gpu执行。

我们可以运行时动态编译根签名,也可以通过HLSL来生成根签名对象。我们通过HLSL来写一个根签名文件,然后用VS去编译它。这时候生成的依然是字节码,D3D12会在运行时将字节码解析成特定GPU上执行的代码。通过CreateRootSignature可以创建根签名。通过SetGraphicsRootSignature可以设置图形管线的根签名。

然后我们还需要创建图形管线的状态对象。它需要填充一个D3D12_GRAPHICS_PIPELINE_STATE_DESC来描述所要创建的图形管线状态对象。里面有大量的成员,我们就不一一介绍了,不少是前面提到过的,这里用来校验。

这样结合前面的命令列表就可以完成我们基本的渲染管线的介绍。

内容感觉有点多,希望大家能坚持看完,哈哈哈哈。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值