Самый быстрый способ визуализации линий с АА, различной толщины в DirectX

14

Поэтому я занимаюсь разработкой DirectX, точнее используя SharpDX под .NET (но применимы решения DirectX / C ++ API). Я ищу самый быстрый способ визуализации линий в ортогональной проекции (например, имитация 2D-рисования линий для научных приложений) с использованием DirectX.

Снимок экрана с видами графиков, которые я пытаюсь сделать, выглядит следующим образом: введите описание изображения здесь

Подобные графики нередко имеют линии с миллионами сегментов, различной толщины, с сглаживанием или без него в каждой строке (или вкл / выкл полноэкранного АА). Мне нужно очень часто обновлять вершины для строк (например, 20 раз / секунду) и максимально разгрузить графический процессор.

До сих пор я пытался:

  1. Программное обеспечение рендеринга, например, GDI + на самом деле не плохая производительность, но, очевидно, сильно загружает процессор
  2. Direct2D API - медленнее, чем GDI, особенно с включенным сглаживанием
  3. Direct3D10 использует этот метод для эмуляции AA, используя цвета вершин и тесселяцию на стороне процессора. Также медленный (я профилировал его, и 80% времени уходит на вычисление позиций вершин)

Для третьего метода я использую Vertex Buffers для отправки треугольной полосы в GPU и обновляю каждые 200 мсек новыми вершинами. Я получаю частоту обновления около 5FPS для 100 000 сегментов линии. Мне нужны миллионы в идеале!

Теперь я думаю, что самым быстрым способом было бы сделать тесселяцию на GPU, например, в Geometry Shader. Я мог бы отправить вершины в виде списка строк или упаковать в текстуру и распаковать в Geometry Shader для создания четырехугольников. Или просто отправьте необработанные точки в пиксельный шейдер и полностью реализуйте рисование линии Брезенхема в пиксельном шейдере. Мой HLSL ржавый, модель шейдера 2 с 2006 года, так что я не знаю о сумасшедших вещах, которые могут делать современные графические процессоры.

Итак, вопрос: - кто-нибудь делал это раньше, и есть ли у вас какие-либо предложения, чтобы попробовать? - Есть ли у вас какие-либо предложения по улучшению производительности за счет быстрого обновления геометрии (например, новый список вершин каждые 20 мс)?

ОБНОВЛЕНИЕ 21 января

С тех пор я реализовал метод (3) выше, используя шейдеры Geometry, используя LineStrip и Dynamic Vertex Buffers. Теперь я получаю 100FPS в 100k очков и 10FPS в 1000000 очков. Это огромное улучшение, но теперь я ограничен по частоте заполнения и вычислениям, поэтому я подумал о других методах / идеях.

  • Как насчет аппаратной реализации геометрии линейного сегмента?
  • А как насчет Sprite Batch?
  • А как насчет других (Pixel Shader) ориентированных методов?
  • Могу ли я эффективно отбраковывать на GPU или CPU?

Ваши комментарии и предложения очень ценятся!

Доктор АБТ
источник
1
А как насчет нативного AA в Direct3D?
API-Beast
5
Можете ли вы показать несколько примеров кривых, которые вы хотите построить? Вы упомянули миллион вершин, но ваш экран, вероятно, имеет не более миллиона пикселей , это количество действительно нужно? Мне кажется, что вам не понадобится такая полная плотность данных везде. Вы рассматривали LODs?
Сэм Хоцевар
1
Второй подход, который вы связали, кажется хорошим, вы используете динамический буфер вершин, который вы обновляете, или каждый раз создаете новый?
1
Вы, вероятно, должны использовать динамический буфер вершин (не так много работы, просто скажите, что ваш буфер вершин будет динамическим, отобразите буфер, скопируйте ваши данные, удалите карту), но если ваше узкое место - это генерация вершин, то это, вероятно, выиграло сейчас мало чем поможет. Не думайте, что есть что-то безумное в использовании GS для снижения нагрузки на процессор
1
Если количество вершин окажется слишком большим для графического процессора, вы все равно можете попробовать уменьшить дискретизацию входных данных - вам не нужны сотни миллионов линейных сегментов для одной кривой, когда ваш экран будет иметь ширину в несколько тысяч пикселей. в большинстве.

Ответы:

16

Если вы собираетесь визуализировать Y = f(X)только графики, я предлагаю попробовать следующий метод.

Данные кривой передаются как данные текстуры , что делает их постоянными и позволяет, например, частичное обновление glTexSubImage2D. Если вам нужна прокрутка, вы можете даже использовать циклический буфер и обновлять только несколько значений на кадр. Каждая кривая отображается как полноэкранный квад, и вся работа выполняется пиксельным шейдером.

Содержимое однокомпонентной текстуры может выглядеть так:

+----+----+----+----+
| 12 | 10 |  5 | .. | values for curve #1
+----+----+----+----+
| 55 | 83 | 87 | .. | values for curve #2
+----+----+----+----+

Работа пиксельного шейдера заключается в следующем:

  • найти координату X текущего фрагмента в пространстве набора данных
  • взять например 4 ближайшие точки данных, которые имеют данные; Например , если значение Х 41.3было бы выбрать 40, 41, 42и 43.
  • запросить текстуру для значений 4 Y (убедитесь, что сэмплер не выполняет никакой интерполяции)
  • конвертировать X,Yпары в пространство экрана
  • вычислить расстояние от текущего фрагмента до каждого из трех сегментов и четырех точек
  • использовать расстояние в качестве альфа-значения для текущего фрагмента

