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

What is the most efficient method of rendering sprites in DirectX 10?

本文关键字:有效 方法 是什么 精灵 DirectX      更新时间:2023-10-16

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

我一开始很简单:我创建了一个由4个顶点组成的单个顶点缓冲区,在绘制精灵之前应用过一次。然后,我循环浏览我的精灵,设置要传递到着色器的适当属性,并为每个精灵进行绘制调用,如下所示:d3dDevice->Draw( 4, 0);。虽然它有效,但对每个精灵的draw调用都让我很头疼,所以我寻找一个更有效的方法。

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

我的想法是为每个实例传递一个纹理索引,然后可以使用该索引对像素着色器中阵列中的适当纹理进行采样。然而,在搜索了更多之后,我找不到一个如何做到这一点的例子(发现许多事情表明,如果不转到DirectX 11,就无法做到这一步)。

也就是说,在DirectX 10中通过对象实例化成功渲染精灵的唯一方法是基于纹理批量渲染它们吗?因此,例如,如果我的场景由100个具有20个不同纹理的精灵组成(每个纹理由5个精灵引用),则需要20个单独的绘制调用才能显示场景,并且我一次只发送5个精灵。

最后,我不知所措。我做了很多搜索,似乎得到了相互矛盾的信息。例如,在这篇文章的第6段中,它指出:

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

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

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

然而,我似乎找不到一个具体的例子来说明如何设置着色器以使用每实例纹理索引访问纹理数组。

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

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

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

顺便说一句,我还研究过使用几何着色器来创建实际的四边形,所以我只需要传入一系列点,而不需要管理顶点和实例缓冲区。不过,再次强调,除非有一种方法可以控制哪些纹理应用于生成的四边形,否则我将回到只按纹理批处理精灵。

有几种方法(像往常一样)可以实现您所描述的。

请注意,使用

Texture2D textures[10];

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

因此,您需要的是创建一个Texture2DArray。这有点像体积纹理,但z分量是一个完整的数字,上面没有采样

不过,您需要生成此纹理数组。简单的方法是在启动时执行一个全屏四元绘制调用,将每个纹理绘制到阵列的切片中(可以为特定切片创建RenderTargetView)。着色器在这里将是一个简单的通道。

要创建纹理阵列(代码在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
 };

将其绑定到passthrough着色器,将所需纹理设置为输入,将切片设置为输出,然后绘制全屏四边形(或全屏三角形)。

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

查找你的纹理就像:

Texture2DArray myarray;

像素内着色器:

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

现在关于渲染精灵,您还可以选择GS展开。

因此,您可以创建一个顶点缓冲区,该缓冲区只包含位置/大小/纹理索引/任何其他需要的内容,每个精灵一个顶点。

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

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

将你的点扩展到几何体着色器中的四边形,你可以找到一个例子,即Microsoft SDK中的ParticlesGS,这对你的情况来说有点过头了,因为你只需要渲染部分,而不需要动画。如果你需要一些干净的代码,请告诉我,我会很快制作一个与dx10兼容的示例(在我的情况下,我使用StructuredBuffer而不是VertexBuffer)

执行预先制作的Quad并在Per Instance VertexBuffer中传递上述数据也是可能的,但是,如果你有大量的精灵,它会很容易炸毁你的显卡(我所说的"高"是指超过300万个粒子,按照现在的标准,这并不算多,但如果你有50万个精灵,你就完全可以了;)

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

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);