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

Центральный GUI тред

Страницы: 1 2 3 4 Следующая »
#0
(Правка: 9 ноя. 2019, 13:09) 0:11, 17 окт. 2019

Обычно темы про гуй касаются конкретных вопросов реализации, а общего обзора методик нет, будто бы это само собой разумеющееся.
Попытаюсь здесь систематизировать информацию о гуе, заодно сам разберусь по ходу дела.

Архитектура


Общий принцип — элементы интерфейса состоят из квадов, на которые натягиваются текстуры.
Способы от простого к сложному:
1. Без прозрачности, все элементы квадратные, рисуем от верхнего к нижнему без блендинга с тестом глубины.
2. Однобитовая прозрачность, рисуем от верхнего к нижнему без блендинга с тестом глубины, дискардим фрагменты в которых альфа!=1.
3. При необходимости использовать антиалиасинг или полупрозрачность элементов, рисуем от нижнего к верхнему с блендингом без теста глубины.
В первых двух способах можно даже не сортировать, а вот для корректной полупрозрачности сортировка обязательна.
Вместо сортировки на ЦПУ можно сортировать на ГПУ с применением алгоритмов OIT(порядко-независимая прозрачность)

Текстуры можно грузить из внешнего файла или генерировать самому(например для миникарт).
Или же вместо текстуры может быть рендеринг текста.

Рендеринг текста


Детальный обзор современных методов рендеринга текста: Digital Typography Rendering
В играх два основных метода — просто рендеринг текстур с глифами и SDF.

1. Рендеринг текстур с глифами


Можно отрендерить заранее и сложить глифы в текстурный атлас, либо генерировать на ходу, например через freetype.
Утилита для генерации текстурных атласов от RPG: UBFG + github.

При генерации на ходу надо как-то организовать хранение отрендереных глифов. Хранить каждый глиф в отдельной текстуре неэффективно.
Есть три эффективных решения:
1. Текстурный атлас, в который пакуются глифы по следующему алгоритму: http://blackpawn.com/texts/lightmaps/ + перевод
Более подробно: https://codeincomplete.com/posts/bin-packing/
2. Texture Array, где каждый слой — отдельный глиф.
3. Bindless Textures.

Бонус от Frankinshtein: Постпроцессинг шрифтов на лету | Библиотека и редактор Font Effects.

2. Signed Distance Field


Заранее готовится текстура Signed Distance Field от необходимых глифов. Текстура натягивается на квады и рендерится специальным шейдером, на выходе получается глиф. Особенность текстуры такова, что её можно масштабировать как угодно, всегда получая четкий текст.
Ходют слухи, можно даже считать качественный SDF в реалтайме: Генерация SDF символов на GPU от MrShoor

Статьи с описанием алгоритма и примерами:
Improved Alpha-Tested Magnification for Vector Textures and Special Effects
Рендеринг UTF-8 текста с помощью SDF шрифта
Signed Distance Field или как сделать из растра вектор
CPU vs GPU. Distance field
Drawing Text with Signed Distance Fields in Mapbox GL + Онлайн демо
Font Rendering is Getting Interesting
Playing around with distance field font rendering

3. Сравнение двух методов

Особенности классического метода:
+ при генерации под нужный размер идеальное качество с возможностью субпиксельного сглаживания, хинтинга
+ простота реализации — надо всего-лишь натянуть текстуры на квады
+ с фритайпом, простота использования — закинул файл со шрифтом в папку игры и работает без всякой подготовки
- малейшее масштабирование, поворот, проекция, и текст превращается в размытое говно. проблему с масштабированием можно побороть, рендеря глифы заново при изменении размера, но поворот и проекцию побороть невозможно. большое разрешение экрана вместе с большим размером текста будет жрать память.
- для теней, обводки и прочих эффектов надо много дополнительных телодвижений
- с фритайпом — нагрузка на ЦПУ при рендере новых глифов в текстуру

Особенности SDF:
+ векторный и универсальный — текст всегда четкий при любых поворотах, масштабированиях, проекциях и тд.
+ побочный эффект — бесплатное однопроходное размытие, тени и обводка текста
- в целом алгоритм сложнее предыдущего в реализации
- требует прекомпиляции. для хорошего качества нужно дополнительно запариваться с SDF генератором, прекомпиляция занимает значительное время
- проблемы с острыми гранями букв, особенно заметно при большом размере текста. проблема решается усложнением алгоритма до многоканального
- нет субпиксельного сглаживания и хинтинга. хотя про первое где-то встречалась информация что таки есть способ

В общем панацеи нет, каждый метод имеет свою область применения.
Область безусловного доминирования SDF — текст для внутриигровых объектов(когда можно подойти вплотную к объекту и размер текста будет 5000х5000) и мобилки(где разрешения экранов могут отличаться в разы).
Разница наглядно:

+ Классика
+ SDF

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

Инпут


Молодой ещё, не вскрывал эту тему.

#1
1:01, 17 окт. 2019