Вы можете заменить 4 большими значениями в зависимости от потенциального уровня масштабирования.

Я написал очень быстрый и грязный шейдер GLSL, реализующий эту функцию . Я могу добавить версию HLSL позже, но вы сможете конвертировать ее без особых усилий. Результат можно увидеть ниже, с различными размерами линий и плотностями данных:

кривые

Одно очевидное преимущество заключается в том, что объем передаваемых данных очень низок, а количество вызовов - только один.

Сэм Хоцевар
источник
Это довольно крутая техника - я делал GPGPU раньше, так что я знаю, что нужно упаковывать текстуры с данными, но я не думал об этом. Какой самый большой размер (например, самый большой набор данных, который вы можете загрузить?). Я собираюсь скачать ваш код, если вы не возражаете, и поиграйте с ним - я хочу увидеть производительность.
Доктор АБТ
@ Dr.ABT Размер набора данных ограничен только максимальным размером текстуры. Я не понимаю, почему миллионы точек не сработают при правильном расположении данных. Обратите внимание, что мой код, конечно, не является качественным, но вы можете связаться со мной в частном порядке, если возникнут какие-либо проблемы.
Сэм Хоцевар
Приветствия @SamHocevar - я попробую. Прошло много времени с тех пор, как я собрал пример для OpenGL! :-)
Доктор АБТ
Отмечая как ответ, как будто я не использую текстурную загрузку, вы представили реальный пример OpenGL, который может рисовать линии на пиксельном шейдере и направлять нас в правильном направлении !! :)
Доктор ABT
4

Там была глава Gem Gem для рендеринга сглаженных линий: Fast Prefiltered Lines . Основная идея состоит в том, чтобы визуализировать каждый линейный сегмент в виде квадрата и рассчитать для каждого пикселя гауссову функцию расстояния центра пикселя от линейного сегмента.

Это означает рисование каждого линейного сегмента на графике в виде отдельного четырехугольника, но в D3D11 вы, безусловно, могли бы использовать геометрический шейдер и / или создание экземпляров для создания четырехугольников, уменьшая объем данных, передаваемых в графический процессор, до просто точек данных. самих себя. Я бы, вероятно, установил точки данных как StructuredBuffer для чтения вершинным / геометрическим шейдером, а затем сделал бы вызов отрисовки с указанием количества сегментов, которые нужно нарисовать. Там не будет никаких реальных буферов вершин; вершинный шейдер просто использовал бы SV_VertexID или SV_InstanceID, чтобы определить, на какие точки данных смотреть.

Натан Рид
источник
Эй, спасибо - да, я знаю об этой статье, к сожалению, нет исходного кода :-( Предполагается, что GeoShader + FragShader является победителем для этого типа работы. Эффективное получение данных в GPU будет сложная часть!
Доктор ABT
Привет @NathanReed, возвращаясь к этому - если я использовал Geometry Shader или инстансинг для рисования четырехугольников, то Point1 / Point2 являются противоположными вершинами четырехугольника, как в Pixel Shader я могу вычислить расстояние до линии между Pt1 и Pt2? Обладает ли пиксельный шейдер знаниями о входной геометрии?
Доктор ABT
@ Dr.ABT Параметры математической линии должны быть отправлены в пиксельный шейдер через интерполяторы. Пиксельный шейдер не имеет прямого доступа к геометрии.
Натан Рид
Я вижу, это можно сделать, отправив выходные данные из GeometryShader или экземпляры? Для дополнительного кредита (если вам интересно) я добавил вопрос здесь: gamedev.stackexchange.com/questions/47831/…
Dr. ABT
@ Dr.ABT Это точно так же, как координаты текстуры, нормальные векторы и все подобные вещи обычно отправляются в пиксельный шейдер - путем записи выходных данных вершинного / геометрического шейдера, которые интерполируются и вводятся в пиксельный шейдер.
Натан Рид
0

@ Dr.ABT - Извинения за это вопрос, а не ответ. Я не нашел способа задать это в ответе Сэма Хочевара выше.

Не могли бы вы поделиться более подробной информацией о том, как вы наконец реализовали линии для своего графика? У меня такая же потребность в приложении WPF. Заранее благодарим за любые детали или код, которым вы можете поделиться на этом.

ErcGeek
источник
поскольку вы знаете, что это не ответ, отредактируйте его и поместите в качестве комментария
MephistonX
Я пытался это сделать, но в исходном сообщении нет кнопки «Добавить комментарий».
ErcGeek
Привет ErcGeek, извините, я не могу поделиться окончательным решением, так как эта информация является собственностью моей компании. Хотя приведенные выше примеры и предложения ставят нас на правильный путь. Всего наилучшего!
Доктор АБТ
Вы не можете оставлять комментарии, пока не достигнете определенного уровня репутации. И все эти отрицательные голоса не предоставят вам такую ​​возможность.
Матиас Ликкегор Лоренцен
Парень не должен быть оштрафован за то, что он хотел знать конечный результат и просил его единственным доступным ему способом. Утвержден ответ.
Дэвид Чинг