这章着重解释上一章的工作原理。
在教程2中,当我们调用VSSetShader()和PSSetShader()时,我们实际上将着色器绑定到管道中的一个阶段。 然后,当我们调用Draw时,我们开始处理传递到图形管道的顶点数据。
着色器位于图形管道的不同阶段。它们是由GPU执行的短程序,它接收某些输入数据,处理该数据,然后将结果输出到管道的下一阶段。支持3种着色器:顶点着色器,几何着色器和像素着色器。对于通过顶点缓冲区传递给GPU的每个顶点,它运行一次。几何着色器将基元作为输入,并对传递给GPU的每个基元运行一次。基元是点,线或三角形。像素着色器将像素(或有时称为片段)作为输入,并且对于我们希望渲染的图元的每个像素运行一次。
首先是顶点着色器,它的主要功能是转换,在下一章在详细介绍,以下是个简单的顶点着色器,因为不需要转换所以直接返回。用的是HLSL语法
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需要此位置才能在屏幕上绘制像素。 (我们将在下一个教程中讨论剪辑空间。)在我们的着色器中,我们获取输入位置数据并将完全相同的数据输出回管道。
现代计算机显示器通常是光栅显示器,这意味着屏幕实际上是称为像素的小点的二维网格。 每个像素包含独立于其他像素的颜色。 当我们在屏幕上渲染三角形时,我们并不真正将三角形渲染为一个实体。 相反,我们点亮了三角形区域所覆盖的像素组。 下图显示了这点。
将由三个顶点定义的三角形转换为由三角形覆盖的一组像素的过程称为光栅化。 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 ); // Yellow, with Alpha = 1
}
在应用程序代码中,我们需要创建一个顶点着色器和一个像素着色器对象。 这些对象代表着色器,通过调用D3DX11CompileFromFile()创建。 代码如下所示:
// Create the vertex shader
if( FAILED( D3DX11CompileFromFile( "Tutorial03.fx", NULL, NULL, "VS", "vs_4_0", D3DCOMPILE_ENABLE_STRICTNESS, NULL, NULL, &pVSBlob, &pErrorBlob, NULL ) ) )
return FALSE;
// Create the pixel shader
if( FAILED( D3DX11CompileFromFile( "Tutorial03.fx", NULL, NULL, "PS", "ps_4_0", D3DCOMPILE_ENABLE_STRICTNESS, NULL, NULL, &pPSBlob, &pErrorBlob, NULL ) ) )
return FALSE;
在遍历图形管道之后,我们可以开始理解渲染我们在教程2开始时创建的三角形的过程。创建Direct3D应用程序需要两个不同的步骤。 第一个是在顶点数据中创建源数据,正如我们在教程2中所做的那样。第二个阶段是创建着色器,这些着色器将转换该数据以进行渲染,我们在本教程中展示了这些。