在DirectX 10中渲染精灵的最有效方法是什么?

时间:2013-07-01 14:05:24

标签: c++ sprite directx-10 geometry-instancing

我目前正在尝试在DirectX 10中显示2D精灵的各种方法。我首先使用ID3DX10Sprite界面在一次调用中批量绘制我的精灵。然而,最终,我想要更多地控制我的精灵渲染方式,所以我决定研究基于四边形的精灵渲染(即每个精灵都用四边形表示并应用了纹理)。

我开始很简单:我创建了一个顶点缓冲区,由4个顶点组成,这些顶点在绘制精灵之前应用了一次。然后我循环遍历我的精灵,设置适当的属性以传递到着色器,并为每个精灵进行绘制调用,如:d3dDevice->Draw( 4, 0);。虽然它有效但是每个精灵的绘图调用都会让我感到烦恼,所以我寻找一种更有效的方法。

在搜索之后,我了解了对象实例,并决定尝试一下。一切顺利,直到我尝试实现精灵中最重要的部分 - 纹理。简而言之,虽然我有一个纹理数组(在我的着色器的顶部声明,如Texture2D textures[10];),可以使用文字/常量作为索引在我的像素着色器中成功采样,但我无法弄清楚如何控制哪个纹理通过纹理索引应用于哪些实例。

我的想法是为每个实例传递一个纹理索引,然后可以用它在像素着色器中对数组中的相应纹理进行采样。然而,在搜索了更多之后,我找不到一个如何完成它的例子(并且发现许多事情表明如果不移动到DirectX 11就无法完成)。

是否可以说在DirectX 10中通过对象实例成功渲染精灵的唯一方法是根据纹理分批渲染它们?因此,例如,如果我的场景包含100个具有20种不同纹理的精灵(每个纹理由5个精灵引用),那么它将需要20个单独的绘制调用来显示场景,而我一次只会发送5个精灵。

最后,我感到很茫然。我做了很多搜索,似乎想出了相互矛盾的信息。例如,在第6段的this文章中,它指出:

  

使用DirectX 10,可以将数组中的不同纹理应用于同一对象的不同实例,从而使它们看起来不同

此外,在this白皮书的第3页,它提到了以下选项:

  

从纹理数组中读取每个实例的自定义纹理

但是,我似乎无法找到如何设置着色器以使用每个实例纹理索引访问纹理数组的具体示例。

最后,核心问题是:使用DirectX 10渲染精灵的最有效方法是什么?

如果答案是实例化,那么是否可以控制哪个纹理应用于着色器中的每个特定实例 - 从而可以发送更大批量的精灵及其适当的纹理索引,只需一个画电话?或者我必须满足于一次仅使用相同纹理实例化sprite?

如果答案是回到使用提供的DX10 Sprite界面,那么我有办法更好地控制它的渲染方式吗?

作为旁注,我还研究了使用几何着色器来创建实际的四边形,所以我只需要传递一系列点而不是管理顶点和实例缓冲区。但是,除非有一种方法可以控制将哪些纹理应用于生成的四边形,然后我回到仅按纹理对精灵进行批处理。

2 个答案:

答案 0 :(得分:4)

有一些方法(像往常一样)做你所描述的。

请注意使用

Texture2D textures[10];

将不允许您在Pixel Shader中使用变量索引进行查找(因为从技术上讲,此声明将为每个纹理分配一个插槽)。

所以你需要的是创建一个Texture2DArray。这有点像体积纹理,但z分量是一个完整的数字,并且没有采样。

您需要生成此纹理数组。简单的方法是在启动时进行一次全屏四次绘制调用,将每个纹理绘制到数组的一个切片中(您可以为特定切片创建一个RenderTargetView)。 Shader将是一个简单的passtrough。

创建纹理数组(代码在SlimDX中,但选项类似):

 var texBufferDesc = new Texture2DDescription
  {
            ArraySize = TextureCount,
            BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
            CpuAccessFlags = CpuAccessFlags.None,
            Format = format,
            Height = h,
            Width = w,
            OptionFlags = ResourceOptionFlags.None,
            SampleDescription = new SampleDescription(1,0),
            Usage = ResourceUsage.Default,
 };

然后着色器资源视图是这样的:

  ShaderResourceViewDescription srvd = new ShaderResourceViewDescription()
  {
      ArraySize = TextureCount,
      FirstArraySlice = 0,
      Dimension = ShaderResourceViewDimension.Texture2DArray,
      Format = format,
      MipLevels = 1,
      MostDetailedMip = 0
  };

最后,获取特定切片的渲染目标:

 RenderTargetViewDescription rtd = new RenderTargetViewDescription()
 {
      ArraySize = 1,
      FirstArraySlice = SliceIndex,
      Dimension = RenderTargetViewDimension.Texture2DArray,
      Format = this.Format
 };

将它绑定到您的passtrough着色器,将所需纹理设置为输入并切片为输出并绘制全屏四边形(或全屏三角形)。

请注意,此纹理也可以以dds格式保存(因此每次启动程序时都可以保存以重新生成)。

查找纹理就像:

Texture2DArray myarray;

在Pixel Shader中:

myarray.Sample(mySampler, float2(uv,SliceIndex);

现在关于渲染精灵,你也可以选择GS扩展。

所以你创建一个顶点缓冲区只包含位置/大小/ textureindex /其他你需要每个精灵一个顶点的其他顶点。

使用n个精灵发送绘图调用(拓扑需要设置为点列表)。

将数据从顶点着色器传递到几何着色器。

在几何着色器中将您的点扩展为四边形,您可以在Microsoft SDK中找到一个ParticlesGS的示例,这对您的情况来说有点矫枉过正,因为您只需要渲染部分,而不是动画。如果你需要一些清理过的代码让我知道我会快速制作一个dx10兼容的样本(在我的情况下,我使用StructuredBuffers而不是VertexBuffer)

做一个预先制作的Quad并在Per Instance VertexBuffer中传递上面的数据也是可能的,但是如果你有大量的精灵它会很容易炸掉你的显卡(高我意味着超过300万颗粒) ,现在的标准并不多,但如果你的精灵数量不足50万,你就会完全没问题;)

答案 1 :(得分:0)

在实例缓冲区中包含纹理索引,并使用它来从每个实例的纹理数组中选择正确的纹理:

struct VS
{
    float3 Position: POSITION;
    float2 TexCoord: TEXCOORD0;
    float  TexIndex: TexIndex; // From the instance buffer not the vertex buffer
}

然后将此值传递给像素着色器

struct PS
{
    float4 Positon: SV_POSITION;
    float3 TexCoord: TEXCOORD0;
}

..

vout.TexCoord = float3(vin.TexCoord, vin.TexIndex);