Microsoft? DirectX? 8.0引入了数据流的概念,用来把数据绑定到着色器使用的输入寄存器。一个数据流是一个成员数据的数组,每个成员由一个或多个元素构成,这些元素代表单个实体,如位置、法向、颜色等等。数据流使图形芯片能并行地从多个顶点缓存执行直接内存访问(DMA)操作,同时也降低了多重纹理的开销。可以这样理解数据流:
一个顶点由n个数据流组成。
一个数据流由m个元素组成。
一个元素是[位置、颜色、法向、纹理坐标]。
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 );
这个例子显示了数据流在解决重复的数据/冗余数据在总线上的传输(也就是说,带宽的浪费)问题上的灵活性。