4

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

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

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

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

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

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

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

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

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

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

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

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

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

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

4

2 回答 2

4

有几种方法(像往常一样)可以按照您的描述进行操作。

请注意,使用

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

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

请注意,此纹理也可以保存为 dds 格式(这样可以节省您每次启动程序时重新生成的时间)。

查找您的纹理就像:

Texture2DArray myarray;

在像素着色器中:

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

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

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

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

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

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

做一个预制的 Quad 并在 Per Instance VertexBuffer 中传递上述数据也是可能的,但如果你有大量的 sprite,它很容易炸毁你的显卡(高我的意思是超过 300 万个粒子,即按照现在的标准来说并不多,但如果你的精灵不到一百万,你会完全没问题的;)

于 2013-07-11T17:02:10.427 回答
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);
于 2013-07-09T11:42:58.063 回答