Добрый день! Работаю с существующим не игровым 3D-движком на D3D11. Оптимизирую работу с картой глубины, которая сейчас строится в отдельном проходе. Хочу прояснить ряд моментов, но обо всём по порядку.
Требования:
1. Чтение 32-битной глубины из CPU (для определения мировых координат курсора).
2. Наличие трафарета.
3. Максимально быстрая отрисовка (т.е. избавиться от второго прохода).
4. Визуализация текстуры глубины и трафарета (больше для отладки и из любопытства).
Вижу два пути решения: 1) использовать стандартную DepthStencil-текстуру, 2) создавать дополнительную RenderTarget-текстуру с модификацией всех шейдеров. В первом случае вижу экономию ресурсов, во втором — больший контроль над ситуацией.
Реализовал первый вариант, остались вопросы:
1. Влияет ли DXGI-формат текстуры на содержимое при копировании? Ожидал, что в DS-буфере будет логарифмическая глубина, но достаю привычные линейные значения. Как это возможно?
2. При визуализации на видеокарте рисуется только красный канал с глубиной, причём больше похоже на логарифм (много красного). При экспорте в BMP с помощью WIC-кодека получаю красно-зелёное полосатое изображение (градиенты от красного к зелёному). Трафарет не рисуется нигде. В идеале хочу картинку, где есть и глубина, и трафарет, и чтобы она выглядела одинаково в BMP и на экране. Как это сделать правильно?
3. Какие ещё нюансы и подводные камни есть в этих вариантах? Какой вариант предпочтительнее в перспективе (планируются PSSM-тени, буфер для выделения объектов)?
Весь код привести не могу, ключевые моменты:
— DS-текстура создаётся в формате DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS;
— включается в конвейер как DXGI_FORMAT_D32_FLOAT_S8X24_UINT;
— копируется в ОЗУ как DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS (пробовал также DXGI_FORMAT_R32G8X24_TYPELESS — в этом формате не лезет обратно в видеокарту);
— кодируется в BMP как GUID_WICPixelFormat64bppRGBA -> GUID_WICPixelFormat32bppBGRA.
Заранее спасибо за любые подсказки!
VsM
> Ожидал, что в DS-буфере будет логарифмическая глубина
С чего бы вдруг ей там оказаться?
VsM
> Ожидал, что в DS-буфере будет логарифмическая глубина, но достаю привычные
> линейные значения. Как это возможно?
а какие значения привычные?
VsM
В Depth buffer хранится в первом приближении 1/Z (а точнее некоторая дробно-линейная функция, подгоняющая диапазон zn-zf к диапазону целых чисел 32 бит), потому что 1/Z линейно меняется в пространстве экрана
prowkan
> С чего бы вдруг ей там оказаться?
innuendo
> а какие значения привычные?
Окей, попробую яснее изложить свои мысли.
Что было в старой реализации: была универсальная функция, которая на выходе формировала карту (текстуру) глубины/видимости/теней, под условным названием CreateDepthMap(W,H). Работала функция очень просто: вызывала отрисовку всей сцены в стандартный RenderTarget, со стандартным DepthStencil, с 2 отличиями от обычной отрисовки:
1) шейдер глобально для всех объектов подменялся на специальный (условно Depth.hlsl), который для каждого пикселя записывал значение:
SV_TARGET = pos.z / pos.w, где pos = model_pos * MVP
Дальше меня сбила с толку формула (2) для вычисления Fdepth из статьи: https://habr.com/ru/post/342610/ — ведь это мало похоже на формулу из "моей" старой реализации, и больше похоже на то о чём написал Aslan. Тем не менее, я как и раньше умножаю полученную экранную точку (X,Y,Depth) на матрицу MVP^-1 (с точностью до транспонирования матриц), и получаю правильную модельную точку (?!) — это момент, оставшийся для меня непонятным.
Но практических вопроса осталось два:
1. Как отрендерить "красную" DepthStencil-текстуру более контрастно, чтобы видеть на ней очертания далёких объектов, а не только те, что перед носом.
2. Как отрендерить канал трафарета (например, зелёным цветом на той же картинке).
Причём, если рендеринг буфера в формате DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS даёт красную карту глубины, я ожидаю, что DXGI_FORMAT_X32_TYPELESS_G8X24_UINT даст зелёную карту тарафарета, но это не так.
P.S. Спасибо вам всем за отклик! Давно читаю этот форум, и даже ники уже знакомы :-)
VsM
> Как отрендерить "красную" DepthStencil-текстуру более контрастно, чтобы видеть
> на ней очертания далёких объектов, а не только те, что перед носом.
А зачем?
VsM
> 2) для RenderTarget устанавливался формат текстуры
> DXGI_FORMAT_R32G32B32A32_FLOAT.
Это же не depthStencil ?
> со стандартным DepthStencil, с 2 отличиями от обычной отрисовки:
когда делаешь в depthStencil, то color не нужен
есть графический отладчик - там всё видно
VsM
> для RenderTarget устанавливался формат текстуры DXGI_FORMAT_R32G32B32A32_FLOAT.
боль. всё упрётся в филлрейт. тем более если ты рендеришь только глубину, вот глубину и рендерь, color attachment'ы тебе тут вообще не нужны.
innuendo
> а какие значения привычные?
С этим разобрался. Привычные значения — это линейная функция от 1/z, что прямо следует из устройства Proj-матрицы и содержания w-компоненты. Поэтому всё как работало раньше, так и работает сейчас. На языке формул:
pos.z / pos.w = (az + b)/z = b*1/z + a
maks242
> А зачем?
Ок, вопрос отпал.
UPD: С форматами текстур и их конвертацией разобрался.
innuendo
> Это же не depthStencil ?
Suslik
> боль. всё упрётся в филлрейт. тем более если ты рендеришь только глубину, вот
> глубину и рендерь, color attachment'ы тебе тут вообще не нужны.
Такова старая реализация: фактически построение Z-буфера выполняется 3 раза за кадр (1 раз в RT и 2 раза в DS). Это именно то, от чего я избавился в новой реализации :-)
innuendo
> есть графический отладчик - там всё видно
Типа RenderDoc? Использую его тоже, хотя это менее удобно, чем встроенные инструменты.
———
Далее новые для меня факты, которые пока не затрагивал:
1. Вычитал, что в случае с FLOAT получается дополнительное уплотнение вблизи Znear, из-за чего падает точность везде, кроме как вблизи.
2. Для больших пространств рекомендуется Float-буфер + Reversed Depth. Второе заключается в небольшой модификации матрицы.
Но тут небольшой офтоп. В моём случае матрица изначально имеет странный вид:
m33 = -f/(f-n) вместо f/(f-n) m34 = -1 вместо 1
После перехода на новую реализацию возникли вопросы по антиалиасингу:
1. Верно ли я понял, что SampleDesc должен быть одинаковым для всех RenderTarget- и DepthStencil-текстур внутри MRT, а для всех представлений нужно выставить ViewDimension = ..._DIMENSION_TEXTURE2DMS?
2. Как скопировать MS-текстуру глубины, если целевой формат DXGI_FORMAT_R24_UNORM_X8_TYPELESS не поддерживает MSAA (возникает ошибка несоответствия типов при вызове CopySubresourceRegion)?
VsM
> 1. Верно ли я понял, что SampleDesc должен быть одинаковым для всех
> RenderTarget- и DepthStencil-текстур внутри MRT, а для всех представлений нужно
> выставить ViewDimension = ..._DIMENSION_TEXTURE2DMS?
конечно
VsM
> 2. Как скопировать MS-текстуру глубины, если целевой формат
> DXGI_FORMAT_R24_UNORM_X8_TYPELESS не поддерживает MSAA (возникает ошибка
> несоответствия типов при вызове CopySubresourceRegion)?
нужно использовать операции вроде ResolveSubresource(), либо пиши в шейдере вручную усреднённые значения субпикселей.
Suslik
> нужно использовать операции вроде ResolveSubresource(), либо пиши в шейдере
> вручную усреднённые значения субпикселей.
Спасибо, буду изучать! Наивно полагал, что MSAA не изменяет количество пикселей, и копирование пиксель-в-пиксель не должно представлять проблемы.
P.S. Сделал ResolveSubresource, указал Typed-формат (D24_UNORM_S8_UINT) по документации. Пока жёстко ругается:
1) на указанный формат ("is never able to resolve MS resources");
2) на флаг BIND_DEPTH_STENCIL у исходной текстуры.
VsM
код dxsdk
innuendo
> код dxsdk
Можно чуть точнее? Знаю что в SDK 2010 есть пример AntiAlias 2004 года, написанный на DirectX 9, о нём ли речь? Или смотреть в сторону TextureConverter?
Тема в архиве.