Microsoft DirectX® 9.0中的Microsoft® Direct3D®使用了程序化的模块来指定顶点变换、光照流水线、像素/纹理混合流水线的行为。
使用基于程序化的模块来指定硬件的行为有许多好处。
首先,程序化的模型使得用更为通用的语句来指定常用的操作成为可能。与可编程API不同,固定功能API必须随着所支持操作的增长,定义新的模式,标志,等等。此外,随着硬件能力的加强——更多的颜色,更多的纹理,更多的顶点数据流,等等——由输入数据导致操作符空间的增长也变得复杂。另一方面,可编程模型能够以更为直接的方式进行甚至简单的操作,例如把适当的颜色和纹理放到光照模块中合适的地方。开发人员无需搜索所有可能的模式,只要学习计算机体系结构并指定想要执行的算法即可。
例如,可编程流水线可以支持以下众所周知的特性。
其次,程序化的模型为开发新的操作提供了一个简单的机制。当前API不支持许多开发人员需要的操作。大多数情况下,这并不是由于硬件能力的限制造成的,相反是由于API的限制造成的。一般情况下,比起试图超越设计者的意图,强制扩充固定功能API以实现相同功能来说,用程序化的模型执行这些操作会更简单,速度也更快。
开发人员普遍希望实现的新特性包括以下这些:
第三,程序化的模型提供了可扩充性和可发展性。硬件能力正在高速地发展,程序化的表示可以适应API,因为它们很容易扩充。通过以下手段,可以很容易地不断地添加新的特性和能力。
在对复杂事物的描述方法中,代码具有最好的扩充性。此外,对于添加到可编程着色器中的新特性,Direct3D内部所需改动的代码量也非常小。
第四,程序化的模型更容易被掌握。比起硬件,软件开发人员更理解编程。一个真正能够满足软件开发人员需要的API应该把硬件能力映射到代码的形式。
第五,程序化的模型跟随了具有照片真实感的渲染领域所经历的足迹。在具有照片真实感的高端渲染领域,使用可编程着色器的传统已经有许多年了。一般来说,这个领域不受性能的影响,因此对渲染技术而言,可编程着色器代表了无妥协的最终目标。
最后,程序化模型允许直接映射到硬件。当前大多数三维硬件,至少在顶点处理阶段,实际应该是可编程的。API提供的可编程性使用应用程序的指令可以直接映射到这部分硬件。这使用开发人员可以根据需要管理硬件资源。要用有限数量的寄存器和可以运行的指令,去实现一个能启用所有特性并且特性之间不相互影响的固定功能流水线是很难的。如果开发人员打开了太多特性,而这些特性需要共享同一资源,那么这些特性可能都会以意想不到的方式停止运作。通过让应用程序开发人员与硬件直接对话,从而使此类限制对API变得透明,可编程API消除了这个问题,这也遵循了DirectX的传统。
把顶点着色器集成到几何流水线中
可编程顶点着色器在操作状态时会取代Microsoft® Direct3D®的几何流水线中的变换和光照模块。实际上,有关变换和光照的状态都被忽略。但是,如果禁用顶点着色器并重新使用固定功能处理,那么所有当前状态设置会起作用。
任何对high-order图元的tessellation操作必须在顶点着色器执行之前完成。对那些在着色器处理后执行表面tessellation的硬件实现来说,必须采用某种方式使之对应用程序不可见。因为在着色器之前一般来说没有提供语义信息,所以系统使用了一个特殊的token来确定输入流中的哪个成员表示基位置,所有其它成员都相对于该成员进行插值。Direct3D不支持无法插值的数据通道。
在输出时,顶点着色器必须产生齐次裁剪空间中的顶点位置。其它可以产生的数据包括纹理坐标,颜色,雾因子等等。
标准图形流水线会处理着色器输出的顶点,包括以下操作。
Microsoft DirectX® 9.0的顶点着色器和固定功能流水线的裁剪空间是相同的。更多细节,请参阅裁剪体。
可编程几何流水线是Direct3D应用程序编程接口(API)中的一种模式。当启用时,它会取代顶点流水线。当禁用时,API就切换回固定功能顶点处理。顶点着色器的执行不会影响Direct3D的内部状态,同样着色器也不能使用Direct3D的任何状态。
应该用IDirect3DDevice9::CreateVertexShader创建一个顶点着色器,并在进行绘制之前调用IDirect3DDevice9::SetVertexShader设置可编程着色器。
把像素着色器集成到图形流水线中
像素处理由像素着色器对每个像素单独执行。像素着色器可以单独工作,也可以和顶点着色器及数据流协同工作。开发人员不能凭空对像素着色器进行编程,它们要依赖于上游的数据成员。
下面是像素流水线中的操作顺序:
像素着色器的输入来自顶点着色器的输出。寄存器v0和v1包含了顶点颜色,它们来自顶点着色器的输出寄存器oD0和oD1。颜色层中的纹理由诸如tex t0之类的像素着色器指令引用,系统会根据顶点着色器的输出寄存器中对应的纹理坐标(如oT0)对纹理进行取样。像素着色器使用颜色和阿尔法混合指令以及纹理寻址指令对这些输入进行操控并计算出结果。像素着色器计算得到的结果是寄存器r0的内容或输出的像素颜色。着色器完成处理后会把处理结果送到雾处理阶段和渲染目标混合器做进一步的处理。顶点着色器的输出提供了像素着色器的输入。
ps_3_0着色器模型和ps_1_X/ps_2_0有些不同的概念。
可编程数据流模型
本节讲述可用于可编程数据流模型的着色器。
数据流的使用
Microsoft® DirectX® 8.0引入了数据流的概念,用来把数据绑定到着色器使用的输入寄存器。一个数据流是一个成员数据的数组,每个成员由一个或多个元素构成,这些元素代表单个实体,如位置、法向、颜色等等。数据流使图形芯片能并行地从多个顶点缓存执行直接内存访问(DMA)操作,同时也降低了多重纹理的开销。可以这样理解数据流:
IDirect3DDevice9::SetStreamSource方法把一个顶点缓存绑定到一个设备数据流,这样就在顶点数据和一个顶点数据流端口之间建立了联系,有多个数据流端口用来给图元处理函数输入数据。对数据流中的数据的真正引用只有在调用诸如IDirect3DDevice9::DrawPrimitive之类的绘制方法时才发生。
从输入顶点元素到可编程顶点着色器使用的顶点输入寄存器的映射是在着色器声明中定义的,但是输入顶点元素并没有专门的语义来描述它们的使用。对输入顶点元素的解释通过着色器指令进行编程。顶点着色器函数由一个指令数组定义,这些指令会应用于每个顶点。顶点输出寄存器用着色器函数中的指令显式地写入。
本节的讨论较少关注从元素到寄存器的语义映射,而更侧重于“为什么要使用数据流?”和“数据流可以解决什么问题?”这些问题。数据流的最大好处是消除了原来和多重纹理有关的顶点数据的开销。在引入数据流之前,为了处理单纹理和多重纹理的情况,用户要么复制两份顶点数据,每份顶点数据中都没有用不到的数据;要么在一份顶点数据中包含所有的数据元素,但其中一部分数据除了多重纹理的情况以外不会被用到。
这里是一个使用两份顶点数据的示例,一份用于单纹理,另一份用于多重纹理。
struct CUSTOMVERTEX_TEX1
{
FLOAT x, y, z; // 未经变换的顶点位置
DWORD diffColor; // 顶点的漫反射色
DWORD specColor; // 顶点的镜面反射色
float tu_1, tv_1; // 单纹理的纹理坐标
};
struct CUSTOMVERTEX_TEX2
{
FLOAT x, y, z; // 未经变换的顶点位置
DWORD diffColor; // 顶点的漫反射色
DWORD specColor; // 顶点的镜面反射色
float tu_2, tv_2; // 多重纹理的纹理坐标
};
另一种方法是在一个顶点元素中包含全部两组纹理坐标。
struct CUSTOMVERTEX_TEX2
{
FLOAT x, y, z; // 未经变换的顶点位置
DWORD diffColor; // 顶点的漫反射色
DWORD specColor; // 顶点的镜面反射色
float tu_1, tv_1; // 单纹理的纹理坐标
float tu_2, tv_2; // 多重纹理的纹理坐标
};
如果使用这份顶点数据,那么只要在内存中保存一份顶点和颜色数据,代价是在渲染过程中保存了全部两组纹理坐标,甚至在单纹理的情况下也是如此。
现在这其中的权衡已经很清楚了,数据流为这种左右为难的情况提供了一种极好的解决方案。这里提供了一套顶点定义,用来支持三个数据流:一个数据流包含位置和颜色,一个数据流包含第一组纹理坐标,另一个数据流包含第二组纹理坐标。
// 多数据流顶点
// 数据流0, 位置, 漫反射色, 镜面反射色
struct POSCOLORVERTEX
{
FLOAT x, y, z;
DWORD diffColor, specColor;
};
#define D3DFVF_POSCOLORVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_SPECULAR)
// 数据流1, 纹理坐标组0
struct TEXC0VERTEX
{
FLOAT tu1, tv1;
};
#define D3DFVF_TEXC0VERTEX (D3DFVF_TEX1)
// 数据流2, 纹理坐标组1
struct TEXC1VERTEX
{
FLOAT tu2, tv2;
};
#define D3DFVF_TEXC1VERTEX (D3DFVF_TEX0)
顶点定义为:
// 多重纹理 – 多重数据流
D3DVERTEXELEMENT9 dwDecl3[] =
{
{ 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
{ 0, 12, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0 },
{ 0, 28, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 1 },
{ 1, 0, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
{ 2, 0, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
D3DDECL_END()
};
现在创建顶点声明对象,如下所示:
LPDIRECT3DVERTEXDECLARATION9 m_pVertexDeclaration;
g_d3dDevice->CreateVertexDeclaration( dwDecl3, m_pVertexDeclaration );
只用漫反射色渲染的顶点声明和数据流设置看起来会如下所示:
D3DVERTEXELEMENT9 dwDecl3[] =
{
{ 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
{ 0, 12, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0 },
{ 0, 28, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 1 },
D3DDECL_END()
};
m_pd3dDevice->SetStreamSource( 0, m_pVBVertexShader0, 0, sizeof(CUSTOMVERTEX) );
m_pd3dDevice->SetStreamSource( 1, NULL, 0, 0);
m_pd3dDevice->SetStreamSource( 2, NULL, 0, 0);
两个数据流,使用颜色和纹理
使用单纹理进行渲染的顶点声明和数据流设置看起来会如下所示:
D3DVERTEXELEMENT9 dwDecl3[] =
{
{ 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
{ 0, 12, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0 },
{ 0, 28, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 1 },
{ 1, 0, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
D3DDECL_END()
};
m_pd3dDevice->SetStreamSource( 0, m_pVBPosColor, 0, sizeof(POSCOLORVERTEX) );
m_pd3dDevice->SetStreamSource( 1, m_pVBTexC0, 0, sizeof(TEXC0VERTEX) );
m_pd3dDevice->SetStreamSource( 2, NULL, 0, 0);
三个数据流,使用颜色和两张纹理
使用两张纹理进行多重纹理渲染的顶点声明和数据流设置看起来会如下所示。
D3DVERTEXELEMENT9 dwDecl3[] =
{
{ 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
{ 0, 12, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0 },
{ 0, 28, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 1 },
{ 1, 0, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
{ 2, 0, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
D3DDECL_END()
};
m_pd3dDevice->SetStreamSource( 0, m_pVBPosColor, 0, sizeof(POSCOLORVERTEX) );
m_pd3dDevice->SetStreamSource( 1, m_pVBTexC0, 0, sizeof(TEXC0VERTEX) );
m_pd3dDevice->SetStreamSource( 2, m_pVBTexC1, 0, sizeof(TEXC1VERTEX) );
以上所有三种情况,都可以调用以下IDirect3DDevice9::DrawPrimitive。
m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, NUM_TRIS );
这个例子显示了数据流在解决重复的数据/冗余数据在总线上的传输(也就是说,带宽的浪费)问题上的灵活性。
顶点颜色着色器
本主题显示了初始化和使用一个用到了位置和漫反射色的简单顶点着色器的必须步骤。
第一步是声明用来保存位置和颜色的结构,如以下示例代码所示。
struct XYZBuffer
{
FLOAT x, y, z;
};
struct ColBuffer
{
D3DCOLOR color;
};
下一步是创建顶点着色器声明,如以下示例代码所示。
D3DVERTEXELEMENT9 decl[] =
{
{ 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
{ 1, 0, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0 },
D3DDECL_END()
};
现在创建顶点声明对象:
LPDIRECT3DVERTEXDECLARATION9 m_pVertexDeclaration;
g_d3dDevice->CreateVertexDeclaration( decl, &m_pVertexDeclaration );
下一步是调用IDirect3DDevice9::CreateVertexShader方法创建顶点着色器。但首先,必须先对着色器进行汇编。
TCHAR strVertexShaderPath[512];
LPD3DXBUFFER pCode;
LPDIRECT3DVERTEXSHADER9 m_pVertexShader;
hr = DXUtil_FindMediaFileCb( strVertexShaderPath,
sizeof(strVertexShaderPath), _T("ShaderFile.vsh");
hr = D3DXAssembleShaderFromFile( strVertexShaderPath, NULL, NULL,
dwFlags, &pCode, NULL );
g_d3dDevice->CreateVertexShader( (DWORD*)pCode->GetBufferPointer(),
&m_pVertexShader );
以下示例代码显示了如何设置顶点着色器,设置数据流的源,然后渲染三角形表。
g_pd3dDevice->SetVertexDeclaration( m_pVertexDeclaration );
g_d3dDevice->SetVertexShader( m_pVertexShader );
g_d3dDevice->SetStreamSource( 0, xyzbuf, sizeof(XYZBuffer));
g_d3dDevice->SetStreamSource( 1, colbuf, sizeof(ColBuffer));
g_d3dDevice->SetIndices( pIB, 0 );
g_d3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, max - min + 1, 0, count / 3 );
单纹理着色器
本主题显示了初始化和使用一个用到了位置和一组纹理坐标的简单顶点着色器的必须步骤。
第一步是声明用来保存位置和纹理坐标的结构,如以下示例代码所示。
struct XYZBuffer
{
float x, y, z;
};
struct TEX0Buffer
{
float tu, tv;
};
下一步是创建顶点着色器声明,如以下示例代码所示。
D3DVERTEXELEMENT9 decl[] =
{
{ 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
{ 1, 0, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
D3DDECL_END()
};
下一步是调用IDirect3DDevice9::CreateVertexShader方法创建顶点着色器。但首先,必须先对着色器进行汇编。
TCHAR strVertexShaderPath[512];
LPD3DXBUFFER pCode;
LPDIRECT3DVERTEXSHADER9 m_pVertexShader;
hr = m_pd3dDevice->CreateVertexDeclaration( decl, &m_pVertexDeclaration );
hr = DXUtil_FindMediaFileCb( strVertexShaderPath,
sizeof(strVertexShaderPath), _T("ShaderFile.vsh");
hr = D3DXAssembleShaderFromFile( strVertexShaderPath, NULL, NULL,
dwFlags, &pCode, NULL );
g_d3dDevice->CreateVertexShader( (DWORD*)pCode->GetBufferPointer(),
&m_pVertexShader );
在创建完顶点缓存和顶点着色器后,就可以使用了。以下示例代码显示了如何设置顶点着色器,设置数据流的源,然后用新的顶点着色器绘制三角形表。
g_d3dDevice->SetVertexShader( m_pVertexShader );
g_d3dDevice->SetStreamSource( 0, xyzbuf, 0, sizeof(XYZBuffer));
g_d3dDevice->SetStreamSource( 1, tex0buf, 0, sizeof(TEX0Buffer));
g_d3dDevice->SetIndices( pIB, 0 );
g_d3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, max - min + 1, 0, count / 3 );
多重纹理纹理着色器
本主题显示了初始化和使用一个用到了位置和用于多重纹理的多组纹理坐标的简单顶点着色器的必须步骤。
第一步是声明用来保存位置和纹理坐标的结构,如以下示例代码所示。
struct XYZBuffer
{
float x, y, z;
};
struct Tex0Buffer
{
float tu, tv;
};
struct Tex1Buffer
{
float tu2, tv2;
};
下一步是创建顶点着色器声明,如以下示例代码所示。
D3DVERTEXELEMENT9 decl[] =
{
{ 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
{ 1, 0, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
{ 2, 0, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1 },
D3DDECL_END()
};
现在创建顶点声明对象:
LPDIRECT3DVERTEXDECLARATION9 m_pVertexDeclaration;
g_d3dDevice->CreateVertexDeclaration( decl, &m_pVertexDeclaration );
下一步是调用IDirect3DDevice9::CreateVertexShader方法创建顶点着色器。但首先,必须先对着色器进行汇编。
TCHAR strVertexShaderPath[512];
LPD3DXBUFFER pCode;
LPDIRECT3DVERTEXSHADER9 m_pVertexShader;
hr = DXUtil_FindMediaFileCb( strVertexShaderPath,
sizeof(strVertexShaderPath), _T("ShaderFile.vsh");
hr = D3DXAssembleShaderFromFile( strVertexShaderPath, NULL, NULL,
dwFlags, &pCode, NULL );
g_d3dDevice->CreateVertexShader( (DWORD*)pCode->GetBufferPointer(),
&m_pVertexShader );
在创建完顶点缓存和顶点着色器后,就可以使用了。以下示例代码显示了如何设置顶点着色器,设置数据流的源,然后用新的顶点着色器绘制三角形表。
g_pd3dDevice->SetVertexDeclaration( m_pVertexDeclaration );
g_d3dDevice->SetVertexShader( m_pVertexShader );
g_d3dDevice->SetStreamSource( 0, xyzbuf, 0, sizeof(XYZBuffer) );
g_d3dDevice->SetStreamSource( 1, tex0buf, 0, sizeof(Tex0Buffer) );
g_d3dDevice->SetStreamSource( 2, tex1buf, 0, sizeof(Tex1Buffer) );
g_d3dDevice->SetIndices( pIB, 0 );
g_d3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, max - min + 1, 0, count / 3 );
在Microsoft® DirectX® 8.x之前,Microsoft® Direct3D®以固定功能流水线的方式运作,把三维几何体渲染到屏幕上的像素。用户设置流水线的属性,以控制Direct3D变换、光照和渲染像素的方式。固定功能顶点格式用来确定输入顶点的格式,在编译时定义。一旦定义,用户就无法在运行的时候控制流水线的改变。
通过允许在运行的时候进行变换、光照和渲染的功能,可编程着色器将图形流水线带入了一个新的高度。着色器是在运行时定义的,但是当完成以后,用户可以改变要激活哪个着色器,并使用数据流动态地控制着色器。这在渲染像素的方法上,给了用户更高的灵活性。
顶点着色器文件包含顶点着色器指令。顶点着色器可以控制顶点颜色和把纹理应用于顶点的方式。也可以通过使用顶点着色器指令加入光照。着色器指令文件包含的是ASCII文本,因此它是可读的,并且看起来有点象汇编语言。顶点着色器在任何DrawPrimitive或DrawIndexedPrimitive之后被调用。用SetVertexShader指定新的着色器文件可以动态地切换着色器,也可以用数据流输入改变ASCII文本的着色器文件中的指令。Vertex_Shader_1_1中包含了着色器指令的完全列表。
指令集的变化非常快。为避免在使用指令时出现问题,请查阅硬件开发商的网站。或者,也可以使用高级着色器语言,这样就可以得到由Direct3D扩展(D3DX)编译得到的着色器指令。
本示例创建一个顶点着色器,把一固定颜色应用于物体。本示例将会给出着色器文件的内容,以及为了在应用程序中使用该着色器而设置Microsoft® Direct3D®流水线所需的代码。
如果读者已经知道如何构建并运行Direct3D示例,那么可以从本示例中复制代码并粘贴到已有的应用程序中。
本文讨论了以下几个步骤。
本示例使用由两个三角形构成的四边形。顶点数据包含(x,y,z)位置和漫反射色。顶点数据在一个全局顶点数组(g_Vertices)中声明。四个顶点以原点为中心。
// 声明顶点数据结构。
struct CUSTOMVERTEX
{
FLOAT x, y, z;
};
// 声明顶点位置和漫反射色。
CUSTOMVERTEX g_Vertices[]=
{
// x y z
{ -1.0f, -1.0f, 0.0f },
{ +1.0f, -1.0f, 0.0f },
{ +1.0f, +1.0f, 0.0f },
{ -1.0f, +1.0f, 0.0f },
};
该着色器将一固定颜色应用于每个顶点。以下为着色器文件VertexShader.vsh:
vs_1_1 // 版本指令
dcl_position v0 // 说明位置数据在寄存器v0中
m4x4 oPos, v0, c0 // 用视/投影矩阵变换顶点
mov oD0, c4 // 载入固定颜色
该文件包含了三条数据运算指令和一个寄存器定义。着色器文件的第一条指令必须是着色器版本声明。vs指令用来声明顶点着色器的版本,此处为1_1。
dcl_position指令把寄存器v0定义为顶点位置数据的源。m4x4指令用视/投影矩阵对物体的顶点进行变换。矩阵被载入并保存在常量寄存器c0, c1, c2, c3(如下所示)中。Mov指令把寄存器c4中的固定颜色复制到输出漫反射色寄存器oD0中。这导致输出顶点的颜色被改变。
在使用顶点着色器之前,可以查询驱动程序对顶点着色器的支持。
D3DCAPS9 caps;
m_pd3dDevice->GetDeviceCaps(&caps); // 在使用前初始化m_pd3dDevice
if( caps.VertexShaderVersion < D3DVS_VERSION(1,1) )
return E_FAIL;
在调用IDirect3DDevice9::GetDeviceCaps后,caps结构会返回硬件可用的能力。应该用D3DVS_VERSION宏测试硬件支持的版本号。如果版本号小于1_1,那么该调用将会失败。这个方法的结果应该被应用程序用来控制是否使用顶点着色器。
顶点声明用来描述顶点数据。
// 创建着色器声明。
D3DVERTEXELEMENT9 decl[] =
{
{ 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
D3DDECL_END()
};
这个声明说明了以下这些:数据来自数据流0,从数据流起始位置起偏移量为0处的位置开始,声明位置数据为三个浮点数(D3DDECLTYPE_FLOAT3),告诉tessellator复制顶点数据(D3DDECLMETHOD_DEFAULT),定义数据的用途为顶点数据(D3DDECLUSAGE_POSITION),并说明用途索引为0。
D3DDECLEND()宏用来结束顶点声明。
下一步汇编并创建着色器。
LPDIRECT3DPIXELSHADER9 m_pVertexShader;
TCHAR strShaderPath[512];
LPD3DXBUFFER pCode; // 包含经过汇编的着色器代码的缓存
LPD3DXBUFFER pErrorMsgs; // 包含错误信息的缓存
DXUtil_FindMediaFileCb( strShaderPath, sizeof(strShaderPath),
_T("VertexShader.vsh") );
D3DXAssembleShaderFromFile( strPixelShaderPath, NULL, NULL, 0,
&pCode, &pErrorMsgs, NULL );
m_pd3dDevice->CreateVertexShader((DWORD*)pCode->GetBufferPointer(),
&m_pVertexShader)
pCode->Release();
pErrorMsgs->Release()
在定位了着色器文件后,D3DXAssembleShaderFromFile读取并验证着色器指令。然后IDirect3DDevice9::CreateVertexShader对指令进行汇编并创建着色器。该方法会返回用于绘制时调用的着色器对象。
CreateVertexShader用于创建一个可编程着色器。
这里是一份代码示例,可以用在渲染循环中用顶点着色器来渲染物体。由于三维场景的变化,渲染循环不断更新顶点着色器常数,并调用IDirect3DDevice9::DrawPrimitive绘制输出顶点。
// 用视/投影矩阵更新顶点着色器常数。
D3DXMATRIX mat, matView, matProj;
D3DXMatrixMultiply( &mat, &matView, &matProj );
D3DXMatrixTranspose( &mat, &mat );
m_pd3dDevice->SetVertexShaderConstantF( 0, (float*)&mat, 4 );
// 声明并定义固定的顶点颜色。
float color[4] = {0,1.0,0,0};
m_pd3dDevice->SetVertexShaderConstantF( 4, (float*)&color, 1 );
// 关闭镜面反射光,因为顶点着色器没有输出镜面反射光。
m_pd3dDevice->SetRenderState( D3DRS_SPECULAR, FALSE );
// 渲染输出。
m_pd3dDevice->SetStreamSource( 0, m_pQuadVB, sizeof(CUSTOMVERTEX) );
m_pd3dDevice->SetVertexShader( m_pVertexShader );
m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2 );
视和投影矩阵包含了摄像机的位置和方向的数据。因为场景可能在渲染得到的帧之间变化,所以渲染循环中要得到最新的数据,并用这些数据更新着色器的常量寄存器
IDirect3DDevice9::DrawPrimitive照常使用IDirect3DDevice9::SetStreamSource提供的数据渲染输出数据。IDirect3DDevice9::SetVertexShader是用来告诉Direct3D要使用顶点着色器。顶点着色器的结果如下图,它显示了在平面物体上的固定颜色。
本示例把顶点数据中的顶点颜色应用于物体。顶点数据包含了位置和漫反射色。这些数据如下所示:
struct CUSTOMVERTEX_POS_COLOR
{
float x, y, z;
DWORD diffuseColor;
};
// 创建包含位置和纹理坐标的顶点数据。
CUSTOMVERTEX_POS_COLOR g_Vertices[]=
{
// x y z 漫反射色
{ -1.0f, 0.25f, 0.0f, 0xffff0000, }, // 右下 – 红
{ 0.0f, 0.25f, 0.0f, 0xff00ff00, }, // 左下 – 绿
{ 0.0f, 1.25f, 0.0f, 0xff0000ff, }, // 左上 – 蓝
{ -1.0f, 1.25f, 0.0f, 0xffffffff, }, // 右上 – 白
};
顶点着色器声明也需要反映位置和颜色数据。
// 创建着色器声明。
D3DVERTEXELEMENT9 decl[] =
{
{ 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
{ 0, 12, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0 },
D3DDECL_END()
};
着色器得到变换矩阵的一种方法是通过常量寄存器,常量寄存器是通过调用SetVertexShaderConstant设置的。
D3DXMATRIX mat;
D3DXMatrixMultiply( &mat, &m_matView, &m_matProj );
D3DXMatrixTranspose( &mat, &mat );
hr = m_pd3dDevice->SetVertexShaderConstantF( 1, (float*)&mat, 4 );
if(FAILED(hr))
return hr;
该声明声明了一个包含了位置和颜色数据的数据流。颜色数据被指定到寄存器1。以下是着色器代码。
vs_1_1 ; 版本指令
m4x4 oPos, v0, c0 ; 用视/投影矩阵变换顶点
mov oD0, v1 ; 从寄存器1载入颜色并赋给漫反射色
这个着色器包含三条指令。第一条总是版本指令。第二条指令用来变换顶点。第三条指令用来把顶点寄存器中的颜色复制到输出漫反射色寄存器中。结果是输出顶点使用了顶点的颜色数据。
得到的输出看起来如下所示:
本示例将一张纹理贴图应用于物体。
顶点数据包含了物体的位置和纹理坐标(uv)。这导致了顶点声明的改变。下面还显示了顶点数据。
struct CUSTOMVERTEX_POS_TEX1
{
float x, y, z; // 物体位置
float tu1, tv1; // 纹理坐标
};
CUSTOMVERTEX_POS_TEX1 g_Vertices[]=
{
// x y z u1 v1
{ -0.75f, -0.5f, 0.0f, 0.0f, 0.0f }, // 右下 – 红
{ 0.25f, -0.5f, 0.0f, 1.0f, 0.0f }, // 左下 – 绿
{ 0.25f, 0.5f, 0.0f, 1.0f, -1.0f }, // 左上 – 蓝
{ -0.75f, 0.5f, 0.0f, 0.0f, -1.0f }, // 右上 – 白
};
D3DUtil_CreateTexture( m_pd3dDevice, TEXT("earth.bmp"),
&m_pTexture0, D3DFMT_R5G6B5 );
必须先载入纹理的图像。在本例中,文件"earth.bmp"包含了地球的二维纹理贴图,并将被用来给物体着色。
顶点着色器声明需要反映出顶点位置和纹理坐标数据。
// 创建着色器声明。
D3DVERTEXELEMENT9 decl[] =
{
{ 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
{ 0, 12, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
D3DDECL_END()
};
该声明声明了一个包含顶点位置和纹理坐标的数据流。
渲染代码告诉Microsoft® Direct3D®到何处去得到数据流和着色器,因为使用了纹理贴图,所以还要设置纹理层。
m_pd3dDevice->SetStreamSource( 0, m_pQuadVB, sizeof(CUSTOMVERTEX_POS_TEX1) );
m_pd3dDevice->SetVertexShader( m_pVertexShader );
m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE );
m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );
m_pd3dDevice->SetTexture( 0, m_pTexture0 );
m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2 );
m_pd3dDevice->SetTexture( 0, NULL );
因为使用了一个纹理,所以需要设置纹理层0的纹理层状态。上面调用的方法告诉Direct3D纹理的texel值用来给物体的顶点提供漫反射色。换句话说,二维纹理贴图的使用就象贴花纸一样。
这里是着色器代码。
vs_1_1 // 版本指令
dcl_position v0 // 声明位置寄存器
dcl_texcoord v8 // 声明纹理坐标寄存器
def c4, 1, 1, 1, 1 // 初始化常量
m4x4 oPos, v0, c0 // 用视/投影矩阵变换顶点
mov oD0, c4 // 把漫反射色赋给输出颜色寄存器
mov oT0, v8 // 把纹理的颜色赋给纹理寄存器
着色器文件包含的这些指令会产生一个贴上了纹理的物体,如下所示。
本实例使用了一个顶点着色器,将纹理贴图和光照应用于场景。这里使用的物体是一个球体,示例代码把地球的纹理贴图应用于球体,并用漫反射光照来模拟昼夜。
Shader3实例给贴上了纹理的物体添加了光照。关于如何载入纹理和设置纹理层状态的信息,请参阅Shader3。
在示例框架中有关于框架的示例代码的详细说明。读者可以复制这里的示例代码并粘贴到示例框架中去,这样就可以很快得到一个能运行的示例。
为了包含顶点法向,需要修改Shader3实例中的顶点数据。要产生光照,物体必须有顶点法向。修改后的顶点数据的数据结构如下所示。
struct CUSTOMVERTEX_POS_NORM_COLOR1_TEX1
{
float x, y, z; // 位置
float nx, ny, nz; // 法向
DWORD color1; // 漫反射色
float tu1, tv1; // 纹理坐标
};
着色器声明定义了输入顶点寄存器以及和它们关联的数据。
// 创建着色器声明。
D3DVERTEXELEMENT9 decl[] =
{
{ 0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
{ 0, 12, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0 },
{ 0, 24, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR, 0 },
{ 0, 28, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
D3DDECL_END()
};
这里声明了一个数据流,其中包含了顶点位置,法向,漫反射色和纹理坐标。
下一步是创建着色器。可以用一个ASCII文本字符串创建着色器,也可以从包含相同指令的着色器文件中载入。本示例使用着色器文件。
// v7 用于光照的顶点漫反射色
// v8 纹理
// c4 视/投影矩阵
// c12 光的方向
vs_1_1 // 版本指令
dcl_position v0 // 声明寄存器数据
dcl_normal v4 // v0为位置,v4为法向
dcl_color0 v7 // v7为漫反射色
dcl_texcoord0 v8 // v8为纹理坐标
m4x4 oPos, v0, c4 // 用视/投影矩阵变换顶点
dp3 r0, v4, c12 // 执行世界空间中的光照计算N dot L
mul oD0, r0.x , v7 // 根据光强度和经插值的顶点漫反射色计算像素的最终颜色
mov oT0.xy , v8 // 将纹理坐标得到到输出
第一条总是版本指令,最后一条指令把纹理数据复制到输出寄存器oT0。写完了着色器指令后,就可以用来创建着色器。
LPDIRECT3DPIXELSHADER9 m_pVertexShader;
TCHAR strShaderPath[512];
LPD3DXBUFFER pCode; // 包含汇编后的着色器代码的缓存
LPD3DXBUFFER pErrorMsgs; // 包含错误信息的缓存
DXUtil_FindMediaFileCb( strShaderPath, sizeof(strShaderPath),
_T("VertexShader3.vsh") );
D3DXAssembleShaderFromFile( strPixelShaderPath, NULL, NULL, 0,
&pCode, &pErrorMsgs, NULL );
m_pd3dDevice->CreateVertexShader((DWORD*)pCode->GetBufferPointer(),
&m_pVertexShader)
pCode->Release();
pErrorMsgs->Release();
在定位了文件后,Microsoft® Direct3D®会创建顶点着色器并返回着色器对象。本示例用了一个着色器文件,这样调用一个方法就可以创建着色器。另一种方法是创建一个包含着色器指令的ASCII文本字符串。
可以在着色器外定义顶点着色器常量,如以下示例所示。此处,常量用来给着色器提供视/投影矩阵,漫反射光颜色RGBA和光的方向向量。
float constants[4] = {0, 0.5f, 1.0f, 2.0f};
m_pd3dDevice->SetVertexShaderConstantF( 0, (float*)&constants, 1 );
D3DXMATRIX mat;
D3DXMatrixMultiply( &mat, &m_matView, &m_matProj );
D3DXMatrixTranspose( &mat, &mat );
m_pd3dDevice->SetVertexShaderConstantF( 4, (float*)&mat, 4 );
float color[4] = {1,1,1,1};
m_pd3dDevice->SetVertexShaderConstantF( 8, (float*)&color, 1 );
float lightDir[4] = {-1,0,1,0}; // fatter slice
m_pd3dDevice->SetVertexShaderConstantF( 12, (float*)&lightDir, 1 );
也可以在着色器内部用def指令定义常量。
在写完着色器指令后,要将顶点数据与正确的顶点寄存器连接并初始化常量,然后渲染输出。渲染代码告诉Direct3D到哪里去得到顶点缓存的数据流,并给Direct3D提供着色器的句柄。因为使用了纹理,所以还必须设置纹理层以告诉Direct3D如何使用纹理数据。
// 设置顶点缓存的数据源。
m_pd3dDevice->SetStreamSource(0, m_pVB, sizeof(CUSTOMVERTEX_POS_NORM_COLOR1_TEX1));
// 设置着色器。
m_pd3dDevice->SetVertexShader( m_pVertexShader );
// 设置用到的纹理和纹理层状态。
m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE );
m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );
m_pd3dDevice->SetTexture( 0, m_pTexture0 );
// 绘制物体。
DWORD dwNumSphereVerts = 2 * m_dwNumSphereRings*(m_dwNumSphereSegments + 1);
m_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, dwNumSphereVerts - 2);
下面是输出的图像。
贴了纹理后,球体看起来像是地球。光照在球体表面产生了从亮到暗的变化。
在Microsoft® DirectX® 9.0中,顶点着色器和顶点声明不再是在创建顶点着色器(CreateVertexShader)的时候绑定在一起。对着色器的验证已经被分成两部分,一部分在顶点着色器创建时执行,另一部分在绘制(DrawPrimitive)时执行。
顶点着色器和顶点声明都由相应的对象表示。为了使之能与DirectX 8.x驱动程序一起工作,Direct3D进行了一些高速缓存。在“绘制的时候”,运行库会检查是否存在一个组合着色器对象,该对象封装了当前声明和着色器。如果存在,那么运行库会将它送给驱动程序,反之,运行库会为当前着色器和声明的组合创建一个新的组合着色器对象。另外,为了解决API的可用性问题,9.0版增加了一个与SetVertexDeclaration调用等价的SetFVF调用。这是一个有用的函数,当调用这个函数时,新的FVF会取代当前的顶点声明,反之亦然。如果驱动程序是DirectX 8.0之前的版本(NumStream为0),那么对于那些不能被转换成弹性顶点格式(FVF)的顶点声明,SetVertexDeclaration可能会失败并返回错误码。
在DirectX 8.x驱动程序(numstrems不为零,但不支持数据流偏移量)上允许使用声明的一个子集,只能创建那些可被转换为 DirectX 8.x风格的声明。由于这个原因,如果声明不能被转换,那么CreateVertexDeclaration调用可能会失败。对混合模式设备来说,此类失败会发生在Drawxxx的时候,因为只有这时候才能知道着色器是被用于硬件还是软件顶点处理。表中概括了这类转换。
在DirectX 8.0之前的驱动程序(numstreams为零)上允许使用一个更小的子集,只能使用那些可被转换为FVF的声明。如果无法进行转换,那么CreateVertexDeclaration可能会失败。对混合模式设备来说,此类失败会发生在Drawxxx的时候,因为只有这时候才能知道着色器是被用于硬件还是软件顶点处理。表中概括了这类转换。只能使用数据流0(显然可以从MaxStreams设备能力中看出)。
顶点元素的顺序应该和FVF码相对应,D3DDECLUSAGE_POSITION和D3DDECLUSAGE_NORMAL的用途索引应该为零。使用混合顶点处理的设备时,如果要切换顶点处理模式,那么无需重置所有的输入顶点(和索引)数据流,顶点声明和顶点函数。不能用NULL作为SetVertexDeclaration的输入。在使用软件顶点处理时,可编程顶点流水线的着色器代码中用到的用途也应该存在于在绘制的时候与之绑定的声明(或FVF)中。
符合以下规则的声明可以用来在固定功能流水线(假设不需要tessellation)中进行渲染。
DirectX 8.x |
DirectX 9.0 用途 |
DirectX 9.0 用途索引 |
D3DVSDE_POSITION |
D3DDECLUSAGE_POSITION |
0 |
D3DVSDE_POSITION2 |
D3DDECLUSAGE_POSITION |
1 |
D3DVSDE_NORMAL |
D3DDECLUSAGE_NORMAL |
0 |
D3DVSDE_NORMAL2 |
D3DDECLUSAGE_NORMAL |
1 |
D3DVSDE_BLENDWEIGHT |
D3DDECLUSAGE_BLENDWEIGHT |
0 |
D3DVSDE_BLENDINDICES |
D3DDECLUSAGE_BLENDINDICES |
0 |
D3DVSDE_PSIZE |
D3DDECLUSAGE_PSIZE |
0 |
D3DVSDE_DIFFUSE |
D3DDECLUSAGE_COLOR |
0 |
D3DVSDE_SPECULAR |
D3DDECLUSAGE_COLOR |
1 |
D3DVSDE_TEXCOORDn |
D3DDECLUSAGE_TEXCOORD |
n |
FVF |
数据类型 |
用途 |
用途索引 |
D3DFVF_XYZ |
D3DDECLTYPE_FLOAT3 |
D3DDECLUSAGE_POSITION |
0 |
D3DFVF_XYZRHW |
D3DDECLTYPE_FLOAT4 |
D3DDECLUSAGE_POSITIONT |
0 |
D3DFVF_XYZW |
D3DDECLTYPE_FLOAT4 |
D3DDECLUSAGE_POSITIONT |
0 |
D3DFVF_XYZB5 and D3DFVF_LASTBETA_UBYTE4 |
D3DVSDT_FLOAT3, D3DVSDT_FLOAT4, D3DVSDT_UBYTE4 |
D3DDECLUSAGE_POSITION, D3DDECLUSAGE_BLENDWEIGHT, D3DDECLUSAGE_BLENDINDICES |
0 |
D3DFVF_XYZB5 |
D3DDECLTYPE_FLOAT3, D3DDECLTYPE_FLOAT4, D3DDECLTYPE_FLOAT1 |
D3DDECLUSAGE_POSITION, D3DDECLUSAGE_BLENDWEIGHT, D3DDECLUSAGE_BLENDINDICES |
0 |
D3DFVF_XYZBn (n=1..4) |
D3DDECLTYPE_FLOAT3 D3DDECLTYPE_FLOATn |
D3DDECLUSAGE_POSITION, D3DDECLUSAGE_BLENDWEIGHT |
0 |
D3DFVF_XYZBn (n=1..4) and D3DFVF_LASTBETA_UBYTE4 |
D3DDECLTYPE_FLOAT3 D3DDECLTYPE_FLOAT(n-1) D3DDECLTYPE_UBYTE4 |
D3DDECLUSAGE_POSITION, D3DDECLUSAGE_BLENDWEIGHT, D3DDECLUSAGE_BLENDINDICES |
0 |
D3DFVF_NORMAL |
D3DDECLTYPE_FLOAT3 |
D3DDECLUSAGE_NORMAL |
0 |
D3DFVF_PSIZE |
D3DDECLTYPE_FLOAT1 |
D3DDECLUSAGE_PSIZE |
0 |
D3DFVF_DIFFUSE |
D3DDECLTYPE_D3DCOLOR |
D3DDECLUSAGE_COLOR |
0 |
D3DFVF_SPECULAR |
D3DDECLTYPE_D3DCOLOR |
D3DDECLUSAGE_COLOR |
1 |
D3DFVF_TEXCOORDSIZEm(n) |
D3DDECLTYPE_FLOATm |
D3DDECLUSAGE_TEXCOORD |
n |
用途 |
用途索引 |
DirectX decl |
D3DDECLUSAGE_POSITION |
0 |
D3DVSDE_POSITION |
D3DDECLUSAGE_POSITION |
1 |
D3DVSDE_POSITION2 |
D3DDECLUSAGE_BLENDWEIGHT |
0 |
D3DVSDE_BLENDWEIGHT |
D3DDECLUSAGE_BLENDINDICES |
0 |
D3DVSDE_BLENDINDICES |
D3DDECLUSAGE_NORMAL |
0 |
D3DVSDE_NORMAL |
D3DDECLUSAGE_NORMAL |
1 |
D3DVSDE_NORMAL2 |
D3DDECLUSAGE_PSIZE |
0 |
D3DVSDE_PSIZE |
D3DDECLUSAGE_COLOR |
0 |
D3DVSDE_DIFFUSE |
D3DDECLUSAGE_COLOR |
1 |
D3DVSDE_SPECULAR |
D3DDECLUSAGE_TEXCOORD |
n |
D3DVSDE_TEXTUREn, n <= 7 |
在DirectX 8.0及以后的驱动程序上,更多的类型可以成功地被转换为有效的声明,因此可以被用于固定功能顶点处理。
数据类型 |
用途 |
用途索引 |
FVF |
D3DDECLTYPE_FLOAT3 |
D3DDECLUSAGE_POSITION |
0 |
D3DFVF_XYZ |
D3DDECLTYPE_FLOATn |
D3DDECLUSAGE_BLENDWEIGHT |
0 |
D3DFVF_XYZBn |
D3DDECLTYPE_UBYTE4 |
D3DDECLUSAGE_BLENDINDICES |
0 |
D3DFVF_XYZB (nWeights+1) |
D3DDECLTYPE_FLOAT3 |
D3DDECLUSAGE_NORMAL |
0 |
D3DFVF_NORMAL |
D3DDECLTYPE_FLOAT1 |
D3DDECLUSAGE_PSIZE |
0 |
D3DFVF_PSIZE |
D3DDECLTYPE_D3DCOLOR |
D3DDECLUSAGE_COLOR |
0 |
D3DFVF_DIFFUSE |
D3DDECLTYPE_D3DCOLOR |
D3DDECLUSAGE_COLOR |
1 |
D3DFVF_SPECULAR |
D3DDECLTYPE_FLOATm |
D3DDECLUSAGE_TEXCOORD |
n |
D3DFVF_TEXCOORDSIZEm(n) |
D3DDECLTYPE_FLOAT3 |
D3DDECLUSAGE_POSITION |
1 |
N/A |
D3DDECLTYPE_FLOAT3 |
D3DDECLUSAGE_NORMAL |
1 |
N/A |
在Microsoft DirectX® 8.0之前,Microsoft® Direct3D®使用固定功能流水线把三维几何体转换为屏幕上的像素。用户通过设置流水线的属性来控制Direct3D进行变换、光照和渲染像素的方式。固定功能顶点格式在编译的时候定义并决定输入顶点的格式,一旦定义,用户在运行的时候就几乎无法控制流水线的改变。
通过允许对顶点的变换、光照和对每个像素的着色等功能进行编程,着色器把图形流水线引入了一个新的高度。像素着色器是一些小程序,在对三角形进行光栅化操作时运行。这在渲染像素的方法上给了用户更高一级的灵活性。
像素着色器包含由ASCII文本组成的像素着色器指令。算术指令可以用来进行漫反射和/或镜面反射光照计算。纹理寻址指令提供了多种读取和应用纹理数据的操作。着色器具有这样的功能,可以给颜色分量设置掩码以及交换颜色分量。着色器的正文看起来有点像汇编语言,它用Direct3D扩展(D3DX)进行汇编,输入可以是文本字符串或是文件。汇编器的输出是一系列操作码,应用程序可以通过IDirect3DDevice9::CreatePixelShader方法把这些操作码提供给Direct3D。像素着色器有几个版本,请参阅着色器参考手册。
指令集的变化非常快。为避免在使用指令时出现问题,请查阅硬件开发商的网站。或者,也可以使用高级着色器语言,这样就可以得到由Direct3D扩展(D3DX)编译得到的着色器指令。
本示例用像素着色器对一个四边形的漫反射色进行高洛德插值。示例显示了着色器文件的内容以及应用程序中所需的代码。
以下是创建像素着色器所需的步骤:
如果读者已经知道如何构建并运行Direct3D示例,那么可以从本示例中复制代码并粘贴到已有的应用程序中。
要检查对像素着色器的支持,应该使用以下代码。这个例子检查1.1版本的像素着色器。
D3DCAPS9 caps;
m_pd3dDevice->GetDeviceCaps(&caps); // 使用m_pd3dDevice前要进行初始化
if( caps.PixelShaderVersion < D3DPS_VERSION(1,1) )
return E_FAIL;
caps结构会返回硬件可用的能力。要用D3DPS_VERSION宏检查当前硬件支持的所有着色器版本。如果caps返回的版本小于1.1,那么这个调用会失败。反之,对所有大于或等于1.1的版本,调用会成功。如果硬件不支持被测试的着色器版本,那么应用程序将不得不退而使用别的渲染方法(也许可以使用一个较低版本的着色器)。
这个示例使用了一个四边形,由两个三角形组成。每个顶点的数据结构包含了位置和漫反射色数据。D3DFVF_CUSTOMVERTEX宏定义了与顶点数据相匹配的数据结构。实际的顶点数据在全局数组g_Vertices中声明。四个顶点以原点为中心,每个顶点具有不同的漫反射色。
// 声明顶点数据结构。
struct CUSTOMVERTEX
{
FLOAT x, y, z;
DWORD diffuseColor;
};
// 声明自定义FVF宏。
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE)
// 声明顶点位置和漫反射色数据。
CUSTOMVERTEX g_Vertices[]=
{
// x y z 漫反射色
{ -1.0f, -1.0f, 0.0f, 0xffff0000 }, // 红 – 左下
{ +1.0f, -1.0f, 0.0f, 0xff00ff00 }, // 绿 – 右下
{ +1.0f, +1.0f, 0.0f, 0xff0000ff }, // 蓝 – 右上
{ -1.0f, +1.0f, 0.0f, 0xffffffff }, // 白 – 左上
};
这个着色器把经过高洛德插值的漫反射色数据复制到输出像素。着色器文件PixelShader.txt如下所示:
ps_1_1 // 版本指令
mov r0,v0 // 把顶点的漫反射色复制到输出寄存器。
像素着色器文件的第一条指令声明了像素着色器的版本,此处为1.1。
第二条指令把颜色寄存器(v0)的内容复制到输出寄存器(r0)。因为在第1步中声明的顶点数据已经包含了经过插值的漫反射色,所以颜色寄存器包含了顶点的漫反射色。输出寄存器决定渲染目标使用的像素颜色(因为本例中没有更多的处理,如雾,所以输出寄存器就是最终的像素颜色)。
像素着色器由像素着色器指令创建。本例中,指令被包含在一个单独的文件中。指令也可以被包含在一个文本字符串中。
LPD3DXBUFFER pCode; // 存放经过汇编的着色器代码的缓存
LPD3DXBUFFER pErrorMsgs; // 存放错误信息的缓存