Джек Аллигатор
> В первых двух способах можно даже не сортировать, а вот для корректной
> полупрозрачности сортировка обязательна.
> Вместо сортировки на ЦПУ можно сортировать на ГПУ с применением алгоритмов
> OIT(порядко-независимая прозрачность)
Не нужна там сортировка. У тебя изначально есть иерархия, какой элемент на каком лежит.

struct Control {
  std::vector<Control> childs;
}
Обходишь рекурсинвно от парента к чайлдам - и сразу получаешь отсортированный порядок от нижних элементов к верхним.

> Можно отрендерить заранее и сложить глифы в текстурный атлас
Как по мне, когда речь идет о пользовательском UI - то это такая себе идея. Гораздо правильнее будет в рантайме это генерировать. Вариант с генерацией атласа при билде худо бедно сработает только для внутриигрового текста. И то не факт, что потом не возникнет проблем, когда вдруг понадобится локализация.
Ну и поскольку в рантайме генерить глифы занимает единицы (даже не десятки) миллисекунд - то я вообще не вижу смысла делать атласы заранее.

> 1. Текстурный атлас, в который пакуются глифы по следующему алгоритму:
> http://blackpawn.com/texts/lightmaps/
Оптимальный вариант. Заводится абслютно везде (хоть в WebGL 1). Недостатков не имеет.

#2
1:35, 17 окт. 2019

ClearType-ом даже не пахнет.

#3
(Правка: 2:01) 1:50, 17 окт. 2019

MrShoor
> Обходишь рекурсинвно от парента к чайлдам - и сразу получаешь отсортированный
> порядок от нижних элементов к верхним.
Это работает если иерархия одна.
Что делать если независимых иерархий элементов несколько и они перекрывают друг друга?

+ Показать
#4
2:02, 17 окт. 2019

Джек Аллигатор
> https://www.slideshare.net/NicolasRougier1/siggraph-2018-digital-typography

В одном я согласен, сейчас это задача для GPU.

#5
2:04, 17 окт. 2019

Джек Аллигатор
> Что делать если независимых иерархий элементов несколько и они перекрывают друг
> друга?

Вычислять глубину каждого элемента (одинаковой глубины быть не должно).

#6
2:40, 17 окт. 2019

Джек Аллигатор
> Что делать если независимых иерархий элементов несколько и они перекрывают друг
> друга?
Если ты про sibling контролы, то:

struct Control {
  std::vector<Control> childs;
}
В childs векторе уже изначально порядок определен. Поэтому сортировать там ничего не надо.
Нужно показать окно поверх других окон - ставишь его в конец childs списка.
#7
(Правка: 3:00) 2:59, 17 окт. 2019

MrShoor, всё, стало понятно. Спасибо.

В общем планирую так делать:
1. Вообще все текстуры, включая глифы, залить как bindless.
2. Сделать один квад, позиция и размеры которого настраиваются в шейдере.
3. Отрисовать всё одним вызовом через инстансинг этого квада, передавая информацию обо всех элементах через SSBO.

#8
(Правка: 4:55) 4:46, 17 окт. 2019

игровой гуй сам вообще ничего не должен знать о том, как он рендерится и на каком железе, на что он рендерится и откуда берётся его инпут. его общение с внешним миром реализуется через input injection, а на выход он даёт только display list'ы, которые содержат примитивные команды для рендеринга.

все вопросы отрисовки делегируются рендеру, который проходит по display list'у и исполняет команды из него так, как ему кажется оптимальным для целевой платформы.

только таким образом можно сделать достаточно кроссплатформенный, гибкий и рендер-независимый гуй. на таких парадигмах работают современные гуи-библиотеки от imgui до рендера хрома.

все вопросы с паковкой текстур, формой элементов и прочая ерунда — это вторичные вопросы, являющиеся несущественными деталями реализации. с точки зрения пользователя библиотеки, гораздо важнее, например, реализуется она через immediate mode парадигму или через persistent mode? будет она stateless или stateful? предполагается ли её контролы сериализовать в data-driven представление, или требуется как можно удобнее расширять интерфейс через код? осуществляется вёрстка через стандартные форматы вроде html/css или через хардкод? на эти вопросы нет правильных и неправильных ответов, так как ответы будут различными в зависимости от планируемого применения. однако, именно эти вопросы следует задавать в первую очередь, так как от них зависит самый базовый дизайн.

#9
(Правка: 5:35) 5:31, 17 окт. 2019

Suslik, вот вроде всё понятно, но ничего не понятно.
Наверное, ты с таким выражением будешь читать этот пост:

+ Показать

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

Надо хоть с чего-то начать.
Попробуем к примеру провести аналогию с версткой сайта.
Описание элементов через html+css — это data-driven представление элементов, persistent mode и stateless?
Вот вообще не представляю, как можно представить интерфейс как-то иначе. После многих лет разработки сайтов записалось в подкорку — сначала описываешь элементы в файле разметки, затем получаешь к ним доступ по селектору и что-то с ними делаешь. Буду рад, если поможешь понять альтернативный путь.

