04.开始绘制
现在我们已经初始化了direct3d,我们可以开始在计算机上显示我们疯狂的想法了!我们会绘制一个简单的三角形,因为我们将要绘制的所有3D对象和场景都是由它们组成的。
在这里,我们将画一个简单的、实心的蓝色三角形。我们将讨论渲染管道,并了解着色器的工作原理。
本节的源代码下载地址:
- https://www.braynzarsoft.net/file/4
- /uploadimg/braynzarsoft-d3d11/DX11_Lesson_04_Begin_Drawing!_zip.zip
在这里我们将学习绘制一个简单的蓝色三角形!在我们真正开始之前,我们需要介绍direct3d11的实际工作原理。
可编程图形渲染管道
Programmable Graphics Rendering Pipeline,本节的内容也可以参考:https://www.vaczh.com/article/detail-32.html
如果你有过Direct3D 10的经验,并且了解管道,你几乎可以跳过这些管道部分,因为Direct3D基本上是Direct3D10的扩展,它使用相同的管道,但有几个额外的阶段。
Direct3D11为可编程图形管线增加了3个新阶段。不仅如此,它还支持另一个单独但松散连接的管道,称为计算着色器管道。图形渲染管道(也称为绘制管道)中的三个新阶段是Hull、Tesselator和Domain着色器。它们与曲面tesselation有关,曲面基本上为对象添加了更多的细节,更多的细节。例如,它所做的是从模型中提取一个简单的三角形,它可能会添加更多的顶点来创建更多的三角形,然后重新定位顶点以使三角形更加详细。它可以采用一个简单的低pollygon模型,并在屏幕上显示时将其变成一个非常详细的高polly模型。它能非常快速有效地完成所有这些工作。不过,这是一个高级主题,我们将不会在本课中学习如何实施。
计算着色器(compute shader)(也称为Dispatch Pipeline)用于通过使用GPU作为一种并行处理器来扩展CPU的处理能力,从而进行极快的计算。这与图形无关。例如,可以使用计算着色器管道在GPU上执行性能非常昂贵的操作,例如精确的碰撞检测。本课将不讨论计算着色器。
渲染管道是direct3d用于根据虚拟相机所看到的内容创建2d图像的一组步骤。它由Direct3D 10中使用的7个阶段以及Direct3D 11附带的3个新阶段组成,如下所示:
- Input Assembler (IA) Stage
- Vertex Shader (VS) Stage
- Hull Shader (HS) Stage
- Tesselator Shader (TS) Stage
- Domain Shader (DS) Stage
- Geometry Shader (GS) Stage
- Stream Output (SO) Stage
- Rasterizer (RS) Stage
- Pixel Shader (PS) Stage
- Output Merger (OM) Stage
另一件事是,我们现在必须编译每个着色器。这样可以确保着色器没有错误。此外,我们可以随时在代码中设置各个着色器,而不是将效果文件中的技术设置为活动技术(着色器序列)。我认为这是一种更动态的方法,因为它让我们可以更自由地更改活动着色器,同时保留其他着色器作为活动着色器。例如,我们可以将像素着色器从使用照明计算来确定最终像素颜色更改为不使用照明方程的像素着色器,同时仍然保持相同的顶点着色器处于活动状态。
圆形阶段是“可编程”阶段,实际上是我们自己创造的。方形阶顶是不可编程的,但我们可以使用direct3d11设备上下文更改其设置。
Input Assembler (IA) Stage
IA是一个固定的功能阶段,这意味着我们不需要编程来实现它。IA读取几何数据、顶点和索引。然后,它使用数据创建几何图元,如三角形、正方形、直线和点,这些图元将被输入其他阶段并由其他阶段使用。索引定义了顶点应该如何将基本体放在一起。我们将在后面的课程中讨论索引。
在我们向IA发送任何东西之前,我们需要先做几件事,例如创建一个缓冲区并设置Primitive Topology、Input Layout和活动缓冲区。
首先,我们创建一个缓冲区。IA使用的两个缓冲区是顶点缓冲区和索引缓冲区。在本课中,我们还不关注索引缓冲区。要创建缓冲区,我们将填写D3D11_buffer_DESC结构。
在创建一个或多个缓冲区之后,我们需要创建输入布局对象。它所做的是告诉direct3d我们的顶点结构由什么组成,以及如何处理顶点结构中的每个组件。我们使用D3D11_INPUT_ELEMENT_DESC元素的数组将信息提供给direct3d。D3D11_INPUT_ELEMENT_DESC数组中的每个元素描述顶点结构中的一个元素。如果“顶点”结构有一个位置元素和一个颜色元素,则D3D10_INPUT_element_DESC数组将有一个元素用于位置,一个元素用作颜色。以下是一个示例:
//The vertex Structure
struct Vertex
{
D3DXVECTOR3 pos;
D3DXCOLOR color;
};
//The input-layout description
D3D11_INPUT_ELEMENT_DESC layout[] =
{
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0}
};
本节中,我们的顶点结构如下:
struct Vertex //Overloaded Vertex Structure
{
Vertex(){}
Vertex(float x, float y, float z): pos(x,y,z){}
XMFLOAT3 pos;
};
所以我们的输入布局描述如下:
D3D11_INPUT_ELEMENT_DESC layout[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
};
在用D3D11_input_ELEMENT_DESC结构描述了输入布局后,我们需要用以下函数创建它:
ID3D11Device::CreateInputLayout()
我们还需要创建一个顶点缓冲区来保存对象的顶点。要创建顶点缓冲区,首先我们使用D3D11_BUFFER_DESC 结构描述缓冲区,然后用实际顶点数据填充D3D11_SUBRESOURCE_DATA结构。要创建顶点缓冲区,我们可以调用:
ID3D11Device::CreateBuffer()
下一步是将布局描述和顶点缓冲区绑定到IA。我们可以通过调用以下函数将布局和顶点缓冲区时绑定到IA:
ID3D11DeviceContext::IASetVertexBuffers()
ID3D11DeviceContext::IASetInputLayout()
现在,我们需要设置基元拓扑,以便IA知道如何使用顶点并生成三角形、直线等基元。我们调用函数:
ID3D11DeviceContext::IASetPrimitiveTopology()
我将在本课后面介绍不同的类型。
在我们的管道准备好后,我们调用一个draw方法将原形发送到IA。我们在本课程中调用的方法是:
ID3D11DeviceContext::Draw()
Vertex Shader (VS) Stage
VS是第一个可编程着色器,这意味着我们必须自己编程。VS阶段是在IA中组装基本体后,所有顶点都要经过的阶段。绘制的每个顶点都将经过VS。使用VS,您可以进行变换、缩放、照明、纹理的位移映射等操作。即使程序中的顶点不需要修改,也必须始终实现“顶点着色器”才能使管道工作。管道中的着色器是用HLSL语言编写的。该语言与C++语法相似,因此学习起来并不困难。我将在我们更改的每节课中解释效果文件,稍后我们将有一节专门用于HLSL的课。在本课程中,我们的顶点着色器不执行任何操作,因此我们只返回每个顶点的位置而不进行修改。它看起来如下:
float4 VS(float4 inPos : POSITION) : SV_POSITION
{
return inPos;
}
顶点着色器将单个顶点作为输入,并返回单个输入。请注意VS参数中Pos后面的POSITION。当我们创建顶点(输入)布局时,我们为顶点的位置值指定POSITION,因此它们将被发送到VS中的此参数。如果需要,您可以从POSITION更改名称。
Hull Shader (HS) Stage
HS是direct3d11图形渲染管道中添加的三个新可选阶段中的第一个。这三个新阶段,Hull Shader阶段、Tessellator阶段和Domain Shader阶段,共同实现了所谓的曲面。曲面所做的是,取一个基本对象,如三角形或直线,并将其划分为许多较小的部分,以增加模型的细节,而且速度极快。它在GPU上创建所有这些新的基元,然后再将它们放到屏幕上,并且它们不会保存到内存中,因此这节省了在CPU和内存上创建它们的大量时间。您可以采用一个简单的低polly模型,并使用曲面将其转化为一个非常详细的polly。
那么,回到HS着色器。这是另一个可编程阶段。我不打算详细介绍,但这个阶段所做的是计算如何以及在哪里向基元添加新顶点,以使其更加详细。然后,它将这些数据发送到曲面细分阶段和域着色器阶段。
Tessellator (TS) Stage
曲面细分器阶段是细分过程中的第二个阶段。这是一个固定功能阶段。这个阶段所做的是从Hull Shader 获取输入,并实际进行基本体的分割。然后,它将数据传递给域着色器。
Domain Shader (DS) Stage
这是曲面细分过程中三个阶段中的第三个阶段。这是一个可编程功能阶段。这个阶段所做的是从Hull Shader阶段获取新顶点的位置,并变换从tessalator阶段接收的顶点以创建更多的细节,因为仅仅在三角形或直线的中心添加更多的顶点不会以任何方式增加细节。然后,它将顶点传递到几何体着色器阶段。
Geometry Shader (GS) Stage
此着色器阶段是可选的。这也是另一个可编程功能阶段。它接受基本体作为输入,例如三角形有3个顶点,直线有2个顶点,点有1个顶点。它还可以将边缘相邻基元的数据作为输入,例如直线的额外2个顶点,三角形的额外3个顶点。GS的一个优点是它可以创建或销毁基元,而VS不能(它接收一个顶点,输出一个顶点)。我们可以用这个阶段把一个点变成四边形或三角形。我们能够将数据从GS传递到光栅化器阶段,和/或通过流输出传递到内存中的顶点缓冲区。我们将在后面的课程中了解有关此着色器阶段的更多信息。
Stream Output (SO) Stage
此阶段用于从管道获取顶点数据,特别是几何着色器阶段或顶点着色器阶段(如果没有GS)。从SO发送到内存的顶点数据被放入一个或多个顶点缓冲区。SO输出的顶点数据始终以列表的形式发送,如直线列表或三角形列表。不完整的基本体永远不会被发送出去,它们只是像在顶点和几何体中一样被无声地丢弃。不完全基本体是基本体,例如只有2个顶点的三角形或只有一个顶点的直线。
Rasterization Stage (RS) Stage
RS阶段获取发送给它的矢量信息(形状和基元),并通过在每个基元上按顶点插值将它们转换为像素。它还处理剪切,基本上是剪切屏幕视图之外的基本体。我们使用以下方法在RS中设置视口:
ID3D11DeviceContext::RSSetViewports()
Pixel Shader (PS) Stage
这个阶段进行计算并修改将在屏幕上看到的每个像素,例如在每个像素的基础上进行照明。这是另一个可编程功能,也是一个可选阶段。RS为基本体中的每个像素调用一次像素着色器。就像我们之前所说的,基本体中每个顶点的值和属性都是通过RS中的整个基本体进行插值的。基本上,这就像顶点着色器一样,其中顶点着色器具有1:1映射(它接受一个顶点并返回一个顶点),像素着色器也具有1:1映射。
像素着色器的工作是计算每个像素片段的最终颜色。像素片段是将被绘制到屏幕上的每个潜在像素。例如,在实心圆后面有一个实心正方形。正方形中的像素是像素片段,而圆形中的像素则是像素片段。每个都有机会被写入屏幕,但一旦进入输出合并阶段,决定要绘制到屏幕的最终像素,它就会看到圆圈的深度值小于正方形的深度值,因此只会绘制圆圈中的像素。PS输出4D颜色值。
在本课中,我们使用像素着色器通过返回与蓝色相同的4D浮点值来使三角形变蓝。没有进行任何计算,只是简单地返回绿色,因此通过着色器运行的每个像素都将是蓝色。
float4 PS() : SV_TARGET
{
return float4(0.0f, 0.0f, 1.0f, 1.0f);
}
Output Merger (OM) Stage
管道的最后阶段是输出合并阶段。基本上,此阶段采用像素片段和深度/模板缓冲区,并确定哪些像素实际写入渲染目标。我们在上一课中通过调用来设置渲染目标:
ID3D11DeviceContext::OMSetRenderTargets()
我们可以稍后再谈。
在OM阶段之后,剩下要做的就是将我们的后台缓冲区呈现给屏幕。我们可以通过调用:
IDXGISwapChain::Present();
在这里,我们创建了几个新的接口对象。请记住,接口对象必须在完成操作后释放。第一个是缓冲区,它将保存我们的顶点数据。接下来的两个是我们的顶点和像素着色器。之后,我们有了顶点和像素缓冲区,它将保存有关顶点和像素着色器的信息。最后一个是我们的输入(顶点)布局。
ID3D11Buffer* triangleVertBuffer;
ID3D11VertexShader* VS;
ID3D11PixelShader* PS;
ID3D10Blob* VS_Buffer;
ID3D10Blob* PS_Buffer;
ID3D11InputLayout* vertLayout;