在上一个教程中,我们设置了一个顶点缓冲区并将一个三角形传递给GPU。 现在,我们将逐步完成图形管道并查看每个阶段的工作原理。 将解释着色器和效果系统的概念。
请注意,本教程与前一个源代码共享相同的源代码,但将强调不同的部分。
(SDK root)\Samples\C++\Direct3D11\Tutorials\Tutorial03
在上一个教程中,我们设置顶点缓冲区,然后将顶点布局与顶点着色器相关联。 现在,我们将解释着色器是什么以及它是如何工作的。 为了完全理解各个着色器,我们将退后一步,查看整个图形管道。
在教程2中,当我们调用VSSetShader()和PSSetShader()时,我们实际上将着色器绑定到管道中的一个阶段。 然后,当我们调用Draw时,我们开始处理传递到图形管道的顶点数据。 以下部分详细描述了Draw命令之后发生的情况。
在Direct3D 11中,着色器位于图形管道的不同阶段。它们是由GPU执行的短程序,它接收某些输入数据,处理该数据,然后将结果输出到管道的下一阶段。 Direct3D 11支持三种基本类型的着色器:顶点着色器,几何着色器和像素着色器。顶点着色器将顶点作为输入。对于通过顶点缓冲区传递给GPU的每个顶点,它运行一次。几何着色器将基元作为输入,并对传递给GPU的每个基元运行一次。基元是点,线或三角形。像素着色器将像素(或有时称为片段)作为输入,并且对于我们希望渲染的图元的每个像素运行一次。顶点,几何和像素着色器一起是动作的主要部分。使用Direct3D 11渲染时,GPU必须具有有效的顶点着色器和像素着色器。几何着色器是Direct3D 11中的高级功能,是可选的,因此我们不会在本教程中讨论几何着色器。在Direct3D 11中,还有用于细分的外壳和域着色器以及用于计算的计算着色器。有关这些的更多信息,请参阅其他示例。
顶点着色器是GPU在顶点上执行的短程序。 将顶点着色器视为C函数,将每个顶点作为输入,处理输入,然后输出修改后的顶点。 应用程序以顶点缓冲区的形式将顶点数据传递给GPU后,GPU遍历顶点缓冲区中的顶点,并为每个顶点执行一次活动顶点着色器,将顶点数据作为输入参数传递给顶点着色器。
虽然顶点着色器可用于执行许多任务,但顶点着色器最重要的工作是变换。 转换是将矢量从一个坐标系转换为另一个坐标系的过程。 例如,3D场景中的三角形可以使其顶点位于(0,0,0)(1,0,0)(0,1,0)的位置。 当在2D纹理缓冲区上绘制三角形时,GPU必须知道缓冲区上应该绘制顶点的点的2D坐标。 正是转型帮助我们实现了这一目标。 转换将在下一个教程中详细讨论。 在本教程中,我们将使用一个简单的顶点着色器,除了将输入数据作为输出传递之外什么都不做。
在Direct3D 11教程中,我们将使用高级着色语言(HLSL)编写着色器。 回想一下,我们的顶点数据有一个3D位置元素,顶点着色器根本不对输入进行处理。 生成的顶点着色器如下所示:
float4 VS( float4 Pos : POSITION ) : SV_POSITION
{
return Pos;
}
这个顶点着色器看起来很像C函数。 HLSL使用类似C语法的语言,使C / C ++程序员更容易学习。我们可以看到这个名为VS的顶点着色器采用float4类型的参数并返回一个float4值。在HLSL中,float4是一个4分量向量,其中每个分量都是一个浮点数。冒号定义参数的语义以及返回值。如上所述,HLSL中的语义描述了数据的性质。在上面的着色器中,我们选择POSITION作为Pos输入参数的语义,因为此参数将包含顶点位置。返回值的语义SV_POSITION是具有特殊含义的预定义语义。这种语义告诉图形管道,与语义相关联的数据定义了剪辑空间位置。 GPU需要此位置才能在屏幕上绘制像素。 (我们将在下一个教程中讨论剪辑空间。)在我们的着色器中,我们获取输入位置数据并将完全相同的数据输出回管道。
现代计算机显示器通常是光栅显示器,这意味着屏幕实际上是称为像素的小点的二维网格。 每个像素包含独立于其他像素的颜色。 当我们在屏幕上渲染三角形时,我们并不真正将三角形渲染为一个实体。 相反,我们点亮了三角形区域所覆盖的像素组。 图2显示了这一点。
图2.左:我们想要绘制的内容。 右:屏幕上实际显示的是什么。
将由三个顶点定义的三角形转换为由三角形覆盖的一组像素的过程称为光栅化。 GPU首先确定被渲染的三角形覆盖哪些像素。 然后它为每个像素调用活动像素着色器。 像素着色器的主要用途是计算每个像素应具有的颜色。 着色器对要着色的像素进行某些输入,计算像素的颜色,然后将该颜色输出回管道。 它所采用的输入来自活动几何着色器,或者,如果不存在几何着色器,例如本教程中的情况,则输入直接来自顶点着色器。
我们在上面创建的顶点着色器输出一个带有语义SV_POSITION的float4。 这将是我们的像素着色器的输入。 由于像素着色器输出颜色值,因此像素着色器的输出将为float4。 我们给输出语义SV_TARGET以表示输出到渲染目标格式。 像素着色器如下所示:
float4 PS( float4 Pos : SV_POSITION ) : SV_Target
{
return float4( 1.0f, 1.0f, 0.0f, 1.0f ); // 黄色, 同时透明度为1
}
在应用程序代码中,我们需要创建一个顶点着色器和一个像素着色器对象。 这些对象代表着色器,通过调用 D3DX11CompileFromFile()创建。 代码如下所示:
// 创建顶点着色器
if( FAILED( D3DX11CompileFromFile( "Tutorial03.fx", NULL, NULL, "VS", "vs_4_0", D3DCOMPILE_ENABLE_STRICTNESS, NULL, NULL, &pVSBlob, &pErrorBlob, NULL ) ) )
return FALSE;
// 创建像素着色器
if( FAILED( D3DX11CompileFromFile( "Tutorial03.fx", NULL, NULL, "PS", "ps_4_0", D3DCOMPILE_ENABLE_STRICTNESS, NULL, NULL, &pPSBlob, &pErrorBlob, NULL ) ) )
return FALSE;
在遍历图形管道之后,我们可以开始理解渲染我们在教程2开始时创建的三角形的过程。创建Direct3D应用程序需要两个不同的步骤。