Пока что нашел по теме только это видео:

+ Показать

И эту статью:

Immediate mode GUI немного отличается от классической методики программирования интерфейсов, которая называется retained mode GUI. ImGui виджеты создаются и рисуются в каждом кадре игрового цикла. Сами виджеты не хранят внутри себя своё состояние, либо хранят абсолютно минимальный необходимый минимум, который обычно скрыт от программиста.

В отличие от того же Qt, где для создания кнопки нужно создавать объект QPushButton, а затем связывать с ней какую-нибудь функцию-callback, вызываемую при нажатии, в ImGui всё делается гораздо проще. В коде достаточно написать:

if (ImGui::Button("Some Button")) {
    ... // код, вызываемый при нажатии кнопки
}

Данный код должен вызываться в каждой итерации игрового цикла, в которой эта кнопка должна быть доступна пользователю.

Изначально данный концепт может показаться странным и очень неэффективным, однако это всё работает так быстро в сравнении с остальным кодом, что в результате даже сложные интерфейсы не привносят сильных изменений в производительность игры.

https://habr.com/ru/post/335512/

#10
7:54, 17 окт. 2019

Джек Аллигатор
> if (ImGui::Button("Some Button")) {

А если фонты у кнопки другие? а если кнопка должна растягиваться вместе с формой? А если бекграунд кнопки другой?
Можно конечно для каждой кнопки держать стиль как в CSS, но это не совсем удобно если нужно всем управлять програмно.
Все же дельфийский стиль кода где видно какие объекты на форме более приемлемый:

class TMyForm : public TForm
{
private:
   TLabel* message;
   TButton* ok;
   TButton* cancel;

public:
   TMyForm();

   void OnResize(TControl* sender, const TResizeEventArgs& args);
   
   virtual ~TMyForm();
};

#11
7:56, 17 окт. 2019

А как в Immediate-подходе делаются анимации? Например, если список разворачивается не мгновенно, а с эффектом. Это же какой-то внутренний стейт должен быть у контрола, который сохраняется между кадрами.

#12
9:40, 17 окт. 2019

Джек Аллигатор
> но ничего не понятно
если тебе ничего не понятно, то в твоём случае 100% первый шаг — это изучить то, как работают существующие библиотеки. хотя бы высокоуровнево каким образом в них описывается иерархия компонентов, вёрстка и события. абсолютный минимумум — это разобраться, как это решено в imgui, в qt, ultralight и какой-нибудь cegui.

BUzer
> А как в Immediate-подходе делаются анимации? Например, если список
> разворачивается не мгновенно, а с эффектом. Это же какой-то внутренний стейт
> должен быть у контрола, который сохраняется между кадрами.
так и есть, состояние контролов хранится в глобальном хранилище. какая-то информация (вроде состояния анимации) хранятся во временном хранилище, которое живёт во время работы приложения, а какая-то — в постоянном, чтобы у тебя окошечки там же открывались, где ты их оставил в прошлый раз.

здесь очень важно различать понятие immediate mode, которое на самом деле можно понимать двояко:
1) у контролов нет состояния
2) контролы объявляются по месту использования

так вот 1) может быть реализовано совершенно независимо от 2). imgui, например, реализует 2), но не 1).

gamedevfor
> А если фонты у кнопки другие? а если кнопка должна растягиваться вместе с формой? А если бекграунд кнопки другой?
а если почитать документацию imgui?

вообще вы не подумайте, что я тут рекламирую подход imgui как единственно верный. вовсе нет, у него есть свои принципиальные ограничения. однако, там где эти ограничения не играют роли, то пользоваться им может быть гораздо удобнее. например, для демок, прототипирования, отладки итп.

#13
(Правка: 9:44) 9:42, 17 окт. 2019

BUzer
> Это же какой-то внутренний стейт должен быть у контрола, который сохраняется
> между кадрами.
Джек Аллигатор
> Данная статья — вольный перевод моей статьи на русский с некоторыми небольшими изменениями и улучшениями.
> Сами виджеты не хранят внутри себя своё состояние, либо хранят абсолютно
> минимальный необходимый минимум
Ничего хорошего от чтения вольных переводов не будет (в оригинальной статье даже близко этих строк нет). Да и невольных тоже. Никакого стейта там нет. Храните, когда началось и когда закончится. Тут идёт речь про прозрачность и контроль.

#14
(Правка: 9:53) 9:52, 17 окт. 2019

NyakNyakProduction
> Никакого стейта там нет.
да есть, есть. не всё хранится во внешнем коде. например, состояние свёрнутости/развёрнутости списков, текущий тип color picker'а и прочие динамически меняющиеся параметры хранятся в классе ImGuiStorage, можешь поискать по коду. но теоретически можно было бы обойтись и без них, они просто для удобства.

Страницы: 1 2 3 4 Следующая »
ПрограммированиеФорумГрафика