В настоящее время я экспериментирую с различными способами отображения 2D-спрайтов в DirectX 10. Я начал с использования 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, то есть ли способ для меня иметь больше контроля над тем, как он отображается?
В качестве примечания я также рассмотрел использование геометрического шейдера для создания фактического четырехугольника, поэтому мне нужно было только передать серию точек вместо того, чтобы управлять буфером вершины и экземпляра. Опять же, хотя, если нет способа контролировать, какие текстуры применяются к сгенерированным квадроциклам, я вернусь только к группированию спрайтов по текстурам.
Есть несколько способов (как обычно) сделать то, что вы описываете.
Обратите внимание, что с помощью
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 спрайтами (топология должна быть настроена на список точек).
Передача данных из вершинного шейдера в геометрический шейдер.
Разверните свою точку в quad в геометрическом шейдере, вы можете найти пример, который ParticlesGS в Microsoft SDK делает, это немного излишне для вашего случая, так как вам нужна только часть рендеринга, а не анимация. Если вам нужен очищенный код, дайте мне знать, я быстро сделаю пример, совместимый с dx10 (в моем случае я использую StructuredBuffers вместо VertexBuffer)
Выполнение готового Quad и передача вышеуказанных данных в Per Instance VertexBuffer также возможна, но если у вас большое количество спрайтов, это легко взорвет вашу видеокарту (под высоким я имею в виду что-то вроде более 3 миллионов частиц, что не очень по нынешним меркам, но если у вас меньше полумиллиона спрайтов, у вас все будет в порядке;)
Включите индекс текстуры в буфер экземпляра и используйте его, чтобы выбрать правильную текстуру из массива текстуры для экземпляра:
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);