Войти
ПрограммированиеФорумГрафика

Мипмап гернератор для cubemap'а (DX12)

#0
19:02, 8 мая 2019

Что-то у меня не получается генерировать мипы для кубемапа. Для обычных текстур у меня все давно сделано и работает, но с массивом почему-то не идет. Для кубемапов я слегка модифицировал шейдер, поставив вместо Texture2D - Texture2DArray  и добавил цикл по поверхностям.
Шейдер вроде примитивный:

Texture2DArray<float4> SrcTexture : register(t0);
RWTexture2DArray<float4> DstTexture : register(u0);
SamplerState BilinearClamp : register(s0);

cbuffer CB : register(b0)
{
  float4 TexelSize;
  float ArraySize;
}

[numthreads( 16, 16, 1 )]
void CS(uint3 DTid : SV_DispatchThreadID)
{
  float3 coord = float3(TexelSize.xy * (DTid.xy + 0.5), 0);

  for(int i=0; i<ArraySize; i++)
  {
    coord.z = i;
    float4 color = SrcTexture.SampleLevel(BilinearClamp, coord, 0);

    uint3 pos = uint3(DTid.xy, i);
    DstTexture[pos] = color;
  }
}
Но заполняется только первый элемент массива, т.е. я вижу текстуру только на левом краю модели. Остальные части глухо черные.
Может кто-то знает что в супе не хватает?


#1
0:10, 9 мая 2019

san
> Может кто-то знает что в супе не хватает?
Неправильные view для DstTexture и SrcTexture?
А еще я бы не грузил сеплеры, а просто брал бы нужные пиксели через SrcTexture.Load

#2
(Правка: 0:36) 0:31, 9 мая 2019

MrShoor
> Неправильные view для DstTexture и SrcTexture?
Похоже проблема зарыта глубже. Я вообще убрал генерацию мипмапов и оказалось, что в случае текстуры с мипами тупо не копируются сабресурсы.
У меня два режима, один с мультисемплингом, там стоит ResolveSubresource, второй без мултисемплинга, там просто CopyTextureRegion. Источник без мипов, приемник с ними. Так первая ветка не ругается, просто ничего кроме первого сабресурса из 6 не копируется, а вторая валится при выполнении с диагностикой, мол нет места, показывая размер ресурса в 2 раза меньше, чем у текстуры, т.е. это явно второй мип. Вроде я не вижу в доке ограничения на наличие мипов при копировании региона.
Пока в задумчивости...

#3
2:23, 9 мая 2019

san
> Может кто-то знает что в супе не хватает?

дело было не в бобине?

#4
(Правка: 2:56) 2:55, 9 мая 2019

Судя по всему так лихо заполнять кубемапы как я делал (просто копируя туда информацию по ResolveSubresource/CopyTextureRegion) в случае мип-уровней нельзя. Для этого надо будет шейдер задействовать, заодним он и мипы будет заполнять.

Вот нашел весьма полезный туториал : https://www.3dgep.com/learning-directx-12-4/

Похоже он только что вышел, раньше я его не видел. Там в тексте еще не дошли до кубемапов, но в исходниках уже есть функция PanoToCubemap, которая делает почти то, что мне нужно. Теперь вместо 20 строк это потянет за собой три экрана, но это уж как в DX12 положено. :)

Вообще рекомендую слазить вот сюда: https://github.com/jpvanoosten/LearningDirectX12/tree/v0.0.4 .
Там очень неплохая библиотека, жалко что ее не было когда я начинал писать программу - я бы мог много времени сэкономить. Написано довольно просто, с MiniEngin'ом не сравнить.

#5
(Правка: 7:35) 7:35, 9 мая 2019

san
генерить мипмапы вручную имеет смысл только в случае, когда они динамически меняются в рантайме из всяких reflection probe'ов. если же они статические, то нет никаких причин не использовать специальные инструменты, которые это всё равно сделают лучше тебя вроде cmft.

#6
19:31, 9 мая 2019

Они как раз генерятся динамически. Это отражения на стеклянной посуде и прочих блестящих предметах. Обновляется текстура на двигающихся предметах не совсем в реалтайм, а только однократно, когда предмет успокоится. В реалтайм было бы лучше, но не у все карты потянут сделать это за 1-2 мс.

#7
16:07, 15 мая 2019

ХМ. А в GL эти мипы драйвер сам генерил, ну для 2D-текстур точно.

#8
16:39, 15 мая 2019

g-cont

ты не в теме - новые времена, новые апи, новые нравы

#9
17:27, 15 мая 2019

g-cont
А в GL 1.x даже шейдеров не было, драйвер сам все делал

#10
(Правка: 18:53) 18:30, 15 мая 2019

Конец истории: ошибка была не в шейдере а в заполнении исходной информации кубемапа.
Я имею ввиду что прежде чем генерировать мипы мне надо было заполнить основные текстуры (с максимальным разрешением).
А там имеется тонкость - при работе с массовами имеющими мипы, копировать поверхности надо так:

    D3D12_TEXTURE_COPY_LOCATION src = {}, dest = {};
    src.pResource = src_t->GetD3DResource();
    dest.pResource = dest_t->Get();

    dest.SubresourceIndex = s*desc.MipLevels;  // надо умножать момер элемента массива (s) на количество мипов.

    setResourceBarrier(CommandList, src.pResource, D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_COPY_SOURCE);
    setResourceBarrier(CommandList, dest.pResource, D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST);

    CommandList->CopyTextureRegion(&dest, 0, 0, 0, &src, NULL);

    setResourceBarrier(CommandList, src.pResource, D3D12_RESOURCE_STATE_COPY_SOURCE, D3D12_RESOURCE_STATE_COMMON);
    setResourceBarrier(CommandList, dest.pResource, D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_COMMON);
Другими словами элементы массива расположены не подряд, а между ними сидят все мипы, которые надо пропускать.
Когда до меня это дошло, дальше все оказалось просто.
Если кому-то интересно, то вот шейдер который генерит мипмапы для массивов или кубемапов:

Texture2DArray<float4> SrcTexture : register(t0);
RWTexture2DArray<float4> DstTexture : register(u0);
SamplerState BilinearClamp : register(s0);

cbuffer CB : register(b0)
{
  float2 TexelSize;   // 1.0 / destination dimension
  float ArraySize;
}

[numthreads( 16, 16, 1 )]
void CS(uint3 DTid : SV_DispatchThreadID)
{
  float3 coord = float3(TexelSize.xy * (DTid.xy + 0.5), 0);

  for(int i = 0; i < ArraySize; i++)
  {
    coord.z = i;
    uint3 pos = uint3(DTid.xy, i);
    DstTexture[pos] = SrcTexture.SampleLevel(BilinearClamp, coord, 0);
  }
}
а вот так он вызывается:
    for (int TopMip = 0; TopMip < NumMips - 1; TopMip++)
    {
      //Get mipmap dimensions
      uint32_t dstWidth = max((width >> (TopMip + 1)), 1);
      uint32_t dstHeight = max(height >> (TopMip + 1), 1);

      //Create shader resource view for the source texture in the descriptor heap
      srcTextureSRVDesc.Format = desc.Format;
      srcTextureSRVDesc.Texture2DArray.MipLevels = 1;
      srcTextureSRVDesc.Texture2DArray.MostDetailedMip = TopMip;
      srcTextureSRVDesc.Texture2DArray.ArraySize = desc.DepthOrArraySize;
      m_context->m_Device->CreateShaderResourceView(m_Texture, &srcTextureSRVDesc, currentCPUHandle);
      currentCPUHandle.Offset(1, descriptorSize);

      //Create unordered access view for the destination texture in the descriptor heap
      destTextureUAVDesc.Format = desc.Format;
      destTextureUAVDesc.Texture2DArray.MipSlice = TopMip + 1;
      destTextureUAVDesc.Texture2DArray.ArraySize = desc.DepthOrArraySize;
      m_context->m_Device->CreateUnorderedAccessView(m_Texture, nullptr, &destTextureUAVDesc, currentCPUHandle);
      currentCPUHandle.Offset(1, descriptorSize);

      //Pass the destination texture pixel size to the shader as constants
      Command_list->SetComputeRoot32BitConstant(0, DWParam(1.0f / dstWidth).Uint, 0);
      Command_list->SetComputeRoot32BitConstant(0, DWParam(1.0f / dstHeight).Uint, 1);
      Command_list->SetComputeRoot32BitConstant(0, DWParam((float)desc.DepthOrArraySize).Uint, 2);

      Command_list->SetComputeRootDescriptorTable(1, currentGPUHandle);
      currentGPUHandle.Offset(1, descriptorSize);
      Command_list->SetComputeRootDescriptorTable(2, currentGPUHandle);
      currentGPUHandle.Offset(1, descriptorSize);

      Command_list->Dispatch(max(dstWidth / 8, 1u), max(dstHeight / 8, 1u), 1);

      Command_list->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::UAV(m_Texture));
    }
  };

Работает очень быстро, потому я не стал заморачиваться с одновременным заполнением четырех мипов, как это делает микрософт в своем примере. Так проще и понятнее.

ПрограммированиеФорумГрафика