<?xml version="1.0" encoding="windows-1251"?>
<rss version="2.0">
<channel>
  <title>Программирование игр</title>
  <link>https://gamedev.ru/code/</link>
  <description>Игровой движок, 3D графика, сеть, физика, логика, уеб</description>
  <language>ru</language>
  <generator>http://skif.qrim.ru/</generator>
<item>
  <guid isPermaLink="true">https://gamedev.ru/code/news/3d_rendering_cookbook</guid>
  <pubDate>Fri, 09 May 2025 03:01:21 GMT</pubDate>
  <title>Вышла книга Vulkan 3D Graphics Rendering Cookbook</title>
  <link>https://gamedev.ru/code/news/3d_rendering_cookbook</link>
  <comments>https://gamedev.ru/code/forum/?id=290194</comments>
  <category>Графика</category>
  <description>
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Вышла книга &lt;b&gt;Vulkan 3D Graphics Rendering Cookbook:&lt;/b&gt; Implement expert-level techniques for high-performance graphics with Vulkan авторов Sergey Kosarevsky (&lt;a href=&quot;https://gamedev.ru/users/?id=1860&quot;&gt;_NetSurfer_&lt;/a&gt;), Alexey Medvedev (&lt;a href=&quot;https://gamedev.ru/users/?id=3300&quot;&gt;Rudybear&lt;/a&gt;), и Viktor Latypov (&lt;a href=&quot;https://gamedev.ru/users/?id=16426&quot;&gt;Vinil&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Это полностью переработанная (без шуток) вторая редакция нашей предыдущей книги 3D Graphics Rendering Cookbook. Теперь без OpenGL и с огромным количеством нового контента по Vulkan.&lt;/p&gt;

&lt;p&gt;В книге рассказывается как:&lt;/p&gt;

&lt;p&gt;- использовать Vulkan 1.3 и bindless rendering;
&lt;br&gt;
- загружать и рисовать glTF2 с PBR и анимациями (добавлены 2 главы про расширения glTF и анимации);
&lt;br&gt;
- сделать свой удобный bindless-only враппер над Vulkan 1.3;
&lt;br&gt;
- написать 3D движок на Vulkan 1.3;
&lt;br&gt;
- а также как не нужно делать scene graph и много примеров...&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://gamedev.ru/files/images/cover_image.png&quot; alt=&quot;vulkan_rendering_cookbook_cover | Вышла книга Vulkan 3D Graphics Rendering Cookbook&quot; title=&quot;vulkan_rendering_cookbook_cover&quot;&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;p&gt;Аналогично предыдущему изданию, за 714 страниц показан переход от одного цветного треугольника к вот такой картинке:&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://gamedev.ru/files/images/ch11_fig08_final.jpg&quot; alt=&quot;vulkan_rendering_cookbook_final_demo | Вышла книга Vulkan 3D Graphics Rendering Cookbook&quot; title=&quot;vulkan_rendering_cookbook_final_demo&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;p&gt;Книга Vulkan 3D Graphics Rendering Cookbook доступна для заказа на Amazon:
&lt;br&gt;
&lt;a href=&quot;https://www.amazon.com/Vulkan-Graphics-Rendering-Cookbook-High-Performance/dp/1803248114&quot; rel=&quot;nofollow&quot;&gt;https://www.amazon.com/Vulkan-Graphics-Rendering-Cookbook-High-Pe&amp;hellip; dp/1803248114&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Исходники всех примеров из книги: &lt;a href=&quot;https://github.com/PacktPublishing/3D-Graphics-Rendering-Cookbook-Second-Edition&quot; rel=&quot;nofollow&quot;&gt;https://github.com/PacktPublishing/3D-Graphics-Rendering-Cookbook&amp;hellip; econd-Edition&lt;/a&gt;&lt;/p&gt;</description>
</item>
<item>
  <guid isPermaLink="true">https://gamedev.ru/code/terms/Godot</guid>
  <pubDate>Sat, 02 Dec 2023 13:05:35 GMT</pubDate>
  <title>Godot Game Engine</title>
  <link>https://gamedev.ru/code/terms/Godot</link>
  <comments>https://gamedev.ru/code/forum/?id=280626</comments>
  <category>Общее</category>
  <category>C#</category>
  <category>C++</category>
  <category>game engine</category>
  <category>gdscript</category>
  <category>GLES2</category>
  <category>GLES3</category>
  <category>open source</category>
  <category>Vulkan</category>
  <description>
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Godot&lt;/strong&gt; (читается как &amp;laquo;Годо&amp;raquo;, от фр. Godot) &amp;mdash; кроссплатформенный игровой движок и редактор для 2D и 3D игр, с открытым исходным кодом под лицензией MIT. Изначально был разработан в 2007 году Хуаном Линиецки и Ариэлем Манзур. Core движка разрабатывается на C++.&lt;/p&gt;
&lt;p&gt;&lt;b&gt;Возможности редактора:&lt;/b&gt; &lt;ul&gt;&lt;li&gt;Создавать и редактировать игровые уровни и сцены.&lt;/li&gt;&lt;li&gt;Создавать и редактировать ресурсы движка.&lt;/li&gt;&lt;li&gt;Текстовый и визуальный редактор шейдеров.&lt;/li&gt;&lt;li&gt;Встроенный физический движок для 2D и 3D игр.&lt;/li&gt;&lt;li&gt;Встроенный редактор GDScript с подсветкой и авто-дополнением кода.&lt;/li&gt;&lt;li&gt;Встроенный справочник API движка.&lt;/li&gt;&lt;li&gt;Поддержка расширений и плагинов.&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;Поддерживаемая графика: &lt;b&gt;2D, 3D.&lt;/b&gt;
&lt;br&gt;
Используемый графический API: &lt;b&gt;GLES2, GLES3, Vulkan (начиная с версии 4.x).&lt;/b&gt;
&lt;br&gt;
Поддерживаемые платформы: &lt;b&gt;Windows, Linux, MacOS, Android, iOS, Web.&lt;/b&gt;
&lt;br&gt;
Поддерживаемые языки программирования: &lt;b&gt;GDScript, C/C++, C#.&lt;/b&gt;
&lt;br&gt;
Система сборки движка: &lt;b&gt;Python/SCons.&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;Официальный сайт: &lt;a href=&quot;https://godotengine.org/&quot; rel=&quot;nofollow&quot;&gt;https://godotengine.org/&lt;/a&gt;
&lt;br&gt;
Репозиторий: &lt;a href=&quot;https://github.com/godotengine/godot&quot; rel=&quot;nofollow&quot;&gt;https://github.com/godotengine/godot&lt;/a&gt;
&lt;br&gt;
Сообщество GameDev.ru: &lt;a href=&quot;https://gamedev.ru/community/godot/&quot;&gt;https://gamedev.ru/community/godot/&lt;/a&gt;&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://gamedev.ru/files/images/175594_1709280226_godot.png&quot; alt=&quot;Sergei | Godot Game Engine&quot; title=&quot;Sergei&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound&quot;&gt;</description>
</item>
<item>
  <guid isPermaLink="true">https://gamedev.ru/code/articles/Resource_names_in_engine</guid>
  <pubDate>Tue, 08 Nov 2022 05:12:43 GMT</pubDate>
  <title>Строковые идентификаторы в движке. string pools.</title>
  <link>https://gamedev.ru/code/articles/Resource_names_in_engine</link>
  <comments>https://gamedev.ru/code/forum/?id=268903</comments>
  <category>Общее</category>
  <category>C++</category>
  <description>
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;В программировании игр часто приходится ссылаться на объекты, используя строковые идентификаторы: например, получить текстуру по имени её файла или найти актора по его имени. Однако, хранение и частое сравнение большого количества строк может оказаться дорогим как с вычислительной точки зрения, так и с точки зрения используемой памяти. &lt;/p&gt;
&lt;p&gt;==Введение==&lt;/p&gt;

&lt;p&gt;В этой статье рассмотрены различные способы представления строки, которые позволяют оптимизировать сравнение строк и размер одного инстанса строки.&lt;/p&gt;

&lt;p&gt;==Обзор способов представления строки==&lt;/p&gt;

&lt;p&gt;Рассматриваемые основные способы:&lt;table border=&quot;5&quot; style=&quot;border-collapse:collapse;border: 2px solid #ff5555;&quot;&gt;&lt;tr&gt;&lt;th style=&quot;border: 2px solid #888888;&quot;&gt;Способ&lt;/th&gt;&lt;th style=&quot;border: 2px solid #888888;&quot;&gt;Плюсы&lt;/th&gt;&lt;th style=&quot;border: 2px solid #888888;&quot;&gt;Минусы&lt;/th&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;border: 2px solid #888888;&quot;&gt;string&lt;/td&gt;&lt;td style=&quot;border: 2px solid #888888;&quot;&gt;-просто&lt;/td&gt;&lt;td style=&quot;border: 2px solid #888888;&quot;&gt;-медленно 
&lt;br&gt;
-много памяти занимает&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;border: 2px solid #888888;&quot;&gt;string+static unordered_map&lt;/td&gt;&lt;td style=&quot;border: 2px solid #888888;&quot;&gt;-просто 
&lt;br&gt;
-быстрое сравнение&lt;/td&gt;&lt;td style=&quot;border: 2px solid #888888;&quot;&gt;-строки далеко друг от друга в памяти.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;border: 2px solid #888888;&quot;&gt;указатель на строку в глобальном хранилище строк&lt;/td&gt;&lt;td style=&quot;border: 2px solid #888888;&quot;&gt;-быстрое сравнение
&lt;br&gt;
-эффективное использование памяти&lt;/td&gt;&lt;td style=&quot;border: 2px solid #888888;&quot;&gt;-сложно&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;border: 2px solid #888888;&quot;&gt;индекс типизированный&lt;/td&gt;&lt;td style=&quot;border: 2px solid #888888;&quot;&gt;-быстрое сравнение
&lt;br&gt;
-самое эффективное использование памяти
&lt;br&gt;
sizeof(Name)-2(65536) или 3(16777216)&lt;/td&gt;&lt;td style=&quot;border: 2px solid #888888;&quot;&gt;-в отладчике имя ресурса невидно
&lt;br&gt;
-вывести для отладки сложно&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;br&gt;
Способы через равенство &amp;quot;Str&amp;quot; == &amp;quot;Str&amp;quot;, хеши, кодоген енама строк, COW строки рассмотрены не будут, так как ведут к множеству неочевидных проблем.&lt;/p&gt;

&lt;p&gt;А теперь подробнее про эти методы.
&lt;br&gt;
Далее при представлении используются следующие обозначения:
&lt;br&gt;
D-длина строки
&lt;br&gt;
Памяти: байт на инстанс.
&lt;br&gt;
Время сравнения: временная сложность O(...).&lt;/p&gt;

&lt;p&gt;===Способ представления: string===&lt;/p&gt;

&lt;p&gt;Просто строка.&lt;/p&gt;

&lt;p&gt;Для больших проектов, где имена ресурсов могут быть 500+ символов этот способ не подходит из-за его низкой скорости сравнения строк и большого использования памяти.&lt;/p&gt;

&lt;p&gt;Можно, конечно, как в старой файловой сисеме FAT сказать, что не делаем длинных путей и сокращаем гласные в именах, но это неудобно, особенно в больших проектах.&lt;/p&gt;

&lt;p&gt;Памяти: 16(или больше) + D(если строка не влезла в хранилище самого класса).
&lt;br&gt;
Время сравнения: D.&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;class&lt;/span&gt; NameString
{
&lt;span class=&quot;key&quot;&gt;public&lt;/span&gt;:
  &lt;span class=&quot;key&quot;&gt;explicit&lt;/span&gt; NameString&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;* name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;: _s&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; {}

  string get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
  {
    &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; _s;
  }

  std::strong_ordering &lt;span class=&quot;key&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;=&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;NameString &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt;&amp; b&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; = &lt;span class=&quot;key&quot;&gt;default&lt;/span&gt;;
&lt;span class=&quot;key&quot;&gt;private&lt;/span&gt;:
  string _s;
};&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;===Способ представления: string + static unordered_map===&lt;/p&gt;

&lt;p&gt;Первый же запрос из гугла выдаёт способ с помошью статической переменной и свойств emplace.
&lt;br&gt;
&lt;a href=&quot;https://stackoverflow.com/questions/20938441/implementing-a-string-pool-that-is-guaranteed-not-to-move&quot; rel=&quot;nofollow&quot;&gt;https://stackoverflow.com/questions/20938441/implementing-a-strin&amp;hellip; d-not-to-move&lt;/a&gt;
&lt;br&gt;
Статическая переменная выступает в роли синглтона. Создаёт инстанс и даёт к нему доступ.
&lt;br&gt;
А emplace возвращает итератор на уже имеющийся элемент или создаёт новый.
&lt;br&gt;
Сам unordered_set не инвалидирует указатели при добавлении элементов.&lt;/p&gt;

&lt;p&gt;В итоге пара строк и простейший пуул готов. &lt;/p&gt;

&lt;p&gt;Эффективность такого пуула очень низка. Строки находятся очень далеко в памяти и от этого запрос к строке практически гарантированно кэш мисс, скорее всего по всем 3 уровням. В итоге очень долгий запрос к памяти.&lt;/p&gt;

&lt;p&gt;Зато просто реализовать.&lt;/p&gt;

&lt;p&gt;Памяти: 8.
&lt;br&gt;
Время сравнения: 1.&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;overflow full&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;class&lt;/span&gt; NamePoolMap
{
&lt;span class=&quot;key&quot;&gt;public&lt;/span&gt;:
  &lt;span class=&quot;key&quot;&gt;explicit&lt;/span&gt; NamePoolMap&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;* name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;: _s&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
  {
    &lt;span class=&quot;comment&quot;&gt;//https://stackoverflow.com/questions/20938441/implementing-a-string-pool-that-is-guaranteed-not-to-move&lt;/span&gt;
    &lt;span class=&quot;key&quot;&gt;static&lt;/span&gt; unordered_set&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;string&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; map;
    _s = map.insert&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;.first-&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;c_str&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  }

  string get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
  {
    &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; _s;
  }

  std::strong_ordering &lt;span class=&quot;key&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;=&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;NamePoolMap &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt;&amp; b&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; = &lt;span class=&quot;key&quot;&gt;default&lt;/span&gt;;

&lt;span class=&quot;key&quot;&gt;private&lt;/span&gt;:
  &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;* _s;
};&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;bound&quot;&gt;&lt;p&gt;===Способ представления: указатель на строку в глобальном хранилище строк===&lt;/p&gt;

&lt;p&gt;Можно улучшить прошлый вариант запихнув все строки в отдельный Storage или virtual vector.&lt;/p&gt;

&lt;p&gt;При добавлении новой строки она просто записывается в конец массива, а её указатель в массиве в таблицу.&lt;/p&gt;

&lt;p&gt;В моей реализации предполагается, что строка не содержит нулей. Поэтому записывается нуль терминированная строка. Получается массив данных:&lt;/p&gt;
&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;Str/0Str1/0Str..&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Если же зачем-то нужно иметь нули в строке, то следует записывать: размер строки + сама строка.&lt;/p&gt;

&lt;p&gt;Можно так-же посмотреть, что в анреале сделали, этот класс я написал основываясь на том, что там сделали:
&lt;br&gt;
&lt;a href=&quot;https://docs.unrealengine.com/4.26/en-US/ProgrammingAndScripting/ProgrammingWithCPP/UnrealArchitecture/StringHandling/FName/&quot; rel=&quot;nofollow&quot;&gt;Fname&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Памяти: 8.
&lt;br&gt;
Время сравнения: 1.&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;overflow full&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;class&lt;/span&gt; NamePooled
{
&lt;span class=&quot;key&quot;&gt;public&lt;/span&gt;:
  &lt;span class=&quot;key&quot;&gt;explicit&lt;/span&gt; NamePooled&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;* name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
  {
    _s = Pool::get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;.add&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  }

  string get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
  {
    &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; _s;
  }

  std::strong_ordering &lt;span class=&quot;key&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;=&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;NamePooled &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt;&amp; b&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; = &lt;span class=&quot;key&quot;&gt;default&lt;/span&gt;;

&lt;span class=&quot;key&quot;&gt;private&lt;/span&gt;:
  &lt;span class=&quot;key&quot;&gt;using&lt;/span&gt; IdType = uint16_t;

  &lt;span class=&quot;key&quot;&gt;class&lt;/span&gt; Pool
  {
  &lt;span class=&quot;key&quot;&gt;public&lt;/span&gt;:
    &lt;span class=&quot;key&quot;&gt;static&lt;/span&gt; Pool&amp; get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
    {
      &lt;span class=&quot;key&quot;&gt;static&lt;/span&gt; Pool inst;
      &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; inst;
    }

    &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;* add&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;* name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
    {
      &lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;auto&lt;/span&gt; it = _set.find&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;; it != _set.end&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; *it;
      &lt;span class=&quot;key&quot;&gt;auto&lt;/span&gt; str = _store.add&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
      _set.insert&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;str&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
      &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; str;
    }

  &lt;span class=&quot;key&quot;&gt;private&lt;/span&gt;:
    &lt;span class=&quot;key&quot;&gt;class&lt;/span&gt; Store
    {
    &lt;span class=&quot;key&quot;&gt;public&lt;/span&gt;:
      &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;* add&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;string name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
      {
        &lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;auto&lt;/span&gt; opt = _segments.back&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;.add&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; *opt;
        _segments.emplace_back&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;max&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;name.size&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; + &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;, segmentSizeBase&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
        &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; *_segments.back&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;.add&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
      }

    &lt;span class=&quot;key&quot;&gt;private&lt;/span&gt;:
      &lt;span class=&quot;key&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;constexpr&lt;/span&gt; size_t segmentSizeBase = &lt;span class=&quot;digit&quot;&gt;10000&lt;/span&gt;;

      &lt;span class=&quot;key&quot;&gt;class&lt;/span&gt; Segment
      {
      &lt;span class=&quot;key&quot;&gt;public&lt;/span&gt;:
        &lt;span class=&quot;key&quot;&gt;explicit&lt;/span&gt; Segment&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;size_t size&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
        {
          _data.resize&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;size&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
        }

        optional&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;*&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; add&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;string &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt;&amp; s&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
        {
          &lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;s.size&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; + &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt; + _used &lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; _data.size&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; {};
          ranges::copy&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;s, _data.begin&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; + _used&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
          &lt;span class=&quot;comment&quot;&gt;// Vector initialized to 0 anyway&lt;/span&gt;
          &lt;span class=&quot;comment&quot;&gt;//_data[_used + s.size()] = 0; &lt;/span&gt;
          &lt;span class=&quot;key&quot;&gt;auto&lt;/span&gt; res = &amp;_data&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;_used&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
          _used += s.size&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; + &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;;
          &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; res;
        }

      &lt;span class=&quot;key&quot;&gt;private&lt;/span&gt;:
        std::vector&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; _data;
        size_t _used = &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;;
      };

      vector&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;Segment&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; _segments = { Segment&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;segmentSizeBase&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; };
    };

    &lt;span class=&quot;comment&quot;&gt;// You can write hasher that not need std::string instance.&lt;/span&gt;
    &lt;span class=&quot;key&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;inline&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;auto&lt;/span&gt; hash = &lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;* e&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
    {
      &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; std::hash&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;std::string&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;e&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
    };
    &lt;span class=&quot;key&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;inline&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;auto&lt;/span&gt; equal = &lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;* a, &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;* b&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
    {
      &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; strcmp&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;a, b&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; == &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;;
    };

    std::unordered_set&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;*, &lt;span class=&quot;key&quot;&gt;decltype&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;hash&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;key&quot;&gt;decltype&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;equal&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; _set{ &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, hash, equal };

    &lt;span class=&quot;comment&quot;&gt;// Here better use virtual vector&lt;/span&gt;
    Store _store;
  };

  &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;* _s = &lt;span class=&quot;key&quot;&gt;nullptr&lt;/span&gt;;
};&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;bound&quot;&gt;&lt;p&gt;===Способ представления: индекс типизированный===&lt;/p&gt;

&lt;p&gt;Всё бы хорошо, но 8 байт для имени слишком много, да даже 4 многовато. А что если постараться ужать до 2 байт? &lt;/p&gt;

&lt;p&gt;В итоге получается ещё 1 вариант, когда сохраняется не указатель, а индекс. &lt;/p&gt;

&lt;p&gt;Более 2^16(~65&apos;000) различных строк так не уместить, но оно и не в каждой игре столько нужно. Можно взять 3 байта, уж 2^24(~16&apos;000&apos;000) строк должно хватить. &lt;/p&gt;

&lt;p&gt;Как создать int24 думаю известно. &lt;a href=&quot;https://stackoverflow.com/questions/2682725/int24-24-bit-integral-datatype&quot; rel=&quot;nofollow&quot;&gt;Int24&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Памяти: 2.
&lt;br&gt;
Время сравнения: 1.&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;class&lt;/span&gt; NameId
{
&lt;span class=&quot;key&quot;&gt;public&lt;/span&gt;:
  &lt;span class=&quot;key&quot;&gt;explicit&lt;/span&gt; NameId&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;* name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
  {
    _id = Pool::get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;.add&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  }

  string get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
  {
    &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; Pool::get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;.conv&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;_id&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  }

  std::strong_ordering &lt;span class=&quot;key&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;=&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;NameId &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt;&amp; b&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; = &lt;span class=&quot;key&quot;&gt;default&lt;/span&gt;;

&lt;span class=&quot;key&quot;&gt;private&lt;/span&gt;:
  &lt;span class=&quot;key&quot;&gt;using&lt;/span&gt; IdType = uint16_t;

  &lt;span class=&quot;key&quot;&gt;class&lt;/span&gt; Pool
  {
  &lt;span class=&quot;key&quot;&gt;public&lt;/span&gt;:
    &lt;span class=&quot;key&quot;&gt;static&lt;/span&gt; Pool&amp; get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
    {
      &lt;span class=&quot;key&quot;&gt;static&lt;/span&gt; Pool inst;
      &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; inst;
    }

    IdType add&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;* name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
    {
      &lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;auto&lt;/span&gt; it = _map.find&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;; it != _map.end&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; it-&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;second;
      size_t idFull = _map.size&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
      &lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;idFull &lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; std::numeric_limits&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;IdType&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;::max&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
        std::terminate&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;; &lt;span class=&quot;comment&quot;&gt;// So many names&lt;/span&gt;
      IdType id = IdType&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;idFull&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
      _map&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;name&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; = id;
      _mapId&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; = &amp;_map.find&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;-&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;first;
      &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; id;
    }

    string conv&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;IdType id&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
    {
      &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; *_mapId&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
    }

  &lt;span class=&quot;key&quot;&gt;private&lt;/span&gt;:
    unordered_map&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;string, IdType&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; _map;
    &lt;span class=&quot;comment&quot;&gt;// WARN if use not std::unordered_map force std::string&lt;/span&gt;
    &lt;span class=&quot;comment&quot;&gt;// place data outside of class and use const char*&lt;/span&gt;
    unordered_map&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;IdType, string &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt;*&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; _mapId;
  };

  IdType _id = -&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;;
};&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;==Исходники==&lt;/p&gt;

&lt;p&gt;В этой статье не рассматривается правильный форвардинг, какую мапу взять на замену медленной std и прочие вещи относящиеся к ньюансам C++. В С# вообще есть встроенный пул строк &lt;a href=&quot;https://docs.microsoft.com/ru-ru/dotnet/api/system.string.intern?view=net-6.0&quot; rel=&quot;nofollow&quot;&gt;String.intern&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Используется MSVC, C++20, x64.&lt;/p&gt;

 &lt;div id=&quot;spoilerHead1&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(1, 1)&quot;&gt;+ Весь код&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;spoiler1&quot; style=&quot;display:none;&quot;&gt;&lt;div class=&quot;bound overflow&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(1, 0)&quot;&gt;&amp;minus; Скрыть&lt;/span&gt;
&lt;/div&gt;
&lt;div class=&quot;overflow full&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;#include&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;cassert&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;key&quot;&gt;#include&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;numbers&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;key&quot;&gt;#include&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;optional&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;key&quot;&gt;#include&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;unordered_map&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;key&quot;&gt;#include&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;unordered_set&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;key&quot;&gt;#include&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;vector&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;key&quot;&gt;#include&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;chrono&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;

&lt;span class=&quot;key&quot;&gt;using&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;namespace&lt;/span&gt; std;

&lt;span class=&quot;key&quot;&gt;class&lt;/span&gt; NameString
{
&lt;span class=&quot;key&quot;&gt;public&lt;/span&gt;:
  &lt;span class=&quot;key&quot;&gt;explicit&lt;/span&gt; NameString&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;* name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;: _s&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; {}

  string get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
  {
    &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; _s;
  }

  std::strong_ordering &lt;span class=&quot;key&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;=&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;NameString &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt;&amp; b&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; = &lt;span class=&quot;key&quot;&gt;default&lt;/span&gt;;
&lt;span class=&quot;key&quot;&gt;private&lt;/span&gt;:
  string _s;
};

&lt;span class=&quot;key&quot;&gt;class&lt;/span&gt; NamePoolMap
{
&lt;span class=&quot;key&quot;&gt;public&lt;/span&gt;:
  &lt;span class=&quot;key&quot;&gt;explicit&lt;/span&gt; NamePoolMap&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;* name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;: _s&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
  {
    &lt;span class=&quot;comment&quot;&gt;//https://stackoverflow.com/questions/20938441/implementing-a-string-pool-that-is-guaranteed-not-to-move&lt;/span&gt;
    &lt;span class=&quot;key&quot;&gt;static&lt;/span&gt; unordered_set&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;string&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; map;
    _s = map.insert&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;.first-&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;c_str&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  }

  string get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
  {
    &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; _s;
  }

  std::strong_ordering &lt;span class=&quot;key&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;=&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;NamePoolMap &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt;&amp; b&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; = &lt;span class=&quot;key&quot;&gt;default&lt;/span&gt;;

&lt;span class=&quot;key&quot;&gt;private&lt;/span&gt;:
  &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;* _s;
};

&lt;span class=&quot;key&quot;&gt;class&lt;/span&gt; NamePooled
{
&lt;span class=&quot;key&quot;&gt;public&lt;/span&gt;:
  &lt;span class=&quot;key&quot;&gt;explicit&lt;/span&gt; NamePooled&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;* name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
  {
    _s = Pool::get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;.add&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  }

  string get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
  {
    &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; _s;
  }

  std::strong_ordering &lt;span class=&quot;key&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;=&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;NamePooled &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt;&amp; b&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; = &lt;span class=&quot;key&quot;&gt;default&lt;/span&gt;;

&lt;span class=&quot;key&quot;&gt;private&lt;/span&gt;:
  &lt;span class=&quot;key&quot;&gt;using&lt;/span&gt; IdType = uint16_t;

  &lt;span class=&quot;key&quot;&gt;class&lt;/span&gt; Pool
  {
  &lt;span class=&quot;key&quot;&gt;public&lt;/span&gt;:
    &lt;span class=&quot;key&quot;&gt;static&lt;/span&gt; Pool&amp; get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
    {
      &lt;span class=&quot;key&quot;&gt;static&lt;/span&gt; Pool inst;
      &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; inst;
    }

    &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;* add&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;* name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
    {
      &lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;auto&lt;/span&gt; it = _set.find&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;; it != _set.end&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; *it;
      &lt;span class=&quot;key&quot;&gt;auto&lt;/span&gt; str = _store.add&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
      _set.insert&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;str&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
      &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; str;
    }

  &lt;span class=&quot;key&quot;&gt;private&lt;/span&gt;:
    &lt;span class=&quot;key&quot;&gt;class&lt;/span&gt; Store
    {
    &lt;span class=&quot;key&quot;&gt;public&lt;/span&gt;:
      &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;* add&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;string name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
      {
        &lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;auto&lt;/span&gt; opt = _segments.back&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;.add&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; *opt;
        _segments.emplace_back&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;max&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;name.size&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; + &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;, segmentSizeBase&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
        &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; *_segments.back&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;.add&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
      }

    &lt;span class=&quot;key&quot;&gt;private&lt;/span&gt;:
      &lt;span class=&quot;key&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;constexpr&lt;/span&gt; size_t segmentSizeBase = &lt;span class=&quot;digit&quot;&gt;10000&lt;/span&gt;;

      &lt;span class=&quot;key&quot;&gt;class&lt;/span&gt; Segment
      {
      &lt;span class=&quot;key&quot;&gt;public&lt;/span&gt;:
        &lt;span class=&quot;key&quot;&gt;explicit&lt;/span&gt; Segment&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;size_t size&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
        {
          _data.resize&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;size&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
        }

        optional&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;*&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; add&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;string &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt;&amp; s&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
        {
          &lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;s.size&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; + &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt; + _used &lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; _data.size&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; {};
          ranges::copy&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;s, _data.begin&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; + _used&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
          &lt;span class=&quot;comment&quot;&gt;// Vector initialized to 0 anyway&lt;/span&gt;
          &lt;span class=&quot;comment&quot;&gt;//_data[_used + s.size()] = 0; &lt;/span&gt;
          &lt;span class=&quot;key&quot;&gt;auto&lt;/span&gt; res = &amp;_data&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;_used&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
          _used += s.size&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; + &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;;
          &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; res;
        }

      &lt;span class=&quot;key&quot;&gt;private&lt;/span&gt;:
        std::vector&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; _data;
        size_t _used = &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;;
      };

      vector&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;Segment&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; _segments = { Segment&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;segmentSizeBase&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; };
    };

    &lt;span class=&quot;comment&quot;&gt;// You can write hasher that not need std::string instance.&lt;/span&gt;
    &lt;span class=&quot;key&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;inline&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;auto&lt;/span&gt; hash = &lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;* e&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
    {
      &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; std::hash&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;std::string&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;e&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
    };
    &lt;span class=&quot;key&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;inline&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;auto&lt;/span&gt; equal = &lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;* a, &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;* b&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
    {
      &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; strcmp&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;a, b&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; == &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;;
    };

    std::unordered_set&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;*, &lt;span class=&quot;key&quot;&gt;decltype&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;hash&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;key&quot;&gt;decltype&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;equal&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; _set{ &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, hash, equal };

    &lt;span class=&quot;comment&quot;&gt;// Here better use virtual vector&lt;/span&gt;
    Store _store;
  };

  &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;* _s = &lt;span class=&quot;key&quot;&gt;nullptr&lt;/span&gt;;
};

&lt;span class=&quot;key&quot;&gt;class&lt;/span&gt; NameId
{
&lt;span class=&quot;key&quot;&gt;public&lt;/span&gt;:
  &lt;span class=&quot;key&quot;&gt;explicit&lt;/span&gt; NameId&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;* name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
  {
    _id = Pool::get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;.add&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  }

  string get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
  {
    &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; Pool::get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;.conv&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;_id&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  }

  std::strong_ordering &lt;span class=&quot;key&quot;&gt;operator&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;=&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;NameId &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt;&amp; b&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; = &lt;span class=&quot;key&quot;&gt;default&lt;/span&gt;;

&lt;span class=&quot;key&quot;&gt;private&lt;/span&gt;:
  &lt;span class=&quot;key&quot;&gt;using&lt;/span&gt; IdType = uint16_t;

  &lt;span class=&quot;key&quot;&gt;class&lt;/span&gt; Pool
  {
  &lt;span class=&quot;key&quot;&gt;public&lt;/span&gt;:
    &lt;span class=&quot;key&quot;&gt;static&lt;/span&gt; Pool&amp; get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
    {
      &lt;span class=&quot;key&quot;&gt;static&lt;/span&gt; Pool inst;
      &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; inst;
    }

    IdType add&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt;* name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
    {
      &lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;auto&lt;/span&gt; it = _map.find&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;; it != _map.end&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
        &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; it-&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;second;
      size_t idFull = _map.size&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
      &lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;idFull &lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; std::numeric_limits&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;IdType&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;::max&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
        std::terminate&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;; &lt;span class=&quot;comment&quot;&gt;// So many names&lt;/span&gt;
      IdType id = IdType&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;idFull&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
      _map&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;name&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; = id;
      _mapId&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; = &amp;_map.find&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;name&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;-&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;first;
      &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; id;
    }

    string conv&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;IdType id&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
    {
      &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; *_mapId&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;id&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
    }

  &lt;span class=&quot;key&quot;&gt;private&lt;/span&gt;:
    unordered_map&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;string, IdType&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; _map;
    &lt;span class=&quot;comment&quot;&gt;// WARN if use not std::unordered_map force std::string&lt;/span&gt;
    &lt;span class=&quot;comment&quot;&gt;// place data outside of class and use const char*&lt;/span&gt;
    unordered_map&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;IdType, string &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt;*&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; _mapId;
  };

  IdType _id = -&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;;
};

&lt;span class=&quot;key&quot;&gt;template&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;typename&lt;/span&gt; T&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;key&quot;&gt;void&lt;/span&gt; test&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;string className&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
{
  cout &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;class:&amp;quot;&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; className &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot; sizeof:&amp;quot;&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;sizeof&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;T&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; endl;

  string t5s = &lt;span class=&quot;string&quot;&gt;&amp;quot;Str&amp;quot;&lt;/span&gt;;
  &lt;span class=&quot;key&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;int&lt;/span&gt; i = &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;; i &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;digit&quot;&gt;1000&lt;/span&gt;; i++&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
    t5s += &lt;span class=&quot;string&quot;&gt;&amp;quot; loooooooong&amp;quot;&lt;/span&gt;;

  T t1 = T&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;quot;Str&amp;quot;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  T t2 = T&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;quot;Str1&amp;quot;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  T t3 = T&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;quot;Str&amp;quot;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  T t4 = T&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&amp;quot;Str long long long long long long long long long long long long long long long&amp;quot;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  T t5 = T&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;t5s.c_str&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  T t6 = t4; &lt;span class=&quot;comment&quot;&gt;//Str long long long long long long long long long long long long long long long&lt;/span&gt;
  T t7 = t2; &lt;span class=&quot;comment&quot;&gt;//Str1&lt;/span&gt;

  cout &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;t1:&amp;quot;&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; t1.get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; endl;
  cout &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;t2:&amp;quot;&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; t2.get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; endl;
  cout &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;t3:&amp;quot;&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; t3.get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; endl;
  cout &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;t4:&amp;quot;&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; t4.get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; endl;
  cout &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;t5:&amp;quot;&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; t5.get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;.size&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; endl;
  cout &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;t6:&amp;quot;&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; t6.get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; endl;
  cout &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;t7:&amp;quot;&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; t7.get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; endl;

  &lt;span class=&quot;comment&quot;&gt;// Force rehash pools maps&lt;/span&gt;
  &lt;span class=&quot;key&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;char&lt;/span&gt; i = &lt;span class=&quot;string&quot;&gt;&apos;a&apos;&lt;/span&gt;; i &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&apos;z&apos;&lt;/span&gt;; i++&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;key&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;char&lt;/span&gt; r = &lt;span class=&quot;string&quot;&gt;&apos;a&apos;&lt;/span&gt;; r &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&apos;z&apos;&lt;/span&gt;; r++&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;key&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;char&lt;/span&gt; o = &lt;span class=&quot;string&quot;&gt;&apos;a&apos;&lt;/span&gt;; o &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&apos;z&apos;&lt;/span&gt;; o++&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
      {
        T t = T&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;string&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;, i&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; + string&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;, r&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; + string&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;, o&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; + &lt;span class=&quot;string&quot;&gt;&amp;quot;_____________&amp;quot;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;.c_str&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
      }

  T t8 = T&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;t2.get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;.c_str&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;

  cout &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;t1:&amp;quot;&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; t1.get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; endl;
  cout &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;t2:&amp;quot;&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; t2.get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; endl;
  cout &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;t3:&amp;quot;&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; t3.get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; endl;
  cout &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;t4:&amp;quot;&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; t4.get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; endl;
  cout &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;t5:&amp;quot;&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; t5.get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;.size&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; endl;
  cout &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;t6:&amp;quot;&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; t6.get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; endl;
  cout &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;t7:&amp;quot;&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; t7.get&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; endl;

  assert&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;t1 == t3&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  assert&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;t2 == t7&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  assert&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;t2 == t7&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  assert&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;t2 == t7&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  assert&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;t6 == t4&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;

  assert&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;t1 != t2&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  assert&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;t3 != t4&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  assert&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;t5 != t4&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;

  cout &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; endl;
};


&lt;span class=&quot;key&quot;&gt;int&lt;/span&gt; main&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
{
&lt;span class=&quot;key&quot;&gt;#define&lt;/span&gt; test&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;T&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; test&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;#T&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  test&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;NameString&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  test&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;NamePoolMap&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  test&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;NamePooled&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  test&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;NameId&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
}&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;bound&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;bound overflow&quot;&gt;&lt;p&gt;==Дополнение: имена объектов==&lt;/p&gt;

&lt;p&gt;А что если именовать игровые объекты? Пулл строк быстро засорится... &lt;/p&gt;

&lt;p&gt;Но есть решение. От Анреала и их FName класса.
&lt;br&gt;
Создаём новую структуру со строкой из пуула и индексом. 
&lt;br&gt;
При добавлении новой строки парсится число после _. 
&lt;br&gt;
При запросе заново его ставим туда.&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;SomeName_1000 -&amp;gt; {&amp;quot;SomeName&amp;quot;, 1000}
SomeName_-42 -&amp;gt; {&amp;quot;SomeName_-42&amp;quot;, -1}
SomeName_100000000000000000 -&amp;gt; {&amp;quot;SomeName_100000000000000000&amp;quot;, -1}
SomeName_0 -&amp;gt; {&amp;quot;SomeName&amp;quot;, 0}&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Так-же можно сделать функцию с добавлением нового имени и инкрементальный счётчик чтобы не искать занято ли оно. &lt;/p&gt;

&lt;p&gt;Так можно получить имена объектов умещающиеся в 8 байт(3 бита на имя и 5 на индекс) ну или же в 16 байт и без хитрых преобразований.&lt;/p&gt;

&lt;p&gt;==Дополнение: типизация имён по типу ресурсов.==&lt;/p&gt;

&lt;p&gt;Все варианты обёрнуты в классы, сделано это для того чтобы нельзя было случайно сравнить разные типы.&lt;/p&gt;

&lt;p&gt;Всё бы хорошо, но можно пойти и дальше. Ведь при наличии 2 ресурсов(монстр и текстура) нам при загрузке точно известно, что они разного типа. И часто на этапе компиляции известно, что это ресурс определённого типа.&lt;/p&gt;

&lt;p&gt;И это можно использовать.&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;class&lt;/span&gt; ResourceId:&lt;span class=&quot;key&quot;&gt;private&lt;/span&gt; Name{}
&lt;span class=&quot;key&quot;&gt;class&lt;/span&gt; ResourceIdTexture:&lt;span class=&quot;key&quot;&gt;public&lt;/span&gt; ResourceId{}
&lt;span class=&quot;key&quot;&gt;class&lt;/span&gt; ResourceIdMonster:&lt;span class=&quot;key&quot;&gt;public&lt;/span&gt; ResourceId{}&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Для полного же завершения типизации нужно запретить конвертить в производные типы кому либо кроме менеджера ресурсов.&lt;/p&gt;</description>
</item>
<item>
  <guid isPermaLink="true">https://gamedev.ru/code/articles/cpp_coroutines_1</guid>
  <pubDate>Mon, 07 Nov 2022 10:49:14 GMT</pubDate>
  <title>Сопрограммы на С++, начальный уровень</title>
  <link>https://gamedev.ru/code/articles/cpp_coroutines_1</link>
  <comments>https://gamedev.ru/code/forum/?id=272846</comments>
  <category>Общее</category>
  <category>C++</category>
  <description>
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Coroutine&lt;/b&gt; (сопрограмма, корутина) &amp;mdash; функция, выполнение которой можно явно прерываться методами языка программирования. В отличие от прерывания выполнения потока (thread), который реализован в ОС и происходит неявно и в любой момент. Здесь я разбираю в чем преимущество корутин и какие корутины добавлены в C++20.&lt;/p&gt;
&lt;p&gt;Предварительно с терминологией можно ознакомиться в Википедии: &lt;a href=&quot;https://ru.wikipedia.org/wiki/%D0%A1%D0%BE%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%B0&quot; rel=&quot;nofollow&quot;&gt;сопрограмма&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Рассмотрим как работает простой код на уровне железа и ОС:&lt;/p&gt;
&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;auto&lt;/span&gt; buf = &lt;span class=&quot;key&quot;&gt;new&lt;/span&gt; Buffer{ &lt;span class=&quot;digit&quot;&gt;10&lt;/span&gt;_Mb };
ReadFile&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;file.name&amp;quot;&lt;/span&gt;, buf &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;В двух строках происходит множество неявных прерываний:&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;auto&lt;/span&gt; buf = &lt;span class=&quot;key&quot;&gt;new&lt;/span&gt; Buffer{ &lt;span class=&quot;digit&quot;&gt;10&lt;/span&gt;_Mb };&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Выделяется память для записи файла в RAM. Внутри вызывается аллокатор памяти, который обычно защищен мютексом (&lt;a href=&quot;https://ru.wikipedia.org/wiki/%D0%9C%D1%8C%D1%8E%D1%82%D0%B5%D0%BA%D1%81&quot; rel=&quot;nofollow&quot;&gt;wiki&lt;/a&gt;). Если в этот же момент в другом потоке происходит еще одно выделение памяти, то мютекс позволит выполниться одному потоку, а другой остановится и будет разбужен ОС спустя какое-то время.&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;ReadFile&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;file.name&amp;quot;&lt;/span&gt;, buf &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Происходит чтение из файла. Доступ к диску медленный, поэтому ОС останавливает поток и возобновляет его, когда чтение из файла завершится.&lt;/p&gt;

&lt;p&gt;Получается в каждой строке кода происходит неявная остановка выполнения потока, что приводит к смене контекста (context switch), который занимает более 1мс. Когда поток остановлен, его место занимает другой поток, сколько он выполняется - неизвестно, а значит нет гарантий, когда именно возобновится выполнение нашего потока.&lt;/p&gt;

&lt;p&gt;Подход с использованием потоков оказался слишком медленным, так появились различные асинхронные алгоритмы, например с использованием &lt;a href=&quot;https://en.wikipedia.org/wiki/Thread_pool&quot; rel=&quot;nofollow&quot;&gt;thread pool&lt;/a&gt; и &lt;a href=&quot;https://ru.wikipedia.org/wiki/Futures_and_promises&quot; rel=&quot;nofollow&quot;&gt;promise&lt;/a&gt;:&lt;/p&gt;
&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;AllocBuffer&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; &lt;span class=&quot;digit&quot;&gt;10&lt;/span&gt;_Mb &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
  .then&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;Buffer buf&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
  {
    ReadFile&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;file.name&amp;quot;&lt;/span&gt;, buf &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
    .then&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;Buffer buf, size_t readn&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
    {
      &lt;span class=&quot;comment&quot;&gt;// делаем что-то с загруженными данными&lt;/span&gt;
    }&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  }&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;&lt;/pre&gt;&lt;/div&gt;
 &lt;div id=&quot;spoilerHead2&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(2, 1)&quot;&gt;+ пример_реализации&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;spoiler2&quot; style=&quot;display:none;&quot;&gt;&lt;div class=&quot;bound overflow&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(2, 0)&quot;&gt;&amp;minus; Скрыть&lt;/span&gt;
&lt;/div&gt;
&lt;div class=&quot;overflow full&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;template&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;typename&lt;/span&gt; T&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;key&quot;&gt;struct&lt;/span&gt; Promise {
  Promise&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;lambda_result&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;  then &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; lambda&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;T in&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
};

Promise&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;Buffer&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;  AllocBuffer &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;Size size&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
{
  &lt;span class=&quot;comment&quot;&gt;// allocatorAsyncMutex - таски, которые от него зависят, выполняются последовательно&lt;/span&gt;
  &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; threadPool.Add&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;size&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;{ &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;new&lt;/span&gt; Buffer{size} }, DependsOn{allocatorAsyncMutex} &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
}

Promise&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;Buffer, size_t&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;  ReadFile &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;string filename, Buffer buf&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
{
  &lt;span class=&quot;key&quot;&gt;auto&lt;/span&gt; op = ReadFileAsync&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; filename, buf &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;; &lt;span class=&quot;comment&quot;&gt;// по завершении чтения сработает event.&lt;/span&gt;
  &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; threadPool.Add&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;op, buf&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;{ &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; {buf, op.readn}; }, DependsOn{op.event} &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
}&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;bound&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;bound overflow&quot;&gt;&lt;p&gt;Каждая функция здесь &amp;mdash; маленькая задача, которая выполняется во внутреннем планировщике (thread pool, job system и тд). Планировщик внутри программы заменяет планировщик ОС, тем самым избавляясь от мютексов и синхронных чтений файлов. Но читаемость кода сильно ухудшается.&lt;/p&gt;

&lt;p&gt;Корутины это другой синтаксис для использования внутреннем планировщика задач (thread pool). Они позволяют писать более линейный код, который лучше читается.&lt;/p&gt;
&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;auto&lt;/span&gt; buf = &lt;span class=&quot;key&quot;&gt;co_await&lt;/span&gt; AllocBuffer&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; &lt;span class=&quot;digit&quot;&gt;10&lt;/span&gt;_Mb &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
size_t readn = &lt;span class=&quot;key&quot;&gt;co_await&lt;/span&gt; ReadFile&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;file.name&amp;quot;&lt;/span&gt;, buf &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
... &lt;span class=&quot;comment&quot;&gt;// делаем что-то с загруженными данными&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
 &lt;div id=&quot;spoilerHead3&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(3, 1)&quot;&gt;+ пример_реализации&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;spoiler3&quot; style=&quot;display:none;&quot;&gt;&lt;div class=&quot;bound overflow&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(3, 0)&quot;&gt;&amp;minus; Скрыть&lt;/span&gt;
&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;template&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;typename&lt;/span&gt; T&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;key&quot;&gt;struct&lt;/span&gt; Promise
{
  &lt;span class=&quot;key&quot;&gt;bool&lt;/span&gt;  IsComplete&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  T     Result&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  
  &lt;span class=&quot;comment&quot;&gt;// для корутин&lt;/span&gt;
  &lt;span class=&quot;key&quot;&gt;struct&lt;/span&gt; promise_type {
    ...
    Promise&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;  GetTask&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  };
};

&lt;span class=&quot;comment&quot;&gt;// реализация не изменится&lt;/span&gt;
Promise&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;Buffer&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;  AllocBuffer &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;Size size&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
Promise&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;size_t&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;  ReadFile &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;string filename, Buffer buf&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;

&lt;span class=&quot;key&quot;&gt;template&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;key&quot;&gt;auto&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;operator&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;co_await&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;Promise&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; task&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
{
  &lt;span class=&quot;key&quot;&gt;struct&lt;/span&gt; Awaiter
  {
    Promise&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;T&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;&amp; task;
  
    &lt;span class=&quot;key&quot;&gt;bool&lt;/span&gt;  await_ready &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;    { &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; task.IsComplete&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;; }
    T     await_resume &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;   { &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; task.Result&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;; }
    
    &lt;span class=&quot;key&quot;&gt;template&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;typename&lt;/span&gt; P&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;key&quot;&gt;void&lt;/span&gt;  await_suspend &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;coroutine_handle&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;P&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; currentTask&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; {
      threadPool.Add&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; currentTask.GetTask&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;, DependsOn{task} &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
    }
  };
  &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; Awaiter{task};
};

Task  ReadData&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
{
  &lt;span class=&quot;key&quot;&gt;auto&lt;/span&gt; buf = &lt;span class=&quot;key&quot;&gt;co_await&lt;/span&gt; AllocBuffer&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; &lt;span class=&quot;digit&quot;&gt;10&lt;/span&gt;_Mb &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  size_t readn = &lt;span class=&quot;key&quot;&gt;co_await&lt;/span&gt; ReadFile&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; &lt;span class=&quot;string&quot;&gt;&amp;quot;file.name&amp;quot;&lt;/span&gt;, buf &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  ... &lt;span class=&quot;comment&quot;&gt;// делаем что-то с загруженными данными&lt;/span&gt;
}

threadPool.Add&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; ReadData&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;bound overflow&quot;&gt;&lt;p&gt;Как и первый код, корутина прерывается два раза - явно при вызове оператора co_await, но остановки потоков не происходит и в промежутках выполняются другие задачи.&lt;/p&gt;

&lt;p&gt;Далее разбирается, как реализованы &lt;a href=&quot;https://gamedev.ru/code/articles/cpp_coroutines_1?page=2&quot;&gt;корутины в C++20&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;А также приведены простые примеры написания своих корутин, все в однопоточном режиме.
&lt;br&gt;
&lt;a href=&quot;https://gamedev.ru/code/articles/cpp_coroutines_1?page=3&quot;&gt;1. Как работает Awaiter&lt;/a&gt;
&lt;br&gt;
&lt;a href=&quot;https://gamedev.ru/code/articles/cpp_coroutines_1?page=4&quot;&gt;2. Как прерывать и возобновлять выполнение корутины&lt;/a&gt;
&lt;br&gt;
&lt;a href=&quot;https://gamedev.ru/code/articles/cpp_coroutines_1?page=5&quot;&gt;3. Реализации системы тасков на корутинах&lt;/a&gt;
&lt;br&gt;
&lt;a href=&quot;https://gamedev.ru/code/articles/cpp_coroutines_1?page=6&quot;&gt;4. Как возвращать значение из корутины&lt;/a&gt;
&lt;br&gt;
&lt;a href=&quot;https://gamedev.ru/code/articles/cpp_coroutines_1?page=7&quot;&gt;5. Как организовать зависимости между корутинами&lt;/a&gt;
&lt;br&gt;
&lt;a href=&quot;https://gamedev.ru/code/articles/cpp_coroutines_1?page=8&quot;&gt;6. Как реализовать Awaiter для разных типов&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Дополнительные материалы&lt;/b&gt;
&lt;br&gt;
&lt;a href=&quot;https://en.cppreference.com/w/cpp/language/coroutines&quot; rel=&quot;nofollow&quot;&gt;Документация&lt;/a&gt;
&lt;br&gt;
&lt;a href=&quot;https://gitflic.ru/project/azhirnov/coroutine-samples.git&quot; rel=&quot;nofollow&quot;&gt;git репозиторий с примерами&lt;/a&gt;
&lt;br&gt;
&lt;a href=&quot;https://github.com/lewissbaker/cppcoro&quot; rel=&quot;nofollow&quot;&gt;cppcoro - библиотека с корутинами для файлов, сети и тд&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Материалы на английском&lt;/b&gt;
&lt;br&gt;
&lt;a href=&quot;https://www.modernescpp.com/index.php/tag/coroutines&quot; rel=&quot;nofollow&quot;&gt;Статьи на ModernesCPP&lt;/a&gt;
&lt;br&gt;
&lt;a href=&quot;https://youtu.be/tj0URCY_A1s&quot; rel=&quot;nofollow&quot;&gt;Видео: Understanding C++ Coroutines by Example, Part 1&lt;/a&gt;, &lt;a href=&quot;https://youtu.be/9p7obE9KRoU&quot; rel=&quot;nofollow&quot;&gt;Part 2&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://gamedev.ru/code/articles/cpp_coroutines_1?page=2&quot;&gt;Продолжение&lt;/a&gt;&lt;/p&gt;</description>
</item>
<item>
  <guid isPermaLink="true">https://gamedev.ru/code/news/?id=7893</guid>
  <pubDate>Sat, 05 Feb 2022 23:35:30 GMT</pubDate>
  <title>Вышла книга 3D Graphics Rendering Cookbook</title>
  <link>https://gamedev.ru/code/news/?id=7893</link>
  <comments>https://gamedev.ru/code/forum/?id=267056</comments>
  <category>Графика</category>
  <category>OpenGL</category>
  <category>Vulkan</category>
  <description>
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Вышла книга &lt;b&gt;3D Graphics Rendering Cookbook:&lt;/b&gt; A comprehensive guide to exploring rendering algorithms in modern OpenGL and Vulkan авторов Sergey Kosarevsky (&lt;a href=&quot;https://gamedev.ru/users/?id=1860&quot;&gt;_NetSurfer_&lt;/a&gt;) и Viktor Latypov (&lt;a href=&quot;https://gamedev.ru/users/?id=16426&quot;&gt;Vinil&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;В книге рассказывается как:&lt;/p&gt;

&lt;p&gt;- использовать AZDO и bindless;
&lt;br&gt;
- загружать и рисовать glTF2 с PBR;
&lt;br&gt;
- перейти на OpenGL 4.6;
&lt;br&gt;
- написать 3D движок на Vulkan 1.2;
&lt;br&gt;
- а также как не нужно делать scene graph и многое другое...&lt;/p&gt;

&lt;p&gt;За 670 страниц показан переход от одного цветного треугольника к вот такой картинке:&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://gamedev.ru/files/images/rendercookbook.jpeg&quot; alt=&quot;RenderCookBook | Вышла книга 3D Graphics Rendering Cookbook&quot; title=&quot;RenderCookBook&quot;&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;p&gt;Книга 3D Graphics Rendering Cookbook доступна для заказа на Amazon:
&lt;br&gt;
&lt;a href=&quot;https://www.amazon.com/Graphics-Rendering-Cookbook-comprehensive-algorithms/dp/1838986197&quot; rel=&quot;nofollow&quot;&gt;https://www.amazon.com/Graphics-Rendering-Cookbook-comprehensive-&amp;hellip; dp/1838986197&lt;/a&gt;&lt;/p&gt;</description>
</item>
<item>
  <guid isPermaLink="true">https://gamedev.ru/code/articles/RTBA</guid>
  <pubDate>Sat, 08 Jan 2022 13:09:24 GMT</pubDate>
  <title>Recursive Tile-based architecture</title>
  <link>https://gamedev.ru/code/articles/RTBA</link>
  <comments>https://gamedev.ru/code/forum/?id=266313</comments>
  <category>Графика</category>
  <category>mobile</category>
  <category>rasterization</category>
  <category>Reflection</category>
  <category>rtba</category>
  <category>tile-base architecture</category>
  <description>
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Данная статья вводит понятие &lt;strong&gt;Recursive Tile-Based Architecture&lt;/strong&gt; (RTBA), расширяющее возможности традиционной tile-based архитектуры. Предложенный подход позволяет работать с аппаратно-ускоренным тайловым растеризатором в терминах когерентного рейтрейсинга, каждый тайл рассматривается, как средство обработки пучка лучей.&lt;/p&gt;
&lt;p&gt;Для таких эффектов, как отражения, преломления и тени, данная архитектура позволяет уменьшить bandwidth, исключить или уменьшить работу со временными текстурами, не тратить время на обработку невидимых эффектов (заслонённых более близкими объектами), сделать рендеринг вторичных эффектов гибче &amp;mdash; увеличив их количество и качество, упростить разработку риалтаймовых рендереров за счёт упрощения кода. RTBA сохраняет обратную совместимость с классической техникой растеризации и другими методами риалтайм графики.&lt;/p&gt;

&lt;p&gt;В первую очередь, RTBA ориентирован на решение ряда проблем риалтаймовой 3D графики на мобильных платформах. Реализация данной архитектуры возможна на уровне драйверов современных GPU реализующих TBA.&lt;/p&gt;

&lt;p&gt;==Введение==&lt;/p&gt;

&lt;p&gt;Одна из ключевых проблем, с которыми сталкиваются разработчики риалтаймовых 3Д приложений, это повышение эффективности вычислений транспорта фотонов в сцене. Это особенно серьёзная проблема для мобильных устройств из-за их ограничений производительности и энергопотребления.&lt;/p&gt;

&lt;p&gt;Для расчёта транспорта фотонов используется множество техник, таких как: растеризация, path tracing, ray-marching, voxel tracing, shadow maps, beam-tracing и так далее. Каждый способ имеет свои за и против, различную точность, производительность и прочие особенности.&lt;/p&gt;

&lt;p&gt;Растеризация &amp;mdash; самый популярный метод. Она работает быстро и аккуратно, имеет аппаратную поддержку на большинстве целевых платформ. К сожалению, у неё есть ряд ограничений:&lt;/p&gt;

&lt;p&gt;1. На большинстве платформ относительно высокий оверхед на запуск растеризации, что ограничивает количество подрендеров, необходимых для расчёта вторичных эффектов.&lt;/p&gt;

&lt;p&gt;2. Растеризация происходит только в аффинном пространстве. Конечно, можно использовать clip-plane и другие техники, но этого недостаточно для ряда задач.&lt;/p&gt;

&lt;p&gt;Решения на базе трассировки лучей не имеют таких ограничений; они притягивают внимание разработчиков своей высокой гибкостью и низким порогом входа. К сожалению, в типичных игровых сценариях, расчёте на одну точку, алгоритмы трассировки лучей в десятки и сотни раз медленнее, чем типичная растеризация, даже при наличии аппаратной оптимизации. Так же, они требуют denoising, которая тоже неэффективна для мобильных платформ из-за большого bandwidth. Двойная неэффективность этого метода не даст использовать рейтрейсинг в массовых 3Д приложениях на мобильных платформах в обозримом будущем. Альтернативные методы быстрее и позволяют реализовывать большое количество эффектов уже сейчас. Хотя альтернативные методы намного сложнее в реализации, в том числе в вопросе организации их взаимодействия – игровые движки позволяют скрыть эту сложность от разработчиков конечных продуктов.&lt;/p&gt;

&lt;p&gt;Нужно отметить, что растеризацию можно рассматривать, как частный случай рейтрейсинга. В конечном счёте и там и там находится точное пересечение со сценой. В случае с растеризацией расчёт идёт для большого количества лучей, которые обязаны быть регулярным образом расположены во фраструме. Так же отметим, что аппаратные TBA растеризаторы оптимизированы для обработки небольших блоков фиксированного размера, например, Mali использует блоки 16х16 пикселей или лучей, в зависимости от интерпретации.&lt;/p&gt;

&lt;p&gt;RTBA был разработан для того, чтобы раскрыть потенциал тайловых архитектур, рассматривая аппаратные ускорители растеризации маленьких блоков, как самостоятельную функцию, напоминающую когерентный рейтрейсинг.&lt;/p&gt;

&lt;p&gt;==Tile-based architecture (TBA)==&lt;/p&gt;

&lt;p&gt;Для начала рассмотрим некоторые особенности TBA.&lt;/p&gt;

&lt;p&gt;TBA позволяет уменьшить bandwidth и снизить энергопотребление. Малые размеры рабочей части render target (тайл) позволяют разместить основные данные в кэше. Расчёты происходят с минимальной латентностью, а в основную память копируется только небольшая необходимая в дальнейшем часть данных. Например, в типичном случае работы с G-Buffer – z-buffer, нормали, идентификаторы материалов – будут присутствовать только в кэше, в виде небольших блоков фиксированного размера. Наложение прозрачных слоёв поверх, а также постобработка может произойти без обращения к основной памяти, а в конце будет скопирован только слой содержащий цвет пикселей. Некоторые вендоры даже проверяют контрольную сумму данных в тайле с контрольной суммой соответствующих данных в рендертаргете, это позволяет избежать копирования и несколько повысить энергоэффективность.&lt;/p&gt;

&lt;p&gt;Ключевой особенностью TBA является разделение процесса растеризации на две стадии:&lt;/p&gt;

&lt;p&gt;1. Модуль tiler распределяет отправленные на рендер треугольники (geom and vertex shaders) и render state по тайлам, где они могут быть видны. После распределения существует момент, когда в памяти GPU хранятся списки примитивов, которые нужно будет отправлять на растеризацию. Нужно отметить, что система хранения списков может быть нетривиальной, например иерархичной, для того чтобы сделать выделение памяти на список примитивов более предсказуемой.&lt;/p&gt;

&lt;p&gt;2. После запуска растеризации GPU обходит каждый тайл и отправляет соответствующий список примитивов на растеризацию (стадия fragment). После завершения обработки списка примитивов тайла GPU копирует требуемые слои в основную память.&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://gamedev.ru/files/images/159672_1641632545_shema1.png&quot; alt=&quot;shema1 | Recursive Tile-based architecture&quot; title=&quot;shema1&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;br&gt;
&lt;p&gt;&lt;i&gt;TBA пайплайн. Голубые блоки показывают ключевые для нас стадии.&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;Данная схема не устраняет bandwidth требуемый для чтения и записи временных текстур, которые требуются для рендеринга ряда эффектов, таких как отражения, преломления и тени. Конечно, есть и альтернативные способы рендеринга, без использования временных текстур, например shadow volumes или рендеринг отражений с помощью порталов, но и у этих способов есть свои недостатки.&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://gamedev.ru/files/images/159673_1641632689_shema2.png&quot; alt=&quot;shema2 | Recursive Tile-based architecture&quot; title=&quot;shema2&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;br&gt;
&lt;p&gt;&lt;i&gt;Пример пайплайна для более сложного рендеринга. Приходится синхронизировать данные через основную память.&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;Вернёмся к спискам примитивов. Если разработчик имеет доступ к памяти и знает её структуру (это характерно для разработчиков драйверов), данные списки примитивов могут быть заполнены соответствующими низкоуровневыми средствами, например с использованием compute shaders. Конечно можно допустить существование экзотических платформ, например с жёстким разделением памяти по отдельным функциональным модулям, но у нас нет информации о таких платформах.
&lt;br&gt;
Держа в голове данные особенности и возможности TBA давайте рассмотрим две ключевых модификации драйвера способных значимо расширить наши возможности в растеризации сцен.&lt;/p&gt;

&lt;p&gt;===Рекурсивная растеризация в TBA===&lt;/p&gt;

&lt;p&gt;====Primitive Provider====&lt;/p&gt;

&lt;p&gt;Предлагается ввести в драйвер и реализовать открытый API для новой сущности &amp;laquo;primitive provider&amp;raquo; (источник примитивов). Его можно представить как чёрный ящик, который на вход получает фрустум специфичный для отдельного тайла, а на выходе выдаёт список команд для отрисовки в том же формате, как это делает обычный tiler GPU для каждого тайла. В дальнейшем термин primitive provider будет рассматриваться, как интерфейс для&amp;nbsp; &amp;laquo;акселерационной структуры&amp;raquo;. В самом деле, если для процесса растеризации не важно откуда берутся примитивы, и главное, чтобы их можно было получить по запросу, то для полноценного игрового движка - важно, чтобы реализация выборки примитивов была максимально эффективной, она может быть сложной и реализовываться разными алгоритмами, возможно предоставляемых, как драйвером, так и разработчиком.&lt;/p&gt;

&lt;p&gt;Внешний API для акселерационной структуры уже имеет стандартизированный API, например в Vulkan. Этот API может быть использован для встроенной в драйвер реализации без изменений, или с добавлением нескольких функций ускоряющих систему с учётом специфики мобильных платформ (их обсуждение выходит за рамки статьи).&lt;/p&gt;

&lt;p&gt;Ключевой особенностью данного подхода является то, что фрустум для каждого тайла может быть полностью независимым от соседних тайлов, их конфигурация целиком определяется разработчиком приложений. Если для растеризации камеры фрустумы тайлов образуются просто нарезкой всей области видимости камеры, то для растеризации отражения от неровных поверхностей каждый фрустум может имеет собственное направление, более того - они могут пересекаться между собой.&lt;/p&gt;

&lt;p&gt;Для растеризации с использованием primitive provider требуется добавить, как минимум одну новую функцию, которая будет принимать следующие параметры:&lt;/p&gt;

&lt;p&gt;1. Акселерационная структура;&lt;/p&gt;

&lt;p&gt;2. Таблица пайплайнов рендеринга; поскольку сцена рендерится одним запросом, то и все необходимые для рендеринга данные должны быть доступны в один момент. Здесь ситуация аналогична с таблицами &lt;a href=&quot;https://gamedev.ru/code/terms/Shader&quot; title=&quot;Шейдер (Shader)&quot;&gt;шейдеров&lt;/a&gt; для рейтрейсинга, единственное, что поскольку мы работаем в концепции растеризации &amp;mdash; то и пайплайны используются из неё, тут не требуется какой-то принципиально новой функциональности. Детали будут описаны ниже.&lt;/p&gt;

&lt;p&gt;3. Источник фрустумов; В простом варианте просто указатель на их массив в памяти, либо шейдер, который их сгенерирует по запросу;&lt;/p&gt;

&lt;p&gt;4. Куда рендерить. Опционально &amp;mdash; можно указывать маску актуальных тайлов.&lt;/p&gt;

&lt;p&gt;Уже данной функции достаточно для того, чтобы сильно расширить возможности разработчика:&lt;/p&gt;

&lt;p&gt;1.&amp;nbsp; Растут возможности по оптимизации растеризации за счёт использования акселерационных структур, лучшего клиппинга. Интеграция акселерационной структуры в драйвер, вместо использования внешней, позволяет убрать затраты на передачу геометрии от пользовательской структуры, существующей в большинстве игровых движков, в драйвер с помощью вызовов draw. Акселерационная структура может писать непосредственно в нужные участки памяти.&lt;/p&gt;

&lt;p&gt;2.&amp;nbsp; Становится возможным сформировать сложные пространства камеры, вместо простой пирамиды. Например, становится возможным за один вызов создавать области рендеринга эффективно описывающие отражения или преломления от криволинейных поверхностей. Можно попробовать рендерить shadow map с очень сложной структурой каскадов, так как каждый тайл может быть подстроен под сцену.&lt;/p&gt;

&lt;p&gt;3.&amp;nbsp; Становится возможным обновлять render target частично, экономя ресурсы. Например, возможно в reflection probe обновлять только те части, в область видимости которых попали какие-то анимации. То же самое с shadow map.&lt;/p&gt;

&lt;p&gt;4.&amp;nbsp; Становится возможным реализовывать variate rate shading нескольких типов: например, отрендерив один тайл, или набор из 2х2 тайлов в зоне мало актуальных частей сцены, копировать их в финальных рендертаргет с растягиванием.&lt;/p&gt;

&lt;p&gt;5.&amp;nbsp; Значительно упрощается программирование графических движков, так как драйвер может отрендерить сцену самостоятельно: не нужно думать о порядках отправки геометрии и материалов, ни о множестве оптимизаций, которые драйвер берёт на себя.&lt;/p&gt;

&lt;p&gt;====Re-rasterization of a tile====&lt;/p&gt;

&lt;p&gt;Вторым ключевым нововведение должна стать возможность запустить процедуру рендеринга тайла несколько раз, до выгрузки его данных в основную память.&lt;/p&gt;

&lt;p&gt;Это может быть реализовано с помощью последовательного вызова различных типов шейдеров, которые будут использовать память тайла для обработки различных фаз рендеринга. Что-то подобное уже есть, например, в случае deferred shading с использованием G-Buffer, последующим наложением прозрачных объектов и постпроцессингом. Данный подход позволяет уменьшить bandwidth для типичных игровых сценариев. Мы предлагаем развить данный подход, позволив вызывать так же compute shaders и другие стадии растеризации, в условиях, когда данные тайла остаются в быстром кеше, либо выгружаются в кеш более низкого уровня, что должно быть всё-ещё быстрее, чем копировать их в финальный рендертаргет.&lt;/p&gt;

&lt;p&gt;Это позволяет произвести анализ тайла после первичной растеризации, определить, какие необходимы вторичные эффекты и их параметры. Это может быть серия compute shader, которые идентифицируют соответствующие материалы, сформируют для них новые фрустумы (простейший случай – отражения). Затем происходит один или несколько вызовов шейдеров, реализующих функциональность акселерационной структуры. Она заполняет новый список треугольников и цикл растеризации повторяется. Результаты новой растеризации совмещаются с предыдущим состоянием тайла. Цикл может повторяться многократно до тех пор, пока это необходимо. Это может позволить эффективно реализовывать простые формы таких эффектов, как отражение, преломление, тени, &lt;a href=&quot;https://gamedev.ru/code/terms/SSS&quot; title=&quot;SSS: Subsurface scattering (Подповерхностное рассеивание)&quot;&gt;SSS&lt;/a&gt;, OIT.&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://gamedev.ru/files/images/159674_1641632863_shema3.png&quot; alt=&quot;shema3 | Recursive Tile-based architecture&quot; title=&quot;shema3&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;br&gt;
&lt;p&gt;&lt;i&gt;Пример растеризации тайла с отражением. Шаги растеризации синхронизируются через кеш.&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;В качестве удобного и эффективного способа реализации предложенной схемы, с учётом особенностей и&amp;nbsp; ограничений мобильных GPU, предлагается новая конструкция: дерево возможных bounce (bounce tree), где bounce это блок задач связанных с поиском пересечения группы лучей со сценой и первичная обработка найденных пересечений. Например, в типичном случае, дерево начинается с камеры, первый bounce соответствует первичному рендеру. Каждая ветка в дереве определяет некоторый тип спецэффектов. Внутри каждой ветки может происходить несколько подрендеров для обработки распространения лучей в разных направлениях. Это может быть необходимо, если, допустим, внутри одного тайла сошлось несколько отражающих поверхностей.&lt;/p&gt;

&lt;p&gt;Bounce Tree позволяет формализовать алгоритм рендеринга сцены в рамках развитой системы растеризации. В результате, за один вызов, драйвер TBA GPU может более эффективно отправить на рендер такие эффекты, как: множественный рендеринг сцены (для реализации сложных эффектов), обработку отражений, преломлений, SSS и их произвольных сочетаний.&lt;/p&gt;

&lt;p&gt;Например, дерево может содержать следующие bounce: рендеринг непрозрачных частей сцены, отражений, преломлений, прозрачности, UI. Это дерево позволит драйверу отрендерить всё и вернуть готовый тайл уже со всеми эффектами.&lt;/p&gt;

&lt;p&gt;Для реализации рекурсивной растеризации требуется ввести две группы управляющих программ (шейдеров):&lt;/p&gt;

&lt;p&gt;Первая, предназначенная для анализа и постобработки тайлов, обозначается &amp;laquo;Advanced Tile Filter&amp;raquo; или ATF. Шейдеры ATF могут быть вызваны на различных стадиях пайплайна рекурсивной растеризации, далее они будут идентифицироваться, как ATF A, ATF B, ATF C и ATF D.&lt;/p&gt;

&lt;p&gt;ATF A предназначен для первичной постобработки тайла после его растеризации, а так же определяет – необходимо ли выполнение соответствующих веток bounce tree (подрастеризаций), и&amp;nbsp; заполняет массив подфрустумов. Эти фрустумы будут использованы драйвером для получения новых списков примитивов.&lt;/p&gt;

&lt;p&gt;ATF B может быть запущен немедленно после выполнения дочернего bounce. Цель данного шейдера – объединить данные, полученные в ходе основной и дополнительных растеризаций. Например: добавить отражение на основное изображение сцены.&lt;/p&gt;

&lt;p&gt;ATF C может быть запущен после того, как все подрендеры были завершены.&lt;/p&gt;

&lt;p&gt;ATF D может быть запущен, когда подрендеров нет.&lt;/p&gt;

&lt;p&gt;Реализация ATF основана на функциональности compute shader, но некоторые стадии могут быть fragment.&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://gamedev.ru/files/images/159675_1641633060_risunok1.png&quot; alt=&quot;shema4 | Recursive Tile-based architecture&quot; title=&quot;shema4&quot;&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;br&gt;
&lt;p&gt;Структура первичного и вторичных потайловых растеризаций.&lt;/p&gt;

&lt;p&gt;Второй тип шейдеров необходим для определения логики рекурсивной растеризации, мы называем его “Tile Rendering Management” или TRM. TRM позволяет регулировать:
&lt;br&gt;
-&amp;nbsp; Число подрендеров;
&lt;br&gt;
-&amp;nbsp; Параметры обращения к акселерационной структуре;
&lt;br&gt;
-&amp;nbsp; Определяет условия прекращения рекурсии;&lt;/p&gt;

&lt;p&gt;Несмотря на то, что TRM должен быть шейдером при реализации в драйвере, однако для внешних пользователей он может быть определён просто через структуру с параметрами: для всех основных сценариев рендеринга этого предположительно хватит.&lt;/p&gt;

&lt;p&gt;==Пайплайн рендеринга RTBA==&lt;/p&gt;

&lt;p&gt;Мы предлагаем рассмотреть пайплайн рендеринга в виде конечной машины состояний. Рекурсивная растеризация начинается с получения начального фрустума и заканчивается копированием необходимых слоёв тайла в рендертаргет.&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://gamedev.ru/files/images/159676_1641633231_shema5.png&quot; alt=&quot;shema5 | Recursive Tile-based architecture&quot; title=&quot;shema5&quot;&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;br&gt;
&lt;p&gt;&lt;i&gt;Конечная машина состояний для RTBA&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;===Акселерационная структура===&lt;/p&gt;

&lt;p&gt;Полноценное описание требований к акселерационной структуре выходит за пределы данной статьи. Отметим несколько пунктов:&lt;/p&gt;

&lt;p&gt;1.&amp;nbsp; Акселерационная структура может быть, как встроенной, так и предоставленной разработчиком.&lt;/p&gt;

&lt;p&gt;2.&amp;nbsp; Поставщиков примитивов может быть несколько, TRM позволяет определить текущий. Это необходимо, как минимум для того, чтобы в играх можно было раздельно рендерить уровни и UI.&lt;/p&gt;

&lt;p&gt;3.&amp;nbsp; Исходный tiler может быть одним из доступных типов поставщиков примитивов работающих в рядовых случаях.&lt;/p&gt;

&lt;p&gt;4.&amp;nbsp; В большинстве случаев, фрустумы по которым идёт выборка – узкие, боковые грани почти параллельны друг-другу. Можно оценить, что если ширина экрана 1600 пикселей, а соответствующий угол фрустума камеры 120 градусов, то угол фрустума одного тайла будет ~1.2 градуса.&lt;/p&gt;

&lt;p&gt;5.&amp;nbsp; Существует множество оптимизаций для того, чтобы не обрабатывать одиночные фрустумы, а группировать их различными способами и обрабатывать сообща. По нашим расчётам, со всеми оптимизациями, совокупное количество запросов к акселерационной структуре для типичной игры будет порядка 5000–50000&amp;nbsp; в секунду, время на данные вычисления будет незначительным на фоне общего рендеринга.&lt;/p&gt;

&lt;p&gt;6.&amp;nbsp; Акселерационная структура может учитывать LoD объектов, а также детектировать перекрытие видимости для фрустума (clipping).&lt;/p&gt;

&lt;p&gt;7.&amp;nbsp; Акселерационная структура совмещается с системами генерации геометрии, так как только она является поставщиком примитивов.&lt;/p&gt;

&lt;p&gt;8.&amp;nbsp; Одной из серьёзных проблем является рендеринг полупрозрачных объектов. Эта проблема может решаться размещением таких объектов в дополнительных акселерационных структурах, и организации специальных bounce для их рендеринга. Аналогично происходит сейчас при рендеринге с использованием G-buffer. Так же мы демонстрировали работу OIT на базе depth peeling: в случае RTBA этот метод может работать достаточно эффективно для простых случаев.&lt;/p&gt;

&lt;p&gt;===Pipeline Binding Table===&lt;/p&gt;

&lt;p&gt;Для растеризации всех этапов рендеринга сцены драйверу должен быть предоставлена структура всех возможным пайплайнам рендеринга. Это предлагается сделать с помощью введения новой сущности: Pipeline Binding Table. Каждому баунсу сопоставляется такая таблица.&lt;/p&gt;

&lt;p&gt;Запись в этой таблице в качестве значения принимает пайплайн аналогичный пайплайнам Vulkan, а ключом выступает индекс идентификатор материала соответствующий идентификатору материала в акселерационной структуре. &lt;/p&gt;

&lt;p&gt;===Local Tile Memory Management===&lt;/p&gt;

&lt;p&gt;RTBA требует расширение возможностей для разработчика управлять данными тайла, определять их расположение в кэшах GPU. Например, при рендеринге сцены с отражениями, рендеринг основной сцены может потребовать 4-5 слоя в тайле: цвет, нормаль, глубина, материал, depth. После шейдинга, ATF A обходит слои с материалом, глубиной и нормалью и строит под-фрустумы для отражающих поверхностей. После чего нормаль, глубина, depth становятся неактуальными. Слой с материалом должен быть остаться актуальным, т.к. нужно понимать, какие пикселы куда и как отражают. Два актуальных слоя могут остаться в той же памяти, или быть скопированными в кэш более высокого уровня. После вторичного рендера, который может быть проще первичного – ATF C совмещает сохранённый слой с цветом с отражением, с использованием маски (бывшего слоя с материалами).&lt;/p&gt;

&lt;p&gt;==Заключение==&lt;/p&gt;

&lt;p&gt;Существует множество разнородных техник позволяющих реализовывать эффекты реалтаймовой графики. Индустриальные решения вынуждены искать оптимальный баланс по качеству сцен, качеству и количеству эффектов, разрешению, энергоэффективности. Какого-то одного эффективного метода рендеринга пока ожидать не приходится, компании желающие обеспечить высокое качество своего продукта в сравнении с конкурентами вынуждены вкладываться в реализацию сложных методов рендеринга, реализовывать множество разных методик для оптимизации отдельных аспектов рендеринга.&lt;/p&gt;

&lt;p&gt;Способность просто растеризовать сложную сцену без учёта вторичных эффектов уже не является какой-то проблемой. Современные девайсы вполне способны выводить миллионы треугольников в кадре, что сопоставимо с количеством пикселей на экране. Более важным становится запрос на рендеринг вторичных эффектов: реалистичных отражений, преломлений, теней, GI и т.п. Вторичность эффектов подразумевает всё большую связность сцены, её уже неэффективно представлять простыми списками треугольников, даже при растеризации. Поэтому, можно ожидать тенденций расширения практики использования акселерационных структур и всё большей поддержки их в драйверах.&lt;/p&gt;

&lt;p&gt;Эта тенденция очень органично ложится на тайловые архитектуры, которые так же расширяют свои позиции на рынке благодаря своей способности повышать эффективность использования дорогих видов памяти и повышать энергоэффективность.&lt;/p&gt;

&lt;p&gt;Две эти тенденции объединяются в предложенном формате RTBA. Это достаточно узкоспециализированная система, она никак не помогает решениям, работающим в пространстве экрана или развитию некогерентного рейтрейсинга. Но она позволяет эффективно решить или повысить эффективность решения ряда первоочередных задач рендеринга на мобильных платформах:&lt;ul&gt;
&lt;br&gt;
&lt;li&gt;упростить и повысить эффективность растеризации сложных сцен.&lt;/li&gt;
&lt;li&gt;упростить и повысить эффективность рендеринга “когерентных” хорошо видимых эффектов, таких как отражения, преломления, тени.&lt;/li&gt;
&lt;li&gt;дать новые возможности по рендерингу, например в плане повышения качества рендеринга отражений от криволинейных поверхностей.&lt;/li&gt;
&lt;li&gt;упростить разработку в случае интеграции решения в API Vulkan: разработчику не нужно самому отправлять контент на рендеринг, решать в каком порядке, определять зависимости, решать – нужен ли какой-нибудь z-prepass. Это всё отходит драйверу, который лучше знает, что нужно на конкретном железе.&lt;/li&gt;
&lt;li&gt;ускорить решения на OpenGL: по всё тем же причинам, вопросы синхронизации и отправки контента на рендеринг уходят в драйвер.&lt;/li&gt;
&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;Обратная связь:
&lt;br&gt;
nikita.glushkov@ngpraxis.com&lt;/p&gt;

&lt;p&gt;==Список литературы==
&lt;br&gt;
 
&lt;br&gt;
J. Hart, &amp;quot;Sphere Tracing: A Geometric Method for the Antialiased Ray Tracing of Implicit Surfaces,&amp;quot; The Visual Computer, 1995. 
&lt;br&gt;
J. Amanatides and A. Woo, &amp;quot;A Fast Voxel Traversal Algorithm for Ray Tracing,&amp;quot; in EuroGraphics, 1987. 
&lt;br&gt;
C. Barr&amp;#233;-Brisebois, H. Hal&amp;#233;n, G. Wihlidal, A. Lauritzen, J. Bekkers, T. Stachowiak and J. Andersson, &amp;quot;Hybrid Rendering for Real-Time Ray Tracing,&amp;quot; in Ray Tracing Gems, Berkeley, CA, Apress, 2019, pp. 437-473.
&lt;br&gt;
H. Fuchs, J. Poulton, J. Eyles, T. Greer, J. Goldfeather, D. Ellsworth, S. Molnar, G. Turk, B. Tebbs and L. Israel, &amp;quot;Pixel-Planes 5: A Heterogeneous Multiprocessor Graphics System,&amp;quot; in SIGGRAPH&apos;89:Proceedings of the 16th annual conference on Computer graphics and interactive techniques., 1989. 
&lt;br&gt;
R. Barringer, . C. J. Gribel, . A. Lefohn and T. G. Akenine-M&amp;#246;ller, &amp;quot;Graphics tiling architecture with bounding volume hierarchies&amp;quot;. Santa Clara, CA Patent 8823736, 02 09 2014.
&lt;br&gt;
G. Muthler, T. Karras, S. Laine, W. P. Newhall, . R. C. Babich, . J. Burgess and . I. Llamas, &amp;quot;Method for continued bounding volume hierarchy traversal on intersection without shader intervention&amp;quot;. Patent 20210005010, 07 01 2021.
&lt;br&gt;
The Group, &amp;quot;Vulkan® 1.1.178 - A Specification (with all registered Vulkan extensions),&amp;quot; [Online]. Available: &lt;a href=&quot;https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#acceleration-structure&quot; rel=&quot;nofollow&quot;&gt;https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html&amp;hellip; ion-structure&lt;/a&gt;. [Accessed 20 05 2021].
&lt;br&gt;
N. Glushkov and E. Tyuleneva, &amp;quot;Projection matrices and related viewing frustums: new ways to create and apply,&amp;quot; 19 05 2021. [Online]. Available: &lt;a href=&quot;https://arxiv.org/abs/2105.09476&quot; rel=&quot;nofollow&quot;&gt;https://arxiv.org/abs/2105.09476&lt;/a&gt;. [Accessed 21 05 2021].
&lt;br&gt;
C. Magnerfelt, &amp;quot;Transparency with Deferred Shading,&amp;quot; Royal Institute of Technology, Stockholm, Sweden, 2012.
&lt;br&gt;
The Group, &amp;quot;Vulkan® 1.2.178 - A Specification (with KHR extensions),&amp;quot; [Online]. Available: &lt;a href=&quot;https://www.khronos.org/registry/vulkan/specs/1.2-khr-extensions/html/chap34.html#shader-binding-table&quot; rel=&quot;nofollow&quot;&gt;https://www.khronos.org/registry/vulkan/specs/1.2-khr-extensions/&amp;hellip; binding-table&lt;/a&gt;. [Accessed 20 05 2021].
&lt;br&gt;
ARM, &amp;quot;Documentation – Arm Developer,&amp;quot; [Online]. Available: &lt;a href=&quot;https://developer.arm.com/documentation/101811/0101/The-Memory-Management-Unit-MMU&quot; rel=&quot;nofollow&quot;&gt;https://developer.arm.com/documentation/101811/0101/The-Memory-Ma&amp;hellip; ment-Unit-MMU&lt;/a&gt;. [Accessed 20 05 2021].
&lt;br&gt;
G. Gribb and K. Hartmann, &amp;quot;Fast Extraction of Viewing Frustum Planes from the WorldView-Projection Matrix,&amp;quot; 2001. [Online]. Available: &lt;a href=&quot;https://www.gamedevs.org/uploads/fast-extraction-viewing-frustum-planes-from-world-view-projection-matrix.pdf&quot; rel=&quot;nofollow&quot;&gt;https://www.gamedevs.org/uploads/fast-extraction-viewing-frustum-&amp;hellip; on-matrix.pdf&lt;/a&gt;. [Accessed 20 05 2021].
&lt;br&gt;
L. Yuanyuan, B. Hai and L. Guizi, &amp;quot;Graphics processing unit operation&amp;quot;. Santa Clara, California Patent WO2017112403A1, 29 06 2017.
&lt;br&gt;
A. Shcherbakov and V. Frolov, &amp;quot;Dynamic Radiosity,&amp;quot; in WSCG&apos;2019 - 27. International Conference in Central Europe on Computer Graphics, Visualization and Computer Vision&apos;2019, Plzen, Czech Republic, 2019. 
&lt;br&gt;
J. Yu, L. McMillan and P. Sturm, &amp;quot;Multiperspective modeling, rendering, and imaging,&amp;quot; in SIGGRAPH Asia &apos;08: ACM SIGGRAPH ASIA 2008 courses, New York, NY, United States, 2008. 
&lt;br&gt;
L. Baoquan, W. Li-Yi, Y. Xu, M. Chongyang, X. Ying-Qing, G. Baining and W. Enhua, &amp;quot;Non-Linear Beam Tracing on a GPU,&amp;quot; Computer Graphics Forum, vol. 30, no. 8, pp. 2156-2169, 2011. 
&lt;br&gt;
&amp;quot;Inigo Quilez: articles: distance functions,&amp;quot; [Online]. Available: &lt;a href=&quot;https://www.iquilezles.org/www/articles/distfu&quot; rel=&quot;nofollow&quot;&gt;https://www.iquilezles.org/www/articles/distfu&lt;/a&gt;&lt;/p&gt;</description>
</item>
<item>
  <guid isPermaLink="true">https://gamedev.ru/code/articles/TreeAndWind</guid>
  <pubDate>Fri, 09 Oct 2020 08:05:53 GMT</pubDate>
  <title>Имитация раскачивания дерева под воздействием ветра</title>
  <link>https://gamedev.ru/code/articles/TreeAndWind</link>
  <comments>https://gamedev.ru/code/forum/?id=255686</comments>
  <category>Физика</category>
  <category>шейдеры</category>
  <category>Visual Basic</category>
  <description>
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Статья демонстрирует простой метод имитации воздействия ветра на дерево (куст), реализованный на вертексном шейдере.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://gamedev.ru/files/images/149975_1602237686_tree.png&quot; alt=&quot;tree | Имитация раскачивания дерева под воздействием ветра&quot; title=&quot;tree&quot;&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;p&gt;К статье прилагаются три примера, содержащие скомпилированные EXE файлы и их исходники на языке Visual Basic из состава Microsoft Visual Studio 2008 Express. Для запуска EXE файлов необходимо иметь установленными NET Framework 2.0, DirectX9.0c и Managed DirectX. Видеоадаптер должен поддерживать вертексные шейдеры vs_1_1.
&lt;br&gt;
Примеры следуют один за другим, шаг за шагом описывая построение программы.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Step1&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;Этот пример подготовительный. В нем мы готовим скелет дерева, с которым будем работать в дальнейшем. То есть мы будем использовать не готовую модель, а только ее имитацию, достаточную для оценки работы физики. Дерево можно создать на основе фрактала по такой схеме:
&lt;br&gt;
&amp;nbsp; 1.&amp;nbsp; Имеем 2 вектора, начальную позицию и направление.
&lt;br&gt;
&amp;nbsp; 2.&amp;nbsp; Позиция задает начало, а сумма позиции и направления конец отрезка, который будет добавлен в нашу модель.
&lt;br&gt;
&amp;nbsp; 3.&amp;nbsp; Считаем новой позицией конец отрезка.
&lt;br&gt;
&amp;nbsp; 4.&amp;nbsp; Трижды вычисляем новое направление, отклоняя старое направление в разные стороны на некоторый угол, длина вектора нового направления должна быть несколько короче старого (вводим коэффициент). Рекурсивно переходим к п.2 для каждой новой пары позиция-направление.
&lt;br&gt;
Таким образом, первый отрезок становится стволом дерева, от ствола отходит три ветви, несколько короче самого ствола, от каждой ветви еще три ветви и т. д. Глубину рекурсии необходимо ограничить. На языке Visual Basic это выглядит так:&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;  Private Sub AddStick(ByVal Level As Integer, ByVal pV As Integer, ByVal Dir As Vector3)
    Dim d, vS, vC As Vector3
    Dim p As Integer, s As Single
    pVert = pVert + 1
    Vert(pVert).Position = Vert(pV).Position + Dir
    Ind(pInd) = pV
    pInd = pInd + 1
    Ind(pInd) = pVert
    pInd = pInd + 1

    If Level = 1 Then Exit Sub

    StickLen = StickLen * 0.7
    d = Vector3.Normalize(Dir) * StickLen
    s = StickLen * 0.6
    vS = Vector3.Normalize(Vector3.Cross(Dir, New Vector3(Rnd, Rnd, Rnd))) * s
    vC = Vector3.Normalize(Vector3.Cross(Dir, vS)) * s

    p = pVert
    AddStick(Level - 1, p, vS * 0.5 + vC * 0.866 + d * (Rnd() + 1.5) * 0.5)
    AddStick(Level - 1, p, vS * -1 + d * (Rnd() + 1.5) * 0.5)
    AddStick(Level - 1, p, vS * 0.5 + vC * -0.866 + d * (Rnd() + 1.5) * 0.5)
    StickLen = StickLen / 0.7
  End Sub&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Аргументами процедуры являются максимальный уровень рекурсии и указатель на вертекс, содержащий координаты позиции и направление. Для того, чтобы дерево не было фракталом идеальной формы, а выглядело естественно, в длину и направление вычисляемых векторов вносятся небольшие случайные поправки.
&lt;br&gt;
Более подробно останавливаться на генерации фрактала не буду, тема статьи другая, и в интернете достаточно информации на эту тему.
&lt;br&gt;
Формат вертекса модели пока содержит только Position, для отрисовки используется простейший вертексный шейдер:&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;vs_1_1

dcl_position v0
def c8, 1, 1, 1, 0

m4x4 oPos, v0, c0
mov oD0, c8&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Просто перерасчитываем координаты и выводим в oD0 белый цвет, заданный в константе c8. В константы c0-c3 загружаем матрицу, обобщающую трансформации проекции, видовую и мировую:&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;    Dim M1 As Matrix
    Dim M2 As Matrix

    M1 = Dev.Transform.World
    M2 = Dev.Transform.View
    M1 = Matrix.Multiply(M1, M2)
    M2 = Dev.Transform.Projection
    M1 = Matrix.Multiply(M1, M2)
    M2 = Matrix.TransposeMatrix(M1)
    Dev.SetVertexShaderConstant(0, M2)&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Вот и все, посмотрите пример в папке Step1, в нем двойным щелчком мыши можно генерировать новое дерево, левой кнопкой вращаем камеру вокруг дерева, которое пока статично. Уровень рекурсии 7, что соответствует 1094 вертексов (1093 ветви).&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Step2&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;Теперь немного теории. Зададимся целью вносить в координаты вертексов некоторую регулируемую поправку, имитирующую сгибание ветки от ветра. Можно вычислять матрицу поворота вокруг оси, проходящей через начало ветки и перпендикулярной вектору силы (ветра), это было бы правильно, но очень громоздко. При уровне рекурсии 7 крайние ветви должны испытывать отклонение по семи матрицам! Это тяжелые расчеты, и это сильно &amp;laquo;потяжелевший&amp;raquo; вертекс. Попробуем найти более простой, пусть и не столь точный метод. Будем просто смещать вершину ветки в нужном направлении, наша задача найти это направление. Ветер не может быть вертикальным, то есть наша задача найти вектор смещения для ветра единичной силы для каждой ветки для двух направлений ветра, – вдоль оси X и вдоль оси Z, – и записать в вертексы эти вектора. Далее в шейдере останется умножить первый вектор на компоненту X ветра, второй на компоненту Z и просуммировать полученные вектора – получим результирующее смещение.
&lt;br&gt;
Смещение может быть только перпендикулярно направлению ветки (ведь ветка не может изменять длину), оно должно лежать в одной плоскости с векторами направлений ветки и ветра. Чтобы найти такой вектор, достаточно найти Cross продукт от вектора, перпендикулярного ветке и ветру, и вектора самой ветки. А перпендикулярный вектор так же находится Cross продуктом векторов ветра и ветки:
&lt;br&gt;
 &lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;overflow full&quot;&gt;&lt;pre&gt;v = Vector3.Normalize(Vector3.Cross(Dir, Vector3.Cross(New Vector3(1, 0, 0), Dir))) * Dir.Length&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;bound&quot;&gt;
&lt;p&gt;Длина вектора смещения должна быть пропорциональна длине ветки, поэтому мы нормализовали результат и умножили на длину ветки.
&lt;br&gt;
Это для ветра вдоль оси X. То же самое для ветра вдоль оси Z:
&lt;br&gt;
 &lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;overflow full&quot;&gt;&lt;pre&gt;v = Vector3.Normalize(Vector3.Cross(Dir, Vector3.Cross(New Vector3(0, 0, 1), Dir))) * Dir.Length&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;bound&quot;&gt;
&lt;p&gt;Чтобы учитывать то, что кроме собственных колебаний, ветка подвержена колебаниям несущих ветвей, будем перед записью в вертекс вектора суммировать его с соответствующим вектором несущей ветки, а тот вектор уже просуммирован с соответствующим вектором своей несущей ветки и т. д. до основания ствола, которое, естественно, неподвижно. Кроме того, введем зависимость от толщины ветки – чем ветка тоньше, тем она легче гнется, будем умножать вектора не на длину, а на некоторую величину, зависящую от уровня ветки в рекурсии, можно задать такую величину таблично, а можно и подобрать формулу имперически, например, так:&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;s = Dir.Length * (0.3 + meLevels - Level)&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Здесь meLevels –&amp;nbsp; уровень рекурсии ствола, а Level – уровень рекурсии текущей ветки.
&lt;br&gt;
Теперь в вертексе кроме координат содержатся еще два вектора, задающие смещения при ветре вдоль осей X и Z. В вертексный шейдер их можно передать, например, как нормаль и бинормаль (а можно как-нибудь по-другому, вектор – он и есть вектор). Направление ветра передаем в шейдер через константу c4:&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;Dim w As Vector4
w.X = Wind.X
w.Y = 0
w.Z = Wind.Z
w.W = 0
Dev.SetVertexShaderConstant(4, w)&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;А сам шейдер выглядит так:&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;vs_1_1

dcl_position v0
dcl_normal v1
dcl_binormal v2

def c5, 1, 1, 1, 0

mov r0.w, c5.w
mul r0.xyz, v1.xyz, c4.x
add r1, r0, v0

mul r0.xyz, v2.xyz, c4.z
add r1, r1, r0

m4x4 oPos, r1, c0
mov oD0, c5&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Тут вычисляется, как уже было сказано чуть выше, сумма произведений векторов-коэффициентов на соответствующие&amp;nbsp; компоненты вектора ветра, компонента w приравнивается нулю, и само смещение суммируется с координатами вершины. В примере Step2, как и в предыдущем, левой кнопкой вращаем камеру, правой направление ветра, а клавишами&amp;nbsp; 1 – 9 задаем силу ветра. Мы видим сгибающееся в разных направлениях дерево, но на ветер это пока не похоже – не хватает хаотичности. При смене силы ветра дерево меняет вид мгновенно – то есть не хватает еще и инерции.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Step3&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;Хаотичность (неравномерность) ветра хорошо описывает так называемый шум Перлина. Создадим процедуры для его генерации и интерполяции для выборки промежуточных значений:&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;overflow full&quot;&gt;&lt;pre&gt;  Private Function GetNoise(ByVal s As Single) As Single
    Dim n1, n2 As Integer
    Dim t As Single
    n1 = Int(s)
    t = s - n1
    n1 = n1 And 255
    n2 = (n1 + 1) And 255
    GetNoise = Noise(n1) * (1 - t) + Noise(n2) * t
  End Function

  Private Sub GenNoise()
    Dim n As Integer, dn As Integer
    dn = 32
    Do
      For n = dn To 255 Step dn * 2
        Noise(n) = (Noise(n - dn) + Noise((n + dn) And 255)) * 0.5 + (Rnd() - 0.5) * dn / 16
      Next n
      dn = dn &amp;gt;&amp;gt; 1
      If dn = 0 Then Exit Do
    Loop
  End Sub&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;bound&quot;&gt;&lt;p&gt;Подробнее не останавливаюсь, про шум Перлина информации достаточно.
&lt;br&gt;
Инерция подразумевает постепенное изменение позиции при мгновенно изменившейся силе, для ее непосредственной имитации необходимо результат работы шейдера сохранять в вертексе до следующего кадра, во-первых, это заняло бы много памяти, во-вторых у нас нет такой возможности, поэтому опять будет фейк. Постепенно изменять будем сам ветер. Можно использовать обычную линейную интерполяцию между предыдущим и текущим значениями ветра по времени:&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;sv4 = sv4 * (1 - dt) + dv4 * dt&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Здесь sv4 – статичный (сохраняющий значение между вызовами процедуры) вектор, dv4 – вектор ветра, а dt – время, прошедшее с предыдущего кадра. И сразу внесем хаотичность, добавим к sv4 случайную величину, заданную шумом Перлина:&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;dv4.X = v.X * (1 - 0.5 * GetNoise(t * 63))
dv4.Y = 0
dv4.Z = v.Z * (1 - 0.5 * GetNoise(t * 50))
dv4.W = 0&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Константы 63 и 50 подобраны имперически, это скорость движения по функции шума компонент X и Z, они заданы разными, таким образом, ветер будет менять не только силу, но и направление.
&lt;br&gt;
Уже стало реальнее, но добавим последний штрих – резонансные случайные движения тонких веток, зависящие от силы ветра. Добавим в вертекс три коэффициента, вместе они прекрасно уместятся в незанятый еще Tangent. Для трех верхних ступеней рекурсии заполним их случайными числами так, чтобы для тонких веток значения были больше:&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;    If Level = 1 Then
      s = Dir.Length * 0.3
    ElseIf Level = 2 Then
      s = Dir.Length * 0.15
    ElseIf Level = 3 Then
      s = Dir.Length * 0.075
    Else
      s = 0
    End If
    v = New Vector3(s * (Rnd() - 0.5), s * (Rnd() - 0.5), s * (Rnd() - 0.5))&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Мы внесли сразу три коэффициента, будем отправлять в шейдер три соответствующие выборки шума:&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;    v4.X = GetNoise(QTime() * 33) * 10 * s
    v4.Y = GetNoise(QTime() * 52) * 10 * s
    v4.Z = GetNoise(QTime() * 94) * 10 * s
    Dev.SetVertexShaderConstant(5, v4)&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Константы 33, 52, 94 определяют скорость дрожания веток, можно подобрать другие величины на свой вкус. Константа 10 определяет амплитуду колебаний, а s – сила ветра (длина вектора).
&lt;br&gt;
Мы не зря записали в вертексы случайные коэффициенты, на колебания разных веток выборки шума будут сказываться в разной степени, ветки будут двигаться не синхронно. Наша задача – в шейдере перемножить соответствующие коэффициенты на выборки шума и просуммировать эти величины. И с этой задачей прекрасно справится единственная инструкция dp3! Итак, наш шейдер:&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;vs_1_1

dcl_position v0
dcl_normal v1
dcl_binormal v2
dcl_tangent v3

def c6, 1, 1, 1, 0

dp3 r2, v3, c5

add r3, r2, c4.xxxw
mul r0, v1, r3
add r1, r0, v0

add r3, r2, c4.zzzw
mul r0, v2, r3
add r1, r1, r0

m4x4 oPos, r1, c0
mov oD0, c6&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;В примере Step3 глубина рекурсии увеличена до 8 (3281 ветка), управление такое же, как и в Step2. Реализованы инерция, неравномерность и случайные колебания тонких веток.
&lt;br&gt;
Статья окончена, в качестве творческого задания предлагаю разработать алгоритм записи таких же коэффициентов в уже готовую, созданную моделлерами, модель дерева.&lt;/p&gt;

&lt;p&gt;Примеры к статье: &lt;b&gt;&lt;a href=&quot;https://gamedev.ru/files/?id=149973&quot;&gt;Дерево на ветру&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;</description>
</item>
<item>
  <guid isPermaLink="true">https://gamedev.ru/code/articles/PhotometricProfile</guid>
  <pubDate>Wed, 03 Jun 2020 18:53:51 GMT</pubDate>
  <title>Освещение с использованием фотометрического профиля</title>
  <link>https://gamedev.ru/code/articles/PhotometricProfile</link>
  <comments>https://gamedev.ru/code/forum/?id=252653</comments>
  <category>Графика</category>
  <description>
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Фотометрический профиль (ФП) описывает распределение интенсивности света точечного источника, полученные с помощью соответствующего измерительного оборудования. Используя данные ФП можно добиться эффекта симуляции сложных теней и нестандартного затухания света.
&lt;br&gt;
&amp;nbsp; &lt;/p&gt;

&lt;p&gt;==1. Немного теории==&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://gamedev.ru/files/images/photometric_profile.jpg&quot; alt=&quot;photometric profile | Освещение с использованием фотометрического профиля&quot; title=&quot;photometric profile&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;p&gt;В основе лежит &lt;strong&gt;Фотометрия&lt;/strong&gt; &amp;mdash; научная дисциплина, на основании которой производятся количественные измерения энергетических характеристик поля излучения. Первый из законов фотометрии &amp;mdash; закон обратных квадратов (Inverse-square law) был впервые сформулирован Иоганнном Кеплером в 1604 году:&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://gamedev.ru/files/images/inverse_square_law.png&quot; alt=&quot;Inverse square law | Освещение с использованием фотометрического профиля&quot; title=&quot;Inverse square law&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;p&gt;&lt;span class=&quot;mathjax&quot;&gt;\(E = \frac{I}{r^2}\cos i\)&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Где &lt;span class=&quot;mathjax&quot;&gt;\(E\)&lt;/span&gt; &amp;mdash; освещенность, &lt;span class=&quot;mathjax&quot;&gt;\(r\)&lt;/span&gt; &amp;mdash; расстояние от источника освещения до объекта, &lt;span class=&quot;mathjax&quot;&gt;\(I\)&lt;/span&gt; &amp;mdash; сила света точечного источника, &lt;span class=&quot;mathjax&quot;&gt;\(i\)&lt;/span&gt; &amp;mdash; угол падения лучей относительно нормали к поверхности.&lt;/p&gt;

&lt;p&gt;==2. Фотометрический профиль==&lt;/p&gt;

&lt;p&gt;Существуют два распространенных формата для хранения фотометрических данных о распределении интенсивности света: IES и EULUMDAT. Оба формата &amp;mdash; обычные ASCII файлы.&lt;/p&gt;

&lt;p&gt;Пример файла в формате IES:&lt;/p&gt;
&lt;div class=&quot;quote&quot;&gt;&lt;p&gt;IESNA:LM-63-2002
&lt;br&gt;
[TEST]BALLABS TEST NO.&amp;nbsp; 14397.0 
&lt;br&gt;
[TESTLAB] BUILDING ACOUSTICS &amp; LIGHTING LABORATORIES, INC
&lt;br&gt;
[ISSUEDATE] 03-DEC-2008
&lt;br&gt;
[MANUFAC] LeoMoon Studios
&lt;br&gt;
[LUMINAIRE] 1/100W CLEAR ED17 LAMP 6&amp;quot;DIA 38.25&amp;quot;TALL WHITE BOLLARD&amp;nbsp; &amp;nbsp; 
&lt;br&gt;
[MORE] 4.5&amp;quot;DIA SPECULAR CONE REFLECTOR w/5.875&amp;quot;TALL ACRYLIC&amp;nbsp; &amp;nbsp;  
&lt;br&gt;
[MORE] CYLINDER LENS&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 
&lt;br&gt;
[LUMCAT] OSA6R-100PSMH120-AC&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;  
&lt;br&gt;
[LAMPCAT] M90 MH100W/U/PS&amp;nbsp; &amp;nbsp; 
&lt;br&gt;
TILT=NONE
&lt;br&gt;
1 9000 1 35 1 1 1 -0.49 -0.49 0.406
&lt;br&gt;
1 1 100
&lt;br&gt;
0 5 10 15 20 25 30 35 40 45 50 55 60 62.5 65 67.5 70 72.5 75 77.5 80 82.5 85 87.5 90 95 105 115 125 135 145 155 165 175 180
&lt;br&gt;
0
&lt;br&gt;
0.00 12.00 87.00 182.00 167.00 300.00 655.00 944.00 822.00 703.00 614.00 565.00 487.00 438.00 372.00 309.00 262.00 216.00 182.00 142.00 111.00 83.00 61.00 42.00 25.00 19.00 14.00 14.00 15.00 20.00 33.00 36.00 27.00 4.00 0.00&lt;/p&gt;&lt;/div&gt;&lt;p&gt;IES &amp;mdash; формат, разработанный Светотехническим Обществом Северной Америки (Illuminating Engineering Society of North America, IESNA) для электронной передачи фотометрических данных световых приборов между разными светотехническими компьютерными программами. Является наиболее популярным форматом для хранения фотометрических данных в Северной Америке и широко используется в Европе.&lt;/p&gt;

&lt;p&gt;EULUMDAT &amp;mdash; Европейский стандарт и де-факто промышленный стандарт описания фотометрических данных в Европе.&lt;/p&gt;

&lt;p&gt;Оба формата описывают интенсивность освещения при различных углах и используют сферическую систему координат, именуемую как &amp;laquo;Фотометрическая сеть&amp;raquo; или &amp;laquo;Фотометрическая паутина&amp;raquo;.&lt;/p&gt;

&lt;p&gt;Фотометрический профиль в программе IESViewer:&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://gamedev.ru/files/images/iesviewer.png&quot; alt=&quot;Photometric profile in IESViewer | Освещение с использованием фотометрического профиля&quot; title=&quot;Photometric profile in IESViewer&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;p&gt;Фотометрический профиль может быть легко применен в компьютерной графике к точечному источнику освещения или прожектору, в том числе и в реалтайм, если данные об интенсивности света агрегировать в lookup текстуру.&lt;/p&gt;

&lt;p&gt;Также его можно использовать двумя разными способами, например, если нормализовать интенсивность света от 0 до 1, то ФП можно использовать как маску для источника освещения, таким образом, ФП лишь создаст дополнительную аттенуацию (затухание). Второй вариант применения - когда данными о интенсивности света будет являться только фотометрический профиль, для этого необходимо нормализованные значение интенсивности умножить на максимальную интенсивность до нормализации.&lt;/p&gt;

&lt;p&gt;==3. Запаковка в текстуру==&lt;/p&gt;

&lt;p&gt;Мы можем просэмплировать ФП по горизонтали от 0 до 360 градусов, а по вертикали от 0 до 180, восполняя недостающую информацию линейной интерполяцией, таким образом получив 2D текстуру. В данном случае при расчете в &lt;a href=&quot;https://gamedev.ru/code/terms/Shader&quot; title=&quot;Шейдер (Shader)&quot;&gt;шейдере&lt;/a&gt; нам придется учитывать не только направление источника (spot direction), но и поворот вокруг этого направления.&lt;/p&gt;

&lt;p&gt;Можно поступить хитрее и принять во внимание тот факт, что большинство фотометрических файлов симметричны. Получив среднее значение интенсивности по горизонтали для каждого вертикального угла, мы можем уложить данные в 1D текстуру.&lt;/p&gt;

&lt;p&gt;Псевдокод:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;overflow full&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; MaxIntensity = &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;;
&lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; IntensityData&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;TextureWidth&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
&lt;span class=&quot;key&quot;&gt;unsigned&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;char&lt;/span&gt; TextureData&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;TextureWidth&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
&lt;span class=&quot;key&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;int&lt;/span&gt; x = &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt; ; x &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; TextureWidth ; x++ &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; {
   &lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; Angle = &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;x / &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; TextureWidth - &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; * PI;
   IntensityData&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt; x &lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; = PhotometricData.SampleVerticalAngle&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; Angle &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
   MaxIntensity = std::max&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; MaxIntensity, IntensityData&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt; x &lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
}
&lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; Normalizer = MaxIntensity &lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f ? &lt;span class=&quot;digit&quot;&gt;1.0&lt;/span&gt;f / MaxIntensity : &lt;span class=&quot;digit&quot;&gt;1.0&lt;/span&gt;f;
&lt;span class=&quot;key&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;int&lt;/span&gt; x = &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt; ; x &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; TextureWidth ; x++ &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; {
    TextureData&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt; x &lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; = clamp&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; std::pow&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; IntensityData&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt; x &lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; * Normalizer, &lt;span class=&quot;digit&quot;&gt;1.0&lt;/span&gt;f / &lt;span class=&quot;digit&quot;&gt;2.2&lt;/span&gt;f &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; * &lt;span class=&quot;digit&quot;&gt;255&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;255&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
}&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;bound&quot;&gt;&lt;p&gt;Достаточно использовать half-float текстуру, но можно и обойтись восьмибитной текстурой (8 бит на пиксель). Для этого я бы рекомендовал запаковать интенсивность в sRGB-пространство, воспользовавшись аппроксимацией gamma 2.2. Обратите внимание, в приведенном коде, где нормализованная интенсивность возводится в степень 1/2.2. Подробно на sRGB пространстве мы останавливаться не будем, поскольку информации предостаточно в общедоступных источниках.&lt;/p&gt;

&lt;p&gt;Размер текстуры (TextureWidth) подбирается под ваши требования. В большинстве случаев достаточно текстуры шириной 256 пискселей.&lt;/p&gt;

&lt;p&gt;В следующей строке происходит получение средней горизонтальной интенсивности для заданного вертикального угла.&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;IntensityData&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt; x &lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; = PhotometricData.SampleVerticalAngle&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; Angle &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Для этого осуществляется поиск пары вертикальных углов между которыми находится Angle, далее производится получение средней интенсивности по всем горизонтальным углам в найденной паре вертикальных углов и линейная интерполяция между ними.&lt;/p&gt;

&lt;p&gt;Псевдокод:&lt;/p&gt;
&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; CPhotometricData::SampleVerticalAngle&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; Angle &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; {
  &lt;span class=&quot;comment&quot;&gt;// Ищем пару вертикальных углов между которыми находится Angle&lt;/span&gt;
  &lt;span class=&quot;key&quot;&gt;int&lt;/span&gt; AngleIndex = -&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;;
  &lt;span class=&quot;key&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;int&lt;/span&gt; i = &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt; ; i &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; m_NumVerticalAngles - &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt; ; i++ &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; {
    &lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; Angle &lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt;= m_VerticalAngles&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;i&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; &amp;&amp; Angle &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; m_VerticalAngles&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;i+&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; {
       AngleIndex = i;
       &lt;span class=&quot;key&quot;&gt;break&lt;/span&gt;;
    }
  }
  &lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; AngleIndex == -&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; {
    &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f;
  }

  &lt;span class=&quot;comment&quot;&gt;// Рассчитываем среднюю горизонтальную интенсивность найденных вертикальных углов&lt;/span&gt;
  &lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; Intensity0 = &lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f;
  &lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; Intensity1 = &lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f;
  &lt;span class=&quot;key&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;int&lt;/span&gt; i = &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt; ; i &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; m_NumHorizontalAngles - &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt; ; i++ &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; {
    Intensity0 += m_IntensityTable&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt; i &lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt; AngleIndex &lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
    Intensity1 += m_IntensityTable&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt; i &lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt; AngleIndex + &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
  }
  Intensity0 /= m_NumHorizontalAngles;
  Intensity1 /= m_NumHorizontalAngles;

  &lt;span class=&quot;comment&quot;&gt;// Вычисляем значение для интерполяции интенсивности между двумя углами&lt;/span&gt;
  &lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; Delta = m_VerticalAngles&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;AngleIndex+&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; - m_VerticalAngles&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;AngleIndex&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
  &lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; Fract = Delta &lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f ? &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; Angle - m_VerticalAngles&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;AngleIndex&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; / Delta : &lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f

  &lt;span class=&quot;key&quot;&gt;return&lt;/span&gt; Intensity0 * &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; &lt;span class=&quot;digit&quot;&gt;1.0&lt;/span&gt;f - Fract &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; + Intensity1 * Fract;
}&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Код для шейдера (GLSL) будет следующий&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;sampler1D PhotometricLookup;

...

&lt;span class=&quot;comment&quot;&gt;// Косинус угла между нормализованными векторами&lt;/span&gt;
&lt;span class=&quot;comment&quot;&gt;// от источника к объекту и направлением источника освещения (spot direction)&lt;/span&gt;
&lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; LdotD = dot&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; L, D &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;

&lt;span class=&quot;comment&quot;&gt;// Получаем координату в lookup текстуру&lt;/span&gt;
&lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; sampleCoord = acos&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;LdotD&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; * &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1.0&lt;/span&gt; / PI&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;

&lt;span class=&quot;comment&quot;&gt;// Считываем нормализованную интенсивность, распаковываем из sRGB в RGB&lt;/span&gt;
&lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; normalizedIntensity = pow&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; texture&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; PhotometricLookup, sampleCoord &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;.r, &lt;span class=&quot;digit&quot;&gt;2.2&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;

&lt;span class=&quot;comment&quot;&gt;// Восстанавливаем исходную интенсивность.&lt;/span&gt;
&lt;span class=&quot;comment&quot;&gt;// При использовании ФП в качестве маски, умножение на maxIntensity не требуется.&lt;/span&gt;
&lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; I = normalizedIntensity * maxIntensity;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Вычисление acos в некоторых случаях может быть накладно, но можно немного изменить упаковку в 1D текстуру без существенного ухудшения качества, если каждому пикселю 1D текстуры будет соответствовать не вертикальный угол, а косинус вертикального угла.&lt;/p&gt;

&lt;p&gt;Изменим вычисление угла в цикле при упаковке в 1D текстуру:&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;int&lt;/span&gt; x = &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt; ; x &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; TextureWidth ; x++ &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; {
   &lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; NdotL = &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;x / &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; TextureWidth - &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; * &lt;span class=&quot;digit&quot;&gt;2.0&lt;/span&gt;f - &lt;span class=&quot;digit&quot;&gt;1.0&lt;/span&gt;f;
   &lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; Angle = std::acos&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; NdotL &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
   IntensityData&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt; x &lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; = PhotometricData.SampleVerticalAngle&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; Angle &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
   MaxIntensity = max&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt; MaxIntensity, IntensityData&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt; x &lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
}&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;И внесем корректировку в шейдер при вычислении текстурной координаты:&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;&lt;span class=&quot;comment&quot;&gt;// Получаем координату в lookup текстуру, переводим NdotL из диапазона [-1,+1] к [0,1]&lt;/span&gt;
&lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; sampleCoord = LdotD * &lt;span class=&quot;digit&quot;&gt;0.5&lt;/span&gt; + &lt;span class=&quot;digit&quot;&gt;0.5&lt;/span&gt;;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Таким образом мы избавились от вычисления дорогостоящего арккосинуса в шейдере, заменив на одну mad инструкцию для приведения диапазона [-1,+1] к [0,1].&lt;/p&gt;

&lt;p&gt;Для поддержки множества различных фотометрических профилей достаточно заменить одномерную текстуру на 1DArray и обращаться к ней по индексу профиля.&lt;/p&gt;

&lt;p&gt;==4. Заключение==&lt;/p&gt;

&lt;p&gt;В данной статье мы познакомились с фотометрическим профилем, узнали два основных формата для хранения фотометрических данных точечных источников освещения, узнали как запаковать данные в текстуру для использования в шейдере для рендеринга в реальном времени, применили несколько оптимизаций.&lt;/p&gt;

&lt;p&gt;Данная техника особенно применима в визуализации архитектурных строений и в играх с упором на реалистичность. При минимальных затратах можно существенно улучшить качество освещения, и еще немного приблизиться к фотореализму. &lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://gamedev.ru/files/images/photometric_profile2.jpg&quot; alt=&quot;Photometric profile | Освещение с использованием фотометрического профиля&quot; title=&quot;Photometric profile&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;p&gt;==5. Дополнительные материалы по теме==&lt;/p&gt;

&lt;p&gt;1. Описание формата iesna-lm-63-1995:
&lt;br&gt;
&lt;a href=&quot;http://expertunion.ru/normyi-osvescheniya/iesna-lm-63-1995-opisanie-formata-ies.html&quot; rel=&quot;nofollow&quot;&gt;http://expertunion.ru/normyi-osvescheniya/iesna-lm-63-1995-opisan&amp;hellip; mata-ies.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;2. Ian Ashdown. Parsing The IESNA LM-63 Photometric Data File. 1998.
&lt;br&gt;
&lt;a href=&quot;http://lumen.iee.put.poznan.pl/kw/iesna.txt&quot; rel=&quot;nofollow&quot;&gt;http://lumen.iee.put.poznan.pl/kw/iesna.txt&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;3. Photometric Data for Lamps
&lt;br&gt;
&lt;a href=&quot;https://www.usa.lighting.philips.com/support/support/literature/photometric-data&quot; rel=&quot;nofollow&quot;&gt;https://www.usa.lighting.philips.com/support/support/literature/p&amp;hellip; tometric-data&lt;/a&gt;&lt;/p&gt;</description>
</item>
<item>
  <guid isPermaLink="true">https://gamedev.ru/code/tip/?id=7681</guid>
  <pubDate>Sun, 10 May 2020 18:52:48 GMT</pubDate>
  <title>MAX Script: Разделение 3D модели на элементы по группам нормаль векторов.</title>
  <link>https://gamedev.ru/code/tip/?id=7681</link>
  <category>Графика</category>
  <category>3D Studio MAX</category>
  <category>groups</category>
  <category>MAX Script</category>
  <category>normals</category>
  <category>vectors</category>
  <category>скрипты</category>
  <description>
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Здесь представлен скрипт 3D Studio MAX, при помощи которого можно разделить 3D модели на элементы по группам нормалей, также смотрите &lt;a href=&quot;https://gamedev.ru/code/tip/?id=7266&quot;&gt;&amp;laquo;MAX Script: Разделение 3D модели на элементы по группам сглаживания&amp;raquo;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Как мы знаем в идеале каждый, треугольник который должен быть отрисован должен делить вершину или вершины с другими треугольниками. Но не всегда это возможно, поскольку нормаль, векторы треугольников меняются в разных плоскостях.&lt;/p&gt;

&lt;p&gt;Проблема:
&lt;br&gt;
&lt;img src=&quot;https://gamedev.ru/files/images/panels_with_artifacts.png&quot; alt=&quot;Panels With Artifacts | MAX Script: Разделение 3D модели на элементы по группам нормаль векторов.&quot; title=&quot;Panels With Artifacts&quot;&gt;&lt;/p&gt;

&lt;p&gt;Решение:
&lt;br&gt;
&lt;img src=&quot;https://gamedev.ru/files/images/panels_no_artifacts.png&quot; alt=&quot;Panels No Artifacts | MAX Script: Разделение 3D модели на элементы по группам нормаль векторов.&quot; title=&quot;Panels No Artifacts&quot;&gt;&lt;/p&gt;

&lt;p&gt;Есть два варианта разделение треугольников на элементы, разделять всё треугольники на отдельные элементы, или же разделять по группам нормальев.
&lt;br&gt;
В первом случаи множество вершин будет продублировано, во втором варианте некое минимальное количество вершин будет продублировано.&lt;/p&gt;

&lt;p&gt;Примечание: 
&lt;br&gt;
Данный скрипт работает только в режиме &amp;quot;Editable Poly&amp;quot;, если применены группы сглаживания они не будут обрабатываться, также важно заметить что &amp;quot;отмена&amp;quot; не будет работать после обработки модели скриптом, так что будьте аккуратны!&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;overflow full&quot;&gt;&lt;pre&gt;/*
Notes:
This script must be used in Editable Poly mode.
After the script is applied there will be no possibility to undo.
*/

fn splitSmoothingGroupsIntoElements tmesh =
(
  if classof tmesh == Editable_Poly do
  (
    local groupIDsUsed = #()
    local faceCount = tmesh.getNumFaces()
    for f = 1 to faceCount  do 
    (
      local groupID  = (polyop.getFaceSmoothGroup tmesh f)
      appendIfUnique groupIDsUsed groupID
    )

    for g = 1 to groupIDsUsed.count do 
    (
      local faceGroup = #()
      for i = 1 to faceCount do
      (
      local polygonGroupID  = (polyop.getFaceSmoothGroup tmesh i) as integer
      if polygonGroupID == (groupIDsUsed[g] as integer) do
      (
        append faceGroup i
      )
      )
      polyop.detachFaces tmesh faceGroup asNode:false
    )
    groupIDsUsed = #()
  )
)  

struct FaceNormalType
(
  normal,
  faces = #()
)

fn compareNormals normal1 normal2=
(
  deltaX = abs(normal1.x - normal2.x);
  deltaY = abs(normal1.y - normal2.y);
  deltaZ = abs(normal1.z - normal2.z);
    
  if deltaX &amp;lt; 0.001 and deltaY &amp;lt; 0.001 and deltaZ &amp;lt; 0.001 then
  (
    return true;
  )
  return false;
)

fn smarpSplitFn =
(
  ClearListener()
  tmesh = selection[1]
  FaceNormalGroups = #()
  
  if classof tmesh == Editable_Poly do
  (
    local faceCount = tmesh.getNumFaces()
    for i = 1 to faceCount  do 
    (
      local groupID  = (polyop.getFaceSmoothGroup tmesh i)
      
      --Here we may add a check if the groupID == 0, that means that the face doesn&apos;t belong to any
      --smoothing group and therefore may be splitted, otherwise ignoore
      if groupID == 0 do
      (
        faceNormal = polyop.getFaceNormal tmesh i
      
        --Look for similar normal groups
        foundGroup = false
        for n = 1 to FaceNormalGroups.count do
        (
          if (compareNormals FaceNormalGroups[n].normal faceNormal) == true do
          (
            appendifUnique FaceNormalGroups[n].faces i
            foundGroup = true
          )
        )
        
        --If not found create a new group
        if foundGroup == false do
        (
          faceNormalGrp = FaceNormalType normal:faceNormal faces:#(i)
          append FaceNormalGroups faceNormalGrp
        )
      )
    )  
    
    --Split the faces
    for i = 1 to FaceNormalGroups.count do
    (
      polyop.detachFaces tmesh FaceNormalGroups[i].faces asNode:false
    )  
    
    MessageBox &amp;quot;Done!&amp;quot; title:&amp;quot;Information&amp;quot;
  )  
)

fn dumbSplitFn =
(
  --This function will split all faces into seperate elements
  ClearListener()
  tmesh = selection[1]
  if classof tmesh == Editable_Poly do
  (
    local faceCount = tmesh.getNumFaces()
    for i = 1 to faceCount  do 
    (
      polyop.detachFaces tmesh #((i as integer)) asNode:false
    )    
    MessageBox &amp;quot;Done!&amp;quot; title:&amp;quot;Information&amp;quot;
  )
)

fn splitSmoothGroupsFn =
(
  --This function will split faces into their smoothing groups
  ClearListener()
  tmesh = selection[1]
  if tmesh != undefined do
  (
    splitSmoothingGroupsIntoElements tmesh
    MessageBox &amp;quot;Done!&amp;quot; title:&amp;quot;Information&amp;quot;    
  )
)

rollout eSplitFacesIntoElements &amp;quot;Splitter&amp;quot; width:140 height:100
(
  button &apos;btn1&apos; &amp;quot;Smart Split&amp;quot; pos:[10,10] width:120 height:25 align:#left \
    tooltip:&amp;quot;Groups faces by their normal vectors and splits into separate elements.&amp;quot;
  
  button &apos;btn2&apos; &amp;quot;Dumb Split&amp;quot; pos:[10,40] width:120 height:25 align:#left \
    tooltip:&amp;quot;Splits all faces into separate unique elements.&amp;quot;
  
  button &apos;btn3&apos; &amp;quot;Split Sm. Groups&amp;quot; pos:[10,70] width:120 height:25 align:#left \
    tooltip:&amp;quot;Splits all faces into separate smoothing groups.&amp;quot;
  
  on btn1 pressed do
  (
    smarpSplitFn()
  )
  
  on btn2 pressed do
  (
    dumbSplitFn()
  )
  
  on btn3 pressed do
  (
    splitSmoothGroupsFn()
  )
)
createDialog eSplitFacesIntoElements&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;bound&quot;&gt;&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://gamedev.ru/files/images/script_2.png&quot; alt=&quot;script_2 | MAX Script: Разделение 3D модели на элементы по группам нормаль векторов.&quot; title=&quot;script_2&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound&quot;&gt;</description>
</item>
<item>
  <guid isPermaLink="true">https://gamedev.ru/code/tip/Delphi_Timer</guid>
  <pubDate>Thu, 23 Apr 2020 23:30:20 GMT</pubDate>
  <title>Delphi: Точный таймер (Win API)</title>
  <link>https://gamedev.ru/code/tip/Delphi_Timer</link>
  <comments>https://gamedev.ru/code/forum/?id=251641</comments>
  <category>Общее</category>
  <category>Delphi</category>
  <description>
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Всем привет! Выкладываю код точного таймера. Время измеряется в секундах и миллисекундах. Код для Delphi&amp;nbsp; (серия XE). Вставки на ассемблере сделаны для 64-х битной версии и избежания лишнего чтения/записи из стека из-за процедурных фреймов.&lt;/p&gt;
 &lt;div id=&quot;spoilerHead4&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(4, 1)&quot;&gt;+  Показать&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;spoiler4&quot; style=&quot;display:none;&quot;&gt;&lt;div class=&quot;bound overflow&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(4, 0)&quot;&gt;&amp;minus; Скрыть&lt;/span&gt;
&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;unit&lt;/span&gt; ZTimer;

&lt;span class=&quot;key&quot;&gt;interface&lt;/span&gt;

&lt;span class=&quot;key&quot;&gt;uses&lt;/span&gt;
  WinApi.Windows;

&lt;span class=&quot;key&quot;&gt;type&lt;/span&gt;
  TZTimer = &lt;span class=&quot;key&quot;&gt;packed&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;record&lt;/span&gt;
    &lt;span class=&quot;key&quot;&gt;private&lt;/span&gt;
      FStart: UInt64;
      FEnd: UInt64;
      FSEC: Double;
      FMSEC: Double;

    &lt;span class=&quot;key&quot;&gt;public&lt;/span&gt;
      &lt;span class=&quot;key&quot;&gt;procedure&lt;/span&gt; Init;
      &lt;span class=&quot;key&quot;&gt;procedure&lt;/span&gt; Reset;
      &lt;span class=&quot;key&quot;&gt;procedure&lt;/span&gt; Start;
      &lt;span class=&quot;key&quot;&gt;procedure&lt;/span&gt; Stop;
      &lt;span class=&quot;key&quot;&gt;function&lt;/span&gt; TimeInSec: Double;
      &lt;span class=&quot;key&quot;&gt;function&lt;/span&gt; TimeInMSec: Double;
  &lt;span class=&quot;key&quot;&gt;end&lt;/span&gt;;

&lt;span class=&quot;key&quot;&gt;var&lt;/span&gt;
  AuxTimer: TZTimer;
  FpsTimer: TZTimer;
  SortTimer: TZTimer;

implementation

&lt;span class=&quot;key&quot;&gt;procedure&lt;/span&gt; TZTimer.Init;
{$IFDEF WIN32}
&lt;span class=&quot;key&quot;&gt;var&lt;/span&gt;
  Freq: UInt64;

&lt;span class=&quot;key&quot;&gt;begin&lt;/span&gt;
  QueryPerformanceFrequency&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;Freq&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  FSEC := &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1.0&lt;/span&gt; / Freq&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  FMSEC := &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1.0&lt;/span&gt; / Freq&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; * &lt;span class=&quot;digit&quot;&gt;1000.0&lt;/span&gt;;
&lt;span class=&quot;key&quot;&gt;end&lt;/span&gt;;
{$ENDIF}
{$IFDEF WIN64}
&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt;
  D0: Double = &lt;span class=&quot;digit&quot;&gt;1.0&lt;/span&gt;;
  D1: Double = &lt;span class=&quot;digit&quot;&gt;1000.0&lt;/span&gt;;

&lt;span class=&quot;key&quot;&gt;asm&lt;/span&gt;
  .NOFRAME

  lea r8, self.FSEC
  lea r9, self.FMSEC

  sub rsp, &lt;span class=&quot;digit&quot;&gt;8&lt;/span&gt;
  lea rcx, &lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;rsp&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;
  call QueryPerformanceFrequency
  mov rax, &lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;rsp&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;
  add rsp, &lt;span class=&quot;digit&quot;&gt;8&lt;/span&gt;

  cvtsi2sd xmm1, rax

  movsd xmm0, &lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;D0&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;
  divsd xmm0, xmm1
  movsd &lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;r8&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;, xmm0
  mulsd xmm0, &lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;D1&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;
  movsd &lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;r9&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;, xmm0
&lt;span class=&quot;key&quot;&gt;end&lt;/span&gt;;
{$ENDIF}

&lt;span class=&quot;key&quot;&gt;procedure&lt;/span&gt; TZTimer.Start;
{$IFDEF WIN32}
&lt;span class=&quot;key&quot;&gt;begin&lt;/span&gt;
  QueryPerformanceCounter&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;FStart&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
&lt;span class=&quot;key&quot;&gt;end&lt;/span&gt;;
{$ENDIF}
{$IFDEF WIN64}
&lt;span class=&quot;key&quot;&gt;asm&lt;/span&gt;
  .NOFRAME

  sub rsp, &lt;span class=&quot;digit&quot;&gt;8&lt;/span&gt;
  lea rcx, self.FStart
  mov &lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;rsp&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;, rcx
  call QueryPerformanceCounter
  add rsp, &lt;span class=&quot;digit&quot;&gt;8&lt;/span&gt;
&lt;span class=&quot;key&quot;&gt;end&lt;/span&gt;;
{$ENDIF}

&lt;span class=&quot;key&quot;&gt;procedure&lt;/span&gt; TZTimer.Stop;
{$IFDEF WIN32}
&lt;span class=&quot;key&quot;&gt;begin&lt;/span&gt;
  QueryPerformanceCounter&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;FEnd&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
&lt;span class=&quot;key&quot;&gt;end&lt;/span&gt;;
{$ENDIF}
{$IFDEF WIN64}
&lt;span class=&quot;key&quot;&gt;asm&lt;/span&gt;
  .NOFRAME

  sub rsp, &lt;span class=&quot;digit&quot;&gt;8&lt;/span&gt;
  lea rcx, self.FEnd
  mov &lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;rsp&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;, rcx
  call QueryPerformanceCounter
  add rsp, &lt;span class=&quot;digit&quot;&gt;8&lt;/span&gt;
&lt;span class=&quot;key&quot;&gt;end&lt;/span&gt;;
{$ENDIF}

&lt;span class=&quot;key&quot;&gt;function&lt;/span&gt; TZTimer.TimeInSec: Double;
{$IFDEF WIN32}
&lt;span class=&quot;key&quot;&gt;begin&lt;/span&gt;
  Result := &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;FEnd - FStart&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; * FSEC;
&lt;span class=&quot;key&quot;&gt;end&lt;/span&gt;;
{$ENDIF}
{$IFDEF WIN64}
&lt;span class=&quot;key&quot;&gt;asm&lt;/span&gt;
  .NOFRAME

  mov rax, self.FEnd
  sub rax, self.FStart
  cvtsi2sd xmm0, rax
  mulsd xmm0, double ptr &lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;self.FSEC&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;key&quot;&gt;end&lt;/span&gt;;
{$ENDIF}

&lt;span class=&quot;key&quot;&gt;function&lt;/span&gt; TZTimer.TimeInMSec: Double;
{$IFDEF WIN32}
&lt;span class=&quot;key&quot;&gt;begin&lt;/span&gt;
  Result := &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;FEnd - FStart&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; * FMSEC;
&lt;span class=&quot;key&quot;&gt;end&lt;/span&gt;;
{$ENDIF}
{$IFDEF WIN64}
&lt;span class=&quot;key&quot;&gt;asm&lt;/span&gt;
  .NOFRAME

  mov rax, self.FEnd
  sub rax, self.FStart
  cvtsi2sd xmm0, rax
  mulsd xmm0, double ptr &lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;self.FMSEC&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;key&quot;&gt;end&lt;/span&gt;;
{$ENDIF}

&lt;span class=&quot;key&quot;&gt;procedure&lt;/span&gt; TZTimer.Reset;
&lt;span class=&quot;key&quot;&gt;asm&lt;/span&gt;
  .NOFRAME

  &lt;span class=&quot;key&quot;&gt;xor&lt;/span&gt; rax, rax

  mov self.FStart, rax
  mov self.FEnd, rax
  mov self.FSEC, rax
  mov self.FMSEC, rax
&lt;span class=&quot;key&quot;&gt;end&lt;/span&gt;;

&lt;span class=&quot;key&quot;&gt;initialization&lt;/span&gt;

  AuxTimer.Init;
  FpsTimer.Init;
  SortTimer.Init;

&lt;span class=&quot;key&quot;&gt;end&lt;/span&gt;.&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;bound overflow&quot;&gt;&lt;p&gt;Класс не явялется наследником TObject, поэтому создавать экземпляры не требуется.
&lt;br&gt;
Использовать очень просто:&lt;/p&gt;
&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;&lt;span class=&quot;comment&quot;&gt;// Объявим переменную&lt;/span&gt;
&lt;span class=&quot;key&quot;&gt;var&lt;/span&gt;
  AuxTimer: TZTimer;

&lt;span class=&quot;comment&quot;&gt;// Инициализировать можно 1 раз, например при создании формы.&lt;/span&gt;
&lt;span class=&quot;comment&quot;&gt;// У меня это делается в секции Initialization модуля ZTimer.&lt;/span&gt;
AuxTimer.Init;

AuxTimer.Start;
&lt;span class=&quot;comment&quot;&gt;// Рисуем ...&lt;/span&gt;
AuxTimer.Stop;

&lt;span class=&quot;comment&quot;&gt;// Время кадра&lt;/span&gt;
Writeln&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&apos;Время кадра (1): &apos;&lt;/span&gt; + FloatToStr&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;AuxTimer.TimeInSec, &lt;span class=&quot;digit&quot;&gt;4&lt;/span&gt;, False&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; + &lt;span class=&quot;string&quot;&gt;&apos; сек.&apos;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;; 
Writeln&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;string&quot;&gt;&apos;Время кадра (2): &apos;&lt;/span&gt; + FloatToStr&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;AuxTimer.TimeInMSec, &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;, False&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;+ &lt;span class=&quot;string&quot;&gt;&apos; мс.&apos;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;; 

&lt;span class=&quot;comment&quot;&gt;// Вывод&lt;/span&gt;
Время кадра &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;: &lt;span class=&quot;digit&quot;&gt;0.0002&lt;/span&gt; сек.
Время кадра &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;: &lt;span class=&quot;digit&quot;&gt;0.2&lt;/span&gt; мс.&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Надеюсь у фанатов C++ не будет сложностей с переводом кода.
&lt;br&gt;
По крайней мере у меня наоборот не возникает.&lt;/p&gt;</description>
</item>
<item>
  <guid isPermaLink="true">https://gamedev.ru/code/news/poe_renderer</guid>
  <pubDate>Wed, 08 Apr 2020 15:15:26 GMT</pubDate>
  <title>Развитие рендера в Path of Exile</title>
  <link>https://gamedev.ru/code/news/poe_renderer</link>
  <comments>https://gamedev.ru/code/forum/?id=251318</comments>
  <category>Графика</category>
  <category>графика</category>
  <category>Path of Exile</category>
  <category>rendering</category>
  <description>
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Выложили мой доклад с exilecon. В нём я рассказываю о нескольких необычных техниках рендеринга, разработанных для Path of Exile. Доклад на английском языке. Есть русские субтитры.&lt;/p&gt;

&lt;p&gt;
&lt;div class=&quot;video-container&quot;&gt;&lt;div class=&quot;video&quot; id=&quot;tube1&quot; data-value=&quot;whyJzrVEgVc&quot;&gt;&lt;img data-value=&quot;whyJzrVEgVc&quot; src=&quot;https://img.youtube.com/vi/whyJzrVEgVc/maxresdefault.jpg&quot; onload=&quot;skif.loadYoutubeImage(event)&quot; alt=&quot;Запустить видео по клику - Как делать игры&quot; loading=&quot;lazy&quot;&gt;&lt;img class=&quot;play&quot; src=&quot;https://gamedev.ru/_img/youtube-play.svg&quot; onclick=&quot;skif.insertYoutube(1)&quot; onMouseover=&quot;skif.insertYoutube(1)&quot; alt=&quot;Запустить видео по клику - Как делать игры&quot; loading=&quot;lazy&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/p&gt;</description>
</item>
<item>
  <guid isPermaLink="true">https://gamedev.ru/code/tip/LookAtMat</guid>
  <pubDate>Wed, 29 Jan 2020 07:37:37 GMT</pubDate>
  <title>Матрица LookAt</title>
  <link>https://gamedev.ru/code/tip/LookAtMat</link>
  <comments>https://gamedev.ru/code/forum/?id=249698</comments>
  <category>Общее</category>
  <description>
&lt;p&gt;&lt;i&gt;Доступно пользователям со статусом Участник&lt;/i&gt;&lt;/p&gt;</description>
</item>
<item>
  <guid isPermaLink="true">https://gamedev.ru/code/tip/InvMat4</guid>
  <pubDate>Sat, 25 Jan 2020 19:25:43 GMT</pubDate>
  <title>Инверсия матрицы 4x4</title>
  <link>https://gamedev.ru/code/tip/InvMat4</link>
  <comments>https://gamedev.ru/code/forum/?id=249612</comments>
  <category>Общее</category>
  <description>
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Ниже приведен вариант инверсии для матриц 4x4, где порядок элементов строки горизонтальный (RowMajor). Не самый быстрый, но вполне рабочий (проверено под OpenGL) код для Pascal/Delphi.&lt;/p&gt;
&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;type&lt;/span&gt;
  PMatrix3 = ^TMatrix3;
  PMatrix4 = ^TMatrix4;
  TVector3 = &lt;span class=&quot;key&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0..2&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;of&lt;/span&gt; Single;
  TVector4 = &lt;span class=&quot;key&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0..3&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;of&lt;/span&gt; Single;
  TMatrix3 = &lt;span class=&quot;key&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0..2&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;of&lt;/span&gt; TVector3;
  TMatrix4 = &lt;span class=&quot;key&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0..3&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;of&lt;/span&gt; TVector4;

&lt;span class=&quot;key&quot;&gt;type&lt;/span&gt;
  PMinorMatrix4 = ^TMinorMatrix4;
  TMinorMatrix4 = &lt;span class=&quot;key&quot;&gt;packed&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;record&lt;/span&gt;
    a0, b0, c0, d0,
    a1, b1, c1, d1,
    a2, b2, c2, d2,
    a3, b3, c3, d3: Single;
  &lt;span class=&quot;key&quot;&gt;end&lt;/span&gt;;

&lt;span class=&quot;key&quot;&gt;procedure&lt;/span&gt; InitMatrix&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;var&lt;/span&gt; M: TMatrix4&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt;
  SingleOne: Single = &lt;span class=&quot;digit&quot;&gt;1.0&lt;/span&gt;;

&lt;span class=&quot;key&quot;&gt;asm&lt;/span&gt;
  .NOFRAME

  movss xmm0, &lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;SingleOne&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;

  movups dqword ptr &lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;M + &lt;span class=&quot;digit&quot;&gt;00&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;, xmm0 &lt;span class=&quot;comment&quot;&gt;// (1,0,0,0)&lt;/span&gt;
  pslldq xmm0, &lt;span class=&quot;digit&quot;&gt;4&lt;/span&gt;
  movups dqword ptr &lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;M + &lt;span class=&quot;digit&quot;&gt;16&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;, xmm0 &lt;span class=&quot;comment&quot;&gt;// (0,1,0,0)&lt;/span&gt;
  pslldq xmm0, &lt;span class=&quot;digit&quot;&gt;4&lt;/span&gt;
  movups dqword ptr &lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;M + &lt;span class=&quot;digit&quot;&gt;32&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;, xmm0 &lt;span class=&quot;comment&quot;&gt;// (0,0,1,0)&lt;/span&gt;
  pslldq xmm0, &lt;span class=&quot;digit&quot;&gt;4&lt;/span&gt;
  movups dqword ptr &lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;M + &lt;span class=&quot;digit&quot;&gt;48&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;, xmm0 &lt;span class=&quot;comment&quot;&gt;// (0,0,0,1)&lt;/span&gt;
&lt;span class=&quot;key&quot;&gt;end&lt;/span&gt;;

{$EXCESSPRECISION OFF}
&lt;span class=&quot;key&quot;&gt;function&lt;/span&gt; GetMinor4L&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;var&lt;/span&gt; AMatrix: TMatrix4; ACol, ARow: &lt;span class=&quot;key&quot;&gt;Integer&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;: Single;
&lt;span class=&quot;key&quot;&gt;var&lt;/span&gt;
  NC: &lt;span class=&quot;key&quot;&gt;Integer&lt;/span&gt;;
  T: TMatrix4;

&lt;span class=&quot;key&quot;&gt;begin&lt;/span&gt;
  &lt;span class=&quot;comment&quot;&gt;// [0, 1] // 0 - Строка, 1 - Столбец&lt;/span&gt;
  &lt;span class=&quot;comment&quot;&gt;// [2, 3] // 2 - Строка, 3 - Столбец&lt;/span&gt;

  &lt;span class=&quot;comment&quot;&gt;//------------------&lt;/span&gt;
  &lt;span class=&quot;comment&quot;&gt;// Исключаем строки&lt;/span&gt;
  &lt;span class=&quot;comment&quot;&gt;//------------------&lt;/span&gt;

  &lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;ARow = &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;then&lt;/span&gt;
  &lt;span class=&quot;key&quot;&gt;begin&lt;/span&gt;
    T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := AMatrix&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
    T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := AMatrix&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
    T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := AMatrix&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
  &lt;span class=&quot;key&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;key&quot;&gt;else&lt;/span&gt;
  &lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;ARow = &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;then&lt;/span&gt;
  &lt;span class=&quot;key&quot;&gt;begin&lt;/span&gt;
    T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := AMatrix&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
    T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := AMatrix&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
    T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := AMatrix&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
  &lt;span class=&quot;key&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;key&quot;&gt;else&lt;/span&gt;
  &lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;ARow = &lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;then&lt;/span&gt;
  &lt;span class=&quot;key&quot;&gt;begin&lt;/span&gt;
    T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := AMatrix&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
    T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := AMatrix&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
    T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := AMatrix&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
  &lt;span class=&quot;key&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;key&quot;&gt;else&lt;/span&gt;
  &lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;ARow = &lt;span class=&quot;digit&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;then&lt;/span&gt;
  &lt;span class=&quot;key&quot;&gt;begin&lt;/span&gt;
    T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := AMatrix&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
    T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := AMatrix&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
    T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := AMatrix&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
  &lt;span class=&quot;key&quot;&gt;end&lt;/span&gt;;

  &lt;span class=&quot;comment&quot;&gt;//-------------------&lt;/span&gt;
  &lt;span class=&quot;comment&quot;&gt;// Исключаем столбцы&lt;/span&gt;
  &lt;span class=&quot;comment&quot;&gt;//-------------------&lt;/span&gt;

  NC := &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;; &lt;span class=&quot;comment&quot;&gt;// Столбцы (X)&lt;/span&gt;

  &lt;span class=&quot;comment&quot;&gt;// Исключаем столбец (0) матрицы M&lt;/span&gt;
  &lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;ACol = &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;then&lt;/span&gt; Inc&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;NC&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;

  &lt;span class=&quot;comment&quot;&gt;// Копируем в столбец 0&lt;/span&gt;
  T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, NC&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
  T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;, NC&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
  T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;, NC&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;

  Inc&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;NC&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;

  &lt;span class=&quot;comment&quot;&gt;// Исключаем столбец (1) матрицы M&lt;/span&gt;
  &lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;ACol = &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;then&lt;/span&gt; Inc&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;NC&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;

  &lt;span class=&quot;comment&quot;&gt;// Копируем в столбец 1&lt;/span&gt;
  T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, NC&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
  T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;, NC&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
  T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;, NC&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;

  Inc&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;NC&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;

  &lt;span class=&quot;comment&quot;&gt;// Исключаем столбец (2) матрицы M&lt;/span&gt;
  &lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;ACol = &lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;then&lt;/span&gt; Inc&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;NC&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;

  &lt;span class=&quot;comment&quot;&gt;// Копируем в столбец 2&lt;/span&gt;
  T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, NC&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
  T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;, NC&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
  T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := T&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;, NC&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;

  &lt;span class=&quot;comment&quot;&gt;//    a     b     c&lt;/span&gt;
  &lt;span class=&quot;comment&quot;&gt;//[0, 0][1, 0][2, 0] //0&lt;/span&gt;
  &lt;span class=&quot;comment&quot;&gt;//[0, 1][1, 1][2, 1] //1&lt;/span&gt;
  &lt;span class=&quot;comment&quot;&gt;//[0, 2][1, 2][2, 2] //2&lt;/span&gt;
  &lt;span class=&quot;comment&quot;&gt;//[y, x][y, x][y, x]&lt;/span&gt;

  &lt;span class=&quot;comment&quot;&gt;//-----------------------&lt;/span&gt;
  &lt;span class=&quot;comment&quot;&gt;// Формула&lt;/span&gt;
  &lt;span class=&quot;comment&quot;&gt;//-----------------------&lt;/span&gt;
  &lt;span class=&quot;comment&quot;&gt;// detA = a0 * b1 * c2 +&lt;/span&gt;
  &lt;span class=&quot;comment&quot;&gt;//        a2 * b0 * c1 +&lt;/span&gt;
  &lt;span class=&quot;comment&quot;&gt;//        a1 * b2 * c0 -&lt;/span&gt;
  &lt;span class=&quot;comment&quot;&gt;//        a2 * b1 * c0 -&lt;/span&gt;
  &lt;span class=&quot;comment&quot;&gt;//        a0 * b2 * c1 -&lt;/span&gt;
  &lt;span class=&quot;comment&quot;&gt;//        a1 * b0 * c2&lt;/span&gt;
  &lt;span class=&quot;comment&quot;&gt;//-----------------------&lt;/span&gt;

  &lt;span class=&quot;key&quot;&gt;with&lt;/span&gt; TMinorMatrix4&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;T&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;do&lt;/span&gt;
    Result := a0 * b1 * c2 +
              a2 * b0 * c1 +
              a1 * b2 * c0 -
              a2 * b1 * c0 -
              a0 * b2 * c1 -
              a1 * b0 * c2;
&lt;span class=&quot;key&quot;&gt;end&lt;/span&gt;;
{$EXCESSPRECISION ON}

{$EXCESSPRECISION OFF}
&lt;span class=&quot;key&quot;&gt;procedure&lt;/span&gt; InvertMatrix4L&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;var&lt;/span&gt; M, RM: TMatrix4&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
&lt;span class=&quot;key&quot;&gt;var&lt;/span&gt;
  detA, A, B: Single;

&lt;span class=&quot;key&quot;&gt;begin&lt;/span&gt;
  &lt;span class=&quot;comment&quot;&gt;// 1. Находим определитель матрицы 4x4&lt;/span&gt;
  &lt;span class=&quot;comment&quot;&gt;// Столбцы и строки меняем местами! (Транспозиция!)&lt;/span&gt;
  detA := M&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; * GetMinor4L&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;M, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; -
          M&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; * GetMinor4L&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;M, &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; +
          M&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; * GetMinor4L&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;M, &lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; -
          M&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; * GetMinor4L&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;M, &lt;span class=&quot;digit&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;

  &lt;span class=&quot;comment&quot;&gt;// Обратная матрица существует при (detA &amp;#8800; 0)&lt;/span&gt;
  &lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;detA &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;then&lt;/span&gt;
  &lt;span class=&quot;key&quot;&gt;begin&lt;/span&gt;
    A := &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1.0&lt;/span&gt; / detA&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;; &lt;span class=&quot;comment&quot;&gt;// Положительный множитель&lt;/span&gt;
    B := -A; &lt;span class=&quot;comment&quot;&gt;// Отрицательный множитель&lt;/span&gt;

    &lt;span class=&quot;comment&quot;&gt;// 2. Находим матрицу миноров&lt;/span&gt;
    &lt;span class=&quot;comment&quot;&gt;// 3. и учитываем матрицу алгебраических дополнений&lt;/span&gt;
    &lt;span class=&quot;comment&quot;&gt;// [+, -, +, -]&lt;/span&gt;
    &lt;span class=&quot;comment&quot;&gt;// [-, +, -, +]&lt;/span&gt;
    &lt;span class=&quot;comment&quot;&gt;// [+, -, +, -]&lt;/span&gt;
    &lt;span class=&quot;comment&quot;&gt;// [-, +, -, +]&lt;/span&gt;

    RM&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := GetMinor4L&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;M, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; * A; &lt;span class=&quot;comment&quot;&gt;// +&lt;/span&gt;
    RM&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := GetMinor4L&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;M, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; * B; &lt;span class=&quot;comment&quot;&gt;// -&lt;/span&gt;
    RM&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := GetMinor4L&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;M, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; * A; &lt;span class=&quot;comment&quot;&gt;// +&lt;/span&gt;
    RM&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := GetMinor4L&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;M, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; * B; &lt;span class=&quot;comment&quot;&gt;// -&lt;/span&gt;
    RM&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := GetMinor4L&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;M, &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; * B; &lt;span class=&quot;comment&quot;&gt;// -&lt;/span&gt;
    RM&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := GetMinor4L&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;M, &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; * A; &lt;span class=&quot;comment&quot;&gt;// +&lt;/span&gt;
    RM&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := GetMinor4L&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;M, &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; * B; &lt;span class=&quot;comment&quot;&gt;// -&lt;/span&gt;
    RM&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := GetMinor4L&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;M, &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; * A; &lt;span class=&quot;comment&quot;&gt;// +&lt;/span&gt;
    RM&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := GetMinor4L&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;M, &lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; * A; &lt;span class=&quot;comment&quot;&gt;// +&lt;/span&gt;
    RM&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := GetMinor4L&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;M, &lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; * B; &lt;span class=&quot;comment&quot;&gt;// -&lt;/span&gt;
    RM&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := GetMinor4L&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;M, &lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; * A; &lt;span class=&quot;comment&quot;&gt;// +&lt;/span&gt;
    RM&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := GetMinor4L&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;M, &lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; * B; &lt;span class=&quot;comment&quot;&gt;// -&lt;/span&gt;
    RM&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := GetMinor4L&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;M, &lt;span class=&quot;digit&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; * B; &lt;span class=&quot;comment&quot;&gt;// -&lt;/span&gt;
    RM&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := GetMinor4L&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;M, &lt;span class=&quot;digit&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; * A; &lt;span class=&quot;comment&quot;&gt;// +&lt;/span&gt;
    RM&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := GetMinor4L&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;M, &lt;span class=&quot;digit&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; * B; &lt;span class=&quot;comment&quot;&gt;// -&lt;/span&gt;
    RM&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; := GetMinor4L&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;M, &lt;span class=&quot;digit&quot;&gt;3&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; * A; &lt;span class=&quot;comment&quot;&gt;// +&lt;/span&gt;
  &lt;span class=&quot;key&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;key&quot;&gt;else&lt;/span&gt;
  &lt;span class=&quot;key&quot;&gt;begin&lt;/span&gt;
    InitMatrix&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;RM&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
  &lt;span class=&quot;key&quot;&gt;end&lt;/span&gt;;
&lt;span class=&quot;key&quot;&gt;end&lt;/span&gt;;
{$EXCESSPRECISION ON}&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Скорость на процессоре Intel Core i7-6950X 3.0 ГГц:
&lt;br&gt;
Pascal-версия &amp;mdash; 3 627 435 вызовов в секунду (~1158 тактов/вызов).
&lt;br&gt;
SIMD-версия &amp;mdash; 13 326 824 вызовов в секунду (~315 тактов/вызов).&lt;/p&gt;

&lt;p&gt;Возможно существуют более быстрые способы инверсии,
&lt;br&gt;
но на данный момент мне они неизвестны.&lt;/p&gt;

&lt;p&gt;Всем удачи!&lt;/p&gt;</description>
</item>
<item>
  <guid isPermaLink="true">https://gamedev.ru/code/articles/shrodinger_hydrodynamics</guid>
  <pubDate>Wed, 09 Oct 2019 02:39:43 GMT</pubDate>
  <title>Гидродинамика Шрёдингера на пальцах</title>
  <link>https://gamedev.ru/code/articles/shrodinger_hydrodynamics</link>
  <comments>https://gamedev.ru/code/forum/?id=247371</comments>
  <category>Физика</category>
  <category>GPGPU</category>
  <category>физика</category>
  <category>гидродинамика</category>
  <description>
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;В этой статье в качестве эксперимента я постараюсь максимально доступно рассказать, как работает новый метод расчёта гидродинамики, основанный на решении уравнения Шрёдингера.&lt;/p&gt;

&lt;p&gt;Всем привет. В этой статье я хотел бы рассказать о новом методе расчёта гидродинамики, основанном на решении уравнения Шрёдингера вместо уравнений, типично используемых для гидродинамики вроде Навье-Стокса. Сам метод очень подробно и полно раскрыт в &lt;a href=&quot;http://page.math.tu-berlin.de/~chern/projects/PhDThesis/thesis_reduced.pdf&quot; rel=&quot;nofollow&quot;&gt;диссертации&lt;/a&gt; Albert Chern&apos;а, названной &amp;quot;Fluid Dynamics with Incompressible Schr&amp;#246;dinger Flow&amp;quot;. Однако, статья Chern&apos;а кому-то может показаться написанной на не самом доступном языке, поэтому своей статьёй я бы хотел в первую очередь если не объяснить в деталях, как работает этот метод, то хотя бы объяснить, какими интересными свойствами он обладает, и что же именно скрывается за его математикой. Попутно я кратко расскажу о том, как устроены классические методы расчёта гидродинаимики и как новый подход от них отличается. В качестве эксперимента я бы хотел попробовать написать статью так, чтобы каждый, кто отдалённо интересуется программированием физики, нашёл в ней что-то интересное, понятное, и новое для себя &amp;mdash; от начинающего программиста до бывалых расчётчиков.&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://i.imgur.com/4q3Fg3W.gif&quot; alt=&quot;Изображение&quot; /&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;br&gt;
&lt;p&gt;==Вступление==
&lt;br&gt;
Уравнение Навье-Стокса &amp;mdash; это уравнение, описывающее течение жидкости. Уравнение Шрёдингера &amp;mdash; это уравнение, описывающее эволюцию квантовой системы. И вдруг выясняется, что эти уравнения на самом деле при некоторых условиях являются тождественными. Не знаю, как у вас, но у меня после прочтения этого факта возник только один вопрос: &amp;quot;o___O?&amp;quot;. Я достаточно давно занимаюсь гидродинамикой в качестве хобби и работы, в институте с переменным успехом проходил квантмех и я без понятия, но я понятия не имел, что эти уравнения (а значит и соответствующие им физические явления) настолько близки. Сначала я подумал, что парень, который этот факт открыл, уже готовит шампанское к Нобелевке, но нет &amp;mdash; как выяснилось, факт эквивалентности этих уравнений был &lt;a href=&quot;https://en.wikipedia.org/wiki/Madelung_equations&quot; rel=&quot;nofollow&quot;&gt;открыт ещё в начале века неким Madelung&apos;ом&lt;/a&gt;, который пытался найти физическую аналогию для объяснения уравнения Шрёдингера. &lt;/p&gt;

&lt;p&gt;Почему это важно? В первую очередь потому, что это обозначает глубинное родство квантовомеханических и гидродинамических систем. В диссертации того паренька больше сотни страниц уделено тому, как это вообще так получилось. С участием явления сверхтекучести, которая является загадочным связующим звеном, так как проявляет очевидные свойства идеальной жидкости, являющиеся исключительно следствием квантовой механики. Я же в этой статье далее я рассмотрю только некоторые из параллелей, которые из этого, простите, вытекают.&lt;/p&gt;

&lt;p&gt;Следующее очень важное следствие эквивалентности уравнения Шрёдингера и Навье-Стокса &amp;mdash; это что решение одного из них эквивалентно решению другого. Так вот уравнение Навье-Стокса &amp;mdash; нелиненое, его очень неудобно и неэффективно в общем случае решать, в то время как уравнение Шрёдингера &amp;mdash; линеное и его решать гораздо проще. Чтобы составить представление, насколько же неудобным по сей день считается уравнение Навье-Стокса, могу сообщить, что существует &lt;a href=&quot;https://en.wikipedia.org/wiki/Millennium_Prize_Problems#Navier%E2%80%93Stokes_existence_and_smoothness&quot; rel=&quot;nofollow&quot;&gt;целый международный фонд грантов&lt;/a&gt; для исследователей, которым хоть какую-то базу под них подстроит, так как(цитата):&lt;/p&gt;
&lt;div class=&quot;quote&quot;&gt;&lt;p&gt;Even basic properties of the solutions to Navier–Stokes have never been proven.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;Уравнение Шрёдингера же, хоть и описывает мутную квантовую физику, поддаётся решению гораздо легче и эффективнее. Короче, я могу очень долго гудеть про то, как это невероятно и офигенно, но давайте уже перейдём к чему-то более конкретному.&lt;/p&gt;

&lt;p&gt;==Решение классической гидродинамики на пальцах==
&lt;br&gt;
&lt;img src=&quot;https://i.imgur.com/fny0w1J.gif&quot; alt=&quot;Изображение&quot; /&gt;
&lt;br&gt;
Всю эту страницу я буду рассказывать, как работает стандартный солвер Навье-Стокса, который изобрели давным-давно. В основном &amp;mdash; для сравнения, для некоторого бэграунда и, возможно, кому-то будет просто интересно почитать. Если вы открыли статью только из интереса именно к уравнению Шрёдингера в гидродинамике, то листайте сразу на вторую страницу.&lt;/p&gt;

&lt;p&gt;Что вообще такое &amp;mdash; уравнение гидродинамики? Что такое уравнение Навье-Стокса и как его понять? С ответом на этот вопрос гораздо лучше меня справились миллионы авторов статей по этому делу, например, классическая статья от нвидии, по которой многие начинали: &lt;a href=&quot;https://developer.download.nvidia.com/books/HTML/gpugems/gpugems_ch38.html&quot; rel=&quot;nofollow&quot;&gt;https://developer.download.nvidia.com/books/HTML/gpugems/gpugems_ch38.html&lt;/a&gt; Однако, я попробую написать очень сжато и на пальцах, что это всё значит и что с этим обычно делают.
&lt;br&gt;
Уравнение Навье-Стокса описывает закон, которому обязана подчиняться скорость каждой точки пространства, заполненного равномерной несжимаемой жидкостью. Представьте себе, например, бассейн с водой, в котором выделили некоторый куб, достаточно далеко от стенок, поверхности и дна, в котором нет ничего кроме воды. Вода в нём может как угодно течь, но не может ни образовывать пузырей, ни с чем-то сталкиваться (мы для простоты опустим эти эффекты). Тогда само уравнение Навье-Стокса описывает закон, которому будет подчиняться скорость каждой точки воды в этом кубе:&lt;/p&gt;
 &lt;div id=&quot;spoilerHead5&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(5, 1)&quot;&gt;+  Показать&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;spoiler5&quot; style=&quot;display:none;&quot;&gt;&lt;div class=&quot;bound overflow&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(5, 0)&quot;&gt;&amp;minus; Скрыть&lt;/span&gt;
&lt;p&gt;&lt;span class=&quot;mathjax&quot;&gt;\(\frac{\partial \vec u}{\partial t}=-(\vec u \cdot \vec \nabla)\vec u-\frac{1}{\rho}\vec \nabla p + \nu {\vec \nabla}^2 \vec u + \vec F\)&lt;/span&gt;
&lt;br&gt;
&lt;span class=&quot;mathjax&quot;&gt;\(\vec \nabla \vec u = 0\)&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;bound overflow&quot;&gt;&lt;p&gt;прежде чем вообще смотреть на это уравнение, предлагаю сразу из него выбросить ненужное &amp;mdash; то, что нам всё равно не пригодится для понимания и только место занимает. Это член, отвечающий за диффузию &lt;span class=&quot;mathjax&quot;&gt;\(\nu {\vec \nabla}^2 \vec u\)&lt;/span&gt; (у идеальной жидкости один фиг диффузии нет), и за внешнюю силу &lt;span class=&quot;mathjax&quot;&gt;\(\vec F\)&lt;/span&gt; (так как мы обойдёмся без неё). Остаётся система:
&lt;br&gt;
&lt;span class=&quot;mathjax&quot;&gt;\(\frac{\partial \vec u}{\partial t}=-(\vec u \cdot \vec \nabla)\vec u-\frac{1}{\rho}\vec \nabla p\)&lt;/span&gt;
&lt;br&gt;
&lt;span class=&quot;mathjax&quot;&gt;\(\vec \nabla \vec u = 0\)&lt;/span&gt;
&lt;br&gt;
Здесь перевёрнутый треугольник называется оператором Набла, который обозначает дифференцирование. Причём смысл этого оператора меняется в зависимости от того, где именно он стоит (например, перед вектором или скаляром). Я постараюсь объяснить смысл каждого его вхождения по порядку. На пальцах смысл всей формулы в следующем. &lt;span class=&quot;mathjax&quot;&gt;\(\vec u(\vec x)\)&lt;/span&gt; &amp;mdash; это значение скорости жидкости, которое определяется в каждой точке пространства &lt;span class=&quot;mathjax&quot;&gt;\(\vec x\)&lt;/span&gt;. Уравнение описывает закономерности, которым обязана подчиняться эта величина, если она описывает поведение несжимаемой жидкости. Работает хоть для двумерного, хоть для трёхмерного случая. В левой части первого уравнения стоит &lt;span class=&quot;mathjax&quot;&gt;\(\frac{\partial \vec u(\vec x)}{\partial t}\)&lt;/span&gt; &amp;mdash; это величина называется производной по времени и показывает, как быстро и куда(это вектор) изменится скорость в точке &lt;span class=&quot;mathjax&quot;&gt;\(\vec x\)&lt;/span&gt; в момент времени &lt;span class=&quot;mathjax&quot;&gt;\(t\)&lt;/span&gt;.&lt;/p&gt;
 &lt;div id=&quot;spoilerHead6&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(6, 1)&quot;&gt;+ Как представить производную по времени&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;spoiler6&quot; style=&quot;display:none;&quot;&gt;&lt;div class=&quot;bound overflow&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(6, 0)&quot;&gt;&amp;minus; Скрыть&lt;/span&gt;
&lt;p&gt;Нулевой вектор производной по времени обозначает, что скорость в этой точке сейчас не меняется, а, например, вектор (10, 0)[м/c&lt;sup&gt;2&lt;/sup&gt;] обозначает, что за следующую секунду скорость вырастет на 10[м/с] по оси x(если сама производная не поменяется). &lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;bound overflow&quot;&gt;&lt;p&gt;Слагаемое вида &lt;span class=&quot;mathjax&quot;&gt;\(-(\vec v \cdot \vec \nabla)\vec u\)&lt;/span&gt; называется адвекцией и говорит, что поле скоростей &lt;span class=&quot;mathjax&quot;&gt;\(\vec u\)&lt;/span&gt; в этой точке утекает в направлении &lt;span class=&quot;mathjax&quot;&gt;\(\vec v\)&lt;/span&gt;. В нашем же случае &lt;span class=&quot;mathjax&quot;&gt;\(\vec u = \vec v\)&lt;/span&gt;, то есть поле скоростей сносит само себя. Это, кстати, и называется нелинейностью и из-за этого возникает миллион проблем при решении этого уравнения. &lt;/p&gt;
 &lt;div id=&quot;spoilerHead7&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(7, 1)&quot;&gt;+ Как представить производную по направлению&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;spoiler7&quot; style=&quot;display:none;&quot;&gt;&lt;div class=&quot;bound overflow&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(7, 0)&quot;&gt;&amp;minus; Скрыть&lt;/span&gt;
&lt;p&gt;В принципе, смысл этого члена достаточно интуитивно можно представить именно как утекание каждой точки воды по вектору её скорости. Однако, в общем случае производная векторного поля &lt;span class=&quot;mathjax&quot;&gt;\(\vec u\)&lt;/span&gt; по направлению &lt;span class=&quot;mathjax&quot;&gt;\(\vec v\)&lt;/span&gt; обозначается как &lt;span class=&quot;mathjax&quot;&gt;\((\vec v \cdot \vec \nabla)\vec u\)&lt;/span&gt; и обозначает, как меняется функция &lt;span class=&quot;mathjax&quot;&gt;\(\vec u\)&lt;/span&gt; в направлении &lt;span class=&quot;mathjax&quot;&gt;\(\vec v\)&lt;/span&gt; для этой точки.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;bound overflow&quot;&gt;&lt;p&gt;Слагаемое же &lt;span class=&quot;mathjax&quot;&gt;\(-\frac{1}{\rho}\vec \nabla p\)&lt;/span&gt; является ускорением, которое получает жидкость в точке из-за градиента давления.&lt;/p&gt;
 &lt;div id=&quot;spoilerHead8&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(8, 1)&quot;&gt;+ Как представить градиент&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;spoiler8&quot; style=&quot;display:none;&quot;&gt;&lt;div class=&quot;bound overflow&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(8, 0)&quot;&gt;&amp;minus; Скрыть&lt;/span&gt;
&lt;p&gt;Оператор &lt;span class=&quot;mathjax&quot;&gt;\(\vec \nabla\)&lt;/span&gt;, действующий на скалярное поле(например, давление), называется градиентом. Если слева от некоторой точки давление больше, чем справа, то градиент в ней будет направлен вправо и будет увлекать за собой жидкость в этом направлении. Например, ветер всегда дует в направлении, обратном градиенту давления воздуха (отсюда и минус). Электрический ток течёт в направлении градиента электрического потенциала:
&lt;br&gt;
&lt;span class=&quot;mathjax&quot;&gt;\(E=\vec \nabla \phi\)&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;bound overflow&quot;&gt;&lt;p&gt;Второе уравнение &lt;span class=&quot;mathjax&quot;&gt;\(\vec \nabla \vec u = 0\)&lt;/span&gt; называется уравнение непрерывности, а оператор &lt;span class=&quot;mathjax&quot;&gt;\(\vec \nabla\)&lt;/span&gt; здесь действует на вектор и называется дивергенцией.&lt;/p&gt;
 &lt;div id=&quot;spoilerHead9&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(9, 1)&quot;&gt;+ Как представить дивергенцию&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;spoiler9&quot; style=&quot;display:none;&quot;&gt;&lt;div class=&quot;bound overflow&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(9, 0)&quot;&gt;&amp;minus; Скрыть&lt;/span&gt;
&lt;p&gt;Оператор дифференцирования, действующий на вектор, называется дивергенцией. Дивергенция, равная нулю, говорит, что для каждого маленького кубика сколько в него жидкости втекает, столько и вытекает. А так как любой объём можно разбить на маленькие кубики, то свойство будет справедливо и для объёма любой формы. Это свойство называют также условием несжимаемости, так как если бы в какой-то объём втекало больше жидкости, чем вытекало, это бы означало, что жидкость в объёме накапливается, сжимаясь. Другой случай применения дивергенции, который может помочь её представить &amp;mdash; это теорема Гаусса:
&lt;br&gt;
&lt;span class=&quot;mathjax&quot;&gt;\(\vec \nabla E=\rho\)&lt;/span&gt;
&lt;br&gt;
Эта теорема говорит, что напряжённость электрического поля, которая &amp;quot;вытекает&amp;quot; из некоторого объёма, всегда вызвана электрическим зарядом плотности &lt;span class=&quot;mathjax&quot;&gt;\(\rho\)&lt;/span&gt; внутри этого объёма. Если в объёме заряда нет, то и дивергенция нулевая.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;bound overflow&quot;&gt;&lt;p&gt;То есть, одним предложением уравнение Навье-Стокса можно описать так: темп изменения скорости определяется течением и градиентом давления, но жидкость при этом не может сжиматься.&lt;/p&gt;

&lt;p&gt;==Классическое решения уравнения Навье-Стокса==
&lt;br&gt;
Как я уже говорил раньше, аналитически это уравнение решить нельзя. По крайней мере, науке способы его решения на бумаге с ручкой неизвестны. Но можно решить численно, то есть с помощью программы с некоторой погрешностью. Для начала определимся, над какими вообще данными будет работать наша программа и в каком представлении. Так как уравнение описывает закон, которому подчиняется скорость жидкости, то проще всего так и поступить &amp;mdash; представить скорость каждой точки жидкости в некотором условном кубе как трёхмерный массив векторов. Для двумерного случая &amp;mdash; то же самое, только массив двумерный. Важно понимать, что сетка остаётся на месте, а жидкость течёт вдоль скорости, которая хранится в этой сетке. Такой подход называется подходом Эйлера (альтернативный подход, в котором сама сетка съезжает по течению, называется Лагранжевым). Так как жидкость несжимаемая, то плотность её постоянна, для простоты примем, что она равна единице.&lt;/p&gt;

&lt;p&gt;Посмотрим теперь, как это уравнение можно программно решить. Для этого можно использовать подход, который называется расщеплением &amp;mdash; разбить сложный физический процесс, состоящий из нескольких элементарных, на отдельные чередующиеся стадии и считать, что на каждой стадии работает только один элементарный процесс, а остальные выключены. Как ни странно, можно доказать (см. статью выше), что это &amp;mdash; на самом деле математически обоснованная стратегия. Поэтому будем считать, что состояние скоростей для каждой точки в текущий момент времени &lt;span class=&quot;mathjax&quot;&gt;\(\vec u(\vec x, t)\)&lt;/span&gt; нам известно. А для расчёта состояния в следующий момент времени &lt;span class=&quot;mathjax&quot;&gt;\(t+dt\)&lt;/span&gt;, разобьём сложный процесс гидродинамической эволюции на простые стадии:
&lt;br&gt;
1) снесём поле скоростей по течению. это может немного &amp;quot;сжать&amp;quot; жидкость. 
&lt;br&gt;
2) найдём такое давление, чтобы жидкость &amp;quot;расжалась&amp;quot;.
&lt;br&gt;
Первый шаг называется адвекцией, второй &amp;mdash; проекцией.&lt;/p&gt;

&lt;p&gt;===Адвекция===&lt;/p&gt;

&lt;p&gt;Адвекция, или течение, можно приближённо посчитать достаточно легко &amp;mdash; если известно, что в точке &lt;span class=&quot;mathjax&quot;&gt;\(\vec x\)&lt;/span&gt;, в момент времени &lt;span class=&quot;mathjax&quot;&gt;\(t\)&lt;/span&gt; скорость равна &lt;span class=&quot;mathjax&quot;&gt;\(\vec u(\vec x, t)\)&lt;/span&gt;, то в момент времени &lt;span class=&quot;mathjax&quot;&gt;\(t+dt\)&lt;/span&gt; скорость в неё притечёт жидкость из точки &lt;span class=&quot;mathjax&quot;&gt;\(\vec x - \vec u(x, t)\cdot dt\)&lt;/span&gt;. 
&lt;br&gt;
&lt;span class=&quot;mathjax&quot;&gt;\(\vec u^*(\vec x, t+dt)=\vec u(\vec x - \vec u(\vec x, t), t)\)&lt;/span&gt;
&lt;br&gt;
То есть мы получили промежуточное значение скорости, котороже уже утекло по течению, но теперь в нём нарушено условие непрерывности.&lt;/p&gt;

&lt;p&gt;Это особенно удобно программируется на GPU, так как это можно посчитать, если хранить скорость в текстуре и её обновлять, просто читая тексели со смещением &lt;span class=&quot;mathjax&quot;&gt;\(- \vec u(x, t)\cdot dt\)&lt;/span&gt; и используя стандартную аппаратную линейную интерполяцию. &lt;/p&gt;

&lt;p&gt;===Проекция===&lt;/p&gt;

&lt;p&gt;Проекция берёт скорость, для которой нарушено условие непрерывности &lt;span class=&quot;mathjax&quot;&gt;\(\vec u^*\)&lt;/span&gt; и ищет такое давление, которое её &amp;quot;выправит&amp;quot; до нормальной скорости &lt;span class=&quot;mathjax&quot;&gt;\(\vec u\)&lt;/span&gt;. Умные мужики доказали, что такое поле можно найти единственным образом и оно всегда будет градиентом некоторого скалярного поля (давления, в нашем случае):
&lt;br&gt;
&lt;span class=&quot;mathjax&quot;&gt;\(\vec u(\vec x, t+dt)=\vec u^*(\vec x, t+dt) + \vec \nabla p\)&lt;/span&gt;
&lt;br&gt;
Помножим обе стороны этого равенства на оператор дифференцирования:
&lt;br&gt;
&lt;span class=&quot;mathjax&quot;&gt;\(\vec \nabla \vec u(\vec x, t+dt)=\vec \nabla \vec u^*(\vec x, t+dt) + \vec \nabla^2 p\)&lt;/span&gt;
&lt;br&gt;
&amp;quot;ПОГОДИ-КА СУСЕЛ, ЭТО ЕЩЁ ЧТО&amp;quot; &amp;mdash; можете меня спросить вы. Всё по порядку, но на самом деле отсюда для общего понимания достаточно знать, что если &lt;span class=&quot;mathjax&quot;&gt;\(\vec u^*(\vec x)\)&lt;/span&gt; известно(а оно известно), то отсюда можно найти давление &lt;span class=&quot;mathjax&quot;&gt;\(p(\vec x)\)&lt;/span&gt;. Если вспомнить, что в нашем случае дивергенция скорости равна нулю, то остаётся вот такое выражение. 
&lt;br&gt;
&lt;span class=&quot;mathjax&quot;&gt;\(\vec \nabla^2 p=-\vec \nabla u^*\)&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;В правой части этого равенства стоит дивергенция скорости, которую можно легко приблизительно посчитать, если известна скорость &lt;span class=&quot;mathjax&quot;&gt;\(\vec u^*\)&lt;/span&gt;(а она известна). В левой части стоит штука, которая называется лапласианом давления.&lt;/p&gt;
 &lt;div id=&quot;spoilerHead10&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(10, 1)&quot;&gt;+ Как представить лапласиан&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;spoiler10&quot; style=&quot;display:none;&quot;&gt;&lt;div class=&quot;bound overflow&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(10, 0)&quot;&gt;&amp;minus; Скрыть&lt;/span&gt;
&lt;p&gt;Лапласиан &amp;mdash; это оператор дифференцирования (ещё называется оператор набла) в квадрате, то есть применённый дважды к скалярному полю. Первый раз применяем оператор дифференцирования &amp;mdash; получаем градиент. Второй раз &amp;mdash; получаем дивергенцию. Таким образом оператор лапласа &amp;mdash; это дивергенция градиента скалярного поля. Его можно представить как изменение потока скорости через маленький кубик, которое будет вызвано давлением в точке. Ещё одна аналогия &amp;mdash; как поменяется дивергенция электрического поля в объёмчике, если в него положить заряд плотностью &lt;span class=&quot;mathjax&quot;&gt;\(\rho\)&lt;/span&gt; (опять же, теорема Гаусса):
&lt;br&gt;
&lt;span class=&quot;mathjax&quot;&gt;\(\vec nabla \vec E = \rho\)&lt;/span&gt;, &lt;span class=&quot;mathjax&quot;&gt;\(\vec nabla \phi=\vec E\)&lt;/span&gt; =&amp;gt; &lt;span class=&quot;mathjax&quot;&gt;\(\vec nabla^2 \vec phi = \rho\)&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;bound overflow&quot;&gt;&lt;p&gt;Уравнение вида &amp;quot;лапласиан чего-то неизвестного равен чему-то известному&amp;quot; называется уравнением Пуассона. Что бы это ни значило, существует стандартный итеративный алгоритм, который позволяет его решить, то есть найти такое давление, чтобы его лапласиан был равен чему угодно. &amp;quot;Что угодно&amp;quot; мы знаем &amp;mdash; это дивергенция промежуточной скорости, поэтому считаем по ней давление. Далее для давления считаем градиент и вычитаем результат из промежуточной скорости, чтобы получить окончательную скорость для следующего шага по времени:
&lt;br&gt;
&lt;span class=&quot;mathjax&quot;&gt;\(\vec u=\vec u^* + \vec \nabla p\)&lt;/span&gt;&lt;/p&gt;

&lt;p&gt;Шаги адвекции и проекции повторяем до посинения, рассчитывая всё дальше и дальше эволюцию поля течений по времени. Для визуализации можно, например, напускать частиц, которые могу сноситься этим полем скоростей. Результат выглядит так:
&lt;br&gt;

&lt;div class=&quot;video-container&quot;&gt;&lt;div class=&quot;video&quot; id=&quot;tube2&quot; data-value=&quot;bmYnaT8weDo&quot;&gt;&lt;img data-value=&quot;bmYnaT8weDo&quot; src=&quot;https://img.youtube.com/vi/bmYnaT8weDo/maxresdefault.jpg&quot; onload=&quot;skif.loadYoutubeImage(event)&quot; alt=&quot;Запустить видео по клику - Как делать игры&quot; loading=&quot;lazy&quot;&gt;&lt;img class=&quot;play&quot; src=&quot;https://gamedev.ru/_img/youtube-play.svg&quot; onclick=&quot;skif.insertYoutube(2)&quot; onMouseover=&quot;skif.insertYoutube(2)&quot; alt=&quot;Запустить видео по клику - Как делать игры&quot; loading=&quot;lazy&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Важно понять, что в этом видосе, равно как и во всех остальных гифках этой статьи, жидкость на самом деле находится в большом кубе (границы которого не показаны), а не только там, где видны частицы. Частицы только уносятся полем скоростей, как, например, частицы дыма уносятся полем скоростей воздухе. Сами частицы никакой роли в физике процесса не играют и только позволяют относительно наглядно его продемонстрировать. Частицы обычно добавляются заранее туда, где ожидаются какие-то интересные турбулентности.&lt;/p&gt;

&lt;p&gt;==Важные особенности классического подхода==
&lt;br&gt;
&amp;quot;Всё здорово, сусел, но в названии статьи ты написал что-то там про Шрёдингера! Он вообще где? Зачем нам это всё?&amp;quot; &amp;mdash; спросите вы. Вопрос резонный. Но всю крутость подхода со Шрёдингером можно осознать, только если иметь представление о слабых сторонах классического солвера, который мы рассмотрели в предыдущей главе. В чём же они заключаются? Давайте об этом поговорим.
&lt;br&gt;
 
&lt;br&gt;
Основа любого расчётного метода &amp;mdash; это то, как в нём представлены моделируемые данные. В рассмотренном нами подходе мы храним значение скорости для каждой точки. Например, в текселях двумерной или трёхмерной текстуры. Этот способ здорово работает, если требуется описать ровное поле течений, в котором нет особенностей (так называются завихрения и разные другие неоднородности). Неоднородностей обычно нет в вязких жидкостях вроде мёда или майонеза, поэтому метод очень здорово подходит, чтобы моделировать майонез. Но более текучие среды (например, вода, воздух и дым) отличаются тем, что в них существенную роль играют злополучные турбулентные течения &amp;mdash; мелкие завихрения, имеющие очень сложную и нерегулярную структуру, даже образующие фракталы, которые очень неудобно описывать просто их значениями в каждой точке текстуры/массива. Если попытаться их моделировать, то все мелкие особенности быстро смазываются и расплываются, что соответствует поведению вязкой жидкости. Такое поведение называется численной вязкостью &amp;mdash; это вязкость жидкости, которая появляется не потому что она является частью уравнения, которое мы решаем, а это паразитная вязкость, всплывающая как паразитное следствие нашего метода решения. Более того, напомню, что первое, что мы сделали, не успев взглянуть на уравнение Навье-Стокса &amp;mdash; выкинули из него вязкость, так в ней недостатка точно не будет. &lt;/p&gt;

&lt;p&gt;А вот избавиться от вязкости гораздо труднее, чем случайно её посчитать. Один из способов &amp;mdash; это измельчать расчётную сетку. Чтобы таким методом получить что-то хоть как-то похожее на дым, понадобится сетка минимум 1024x1024x1024, то есть как минимум гигабайт памяти, если хранить по 1 байту на узел. А хранить захочется как минимум трёхкомпонентную скорость, то есть, скорее всего, 32 гигабайта в сумме. Это не только не разумно с точки зрения затрат памяти, это ещё и очень медленно. Другой способ &amp;mdash; это представлять скорость не её направлением в каждой точке, а как сумму маленьких элементарных вихрей. Этот метод называется также методом дискретных вихрей. В нём вообще всё не так просто с процессами порождения новых вихрей и удаления старых, с поддержанием нужной плотности (так как вихри друг друга уносят, как частицы) и ещё миллион проблем, можете сами почитать, если интересно. Другой подход основан на том, что в реальных течениях вихри имеют свойство образовывать вращающиеся нити. Представьте медленно движущийся жгут, вокруг которого быстро вращается жидкость. Если такой жгут замыкается в кольцо, получается тороидальный вихрь, образующий знакомое кольцо дыма:
&lt;br&gt;
&lt;img src=&quot;https://gamedev.ru/files/images/1_aex_puwbqc-rzhvobgpecg.jpeg&quot; alt=&quot;Кольцо дыма | Гидродинамика Шрёдингера на пальцах&quot; title=&quot;Кольцо дыма&quot;&gt;&lt;/div&gt;
&lt;br&gt;
Существуют подходы, которые вместо хранения величины скорости в точках, хранят именно параметры таких жгутов. Но такие методы полагаются на топологию, поэтому в них необходимо считать, как жгуты взаимодействуют, сливаются, распадаются и вообще происходящее быстро теряет простоту и наглядность.&lt;/p&gt;

&lt;p&gt;Однако, у классического метода есть одно очень важное положительное свойство &amp;mdash; в нём вообще нет параметров. Обратите внимание, что для расчёта используется только скорость и больше вообще ничего &amp;mdash; ни вязкости, ни даже плотности. В уравнении Навье-Стокса без вязкости есть плотность, но её можно &amp;quot;спрятать&amp;quot; в нормировку давления, поэтому можно сказать, что в исходном уравнении параметров также нет. Забегая вперёд, замему, что в солвере на уравнении Шрёдинге будет параметр. Загадочный. &lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;
&lt;br&gt;
На следующей странице мы рассмотрим, как же применить уравнения Шрёдингера, чтобы смоделировать тот же самый процесс, и какой в этом профит. Будет много картинок.&lt;/p&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://gamedev.ru/code/articles/shrodinger_hydrodynamics?page=2&quot;&gt;Продолжение&lt;/a&gt;&lt;/p&gt;</description>
</item>
<item>
  <guid isPermaLink="true">https://gamedev.ru/code/articles/grass_raycast</guid>
  <pubDate>Sun, 18 Aug 2019 18:11:23 GMT</pubDate>
  <title>Предрасчёт рейкаста для эффективного рендеринга травы и меха</title>
  <link>https://gamedev.ru/code/articles/grass_raycast</link>
  <comments>https://gamedev.ru/code/forum/?id=246320</comments>
  <category>Графика</category>
  <category>fur rendering</category>
  <category>grass rendering</category>
  <category>raycast</category>
  <category>Realtime Rendering</category>
  <description>
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Здравствуйте. В этой статье я хотел бы рассказать об алгоритме, который я разработал в результате своих экспериментов с предрасчётом трассировки лучей. Результат работы одной из промежуточных версий алгоритма можно увидеть здесь:
&lt;br&gt;

&lt;div class=&quot;video-container&quot;&gt;&lt;div class=&quot;video&quot; id=&quot;tube3&quot; data-value=&quot;NoJykuiHuSY&quot;&gt;&lt;img data-value=&quot;NoJykuiHuSY&quot; src=&quot;https://img.youtube.com/vi/NoJykuiHuSY/maxresdefault.jpg&quot; onload=&quot;skif.loadYoutubeImage(event)&quot; alt=&quot;Запустить видео по клику - Как делать игры&quot; loading=&quot;lazy&quot;&gt;&lt;img class=&quot;play&quot; src=&quot;https://gamedev.ru/_img/youtube-play.svg&quot; onclick=&quot;skif.insertYoutube(3)&quot; onMouseover=&quot;skif.insertYoutube(3)&quot; alt=&quot;Запустить видео по клику - Как делать игры&quot; loading=&quot;lazy&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/p&gt;

&lt;p&gt;==Ключевые особенности алгоритма==
&lt;br&gt;
- Алгоритмическая сложность &amp;mdash; O(1). В моём подходе отсутствует классически применяемый в подобных случаях цикл шагания по лучу, ответ находится за одно чтение из предрассчитанной текстуры.
&lt;br&gt;
- Алгоритм разрабатывается для реалтайм работы на mid/low-gen видеокартах
&lt;br&gt;
- Скорость работы алгоритма не зависит прямым образом от количества волосинок шерсти/травинок. Однако, она косвенно зависит от локальности доступа к памяти, в которой хранятся предрассчитанные данные. То есть скорость зависит не от количества травинок, а от их детализации.
&lt;br&gt;
- На выходе алгоритма &amp;mdash; глубина, нормаль, цвет и прочие параметры поверхности, то есть полученная геометрия корректно перекрывает всю остальную геометрию в буфере глубины.
&lt;br&gt;
- В случае рендринга геометрии, представленной выдавливанием (extrusion) произвольной двумерной маски, результат является аналитически точным. То есть параллельные волокна шерсти, параллельные травинки с произвольным сечением и под произвольным углом к горизонту рендерятся геометрически точно, погрешность в таком случае определяется только точностью дискретизации предрассчитанной текстуры. &lt;/p&gt;

&lt;p&gt;Однако, важно понимать границы применимости алгоритма:
&lt;br&gt;
- Случай с непараллельными волокнами или волокнами переменного сечения можно рендерить приближённо. При некоторых ракурсах результат может выглядеть неестественно.
&lt;br&gt;
- Результат будет тайлиться. Тайлинг можно разными способами скрывать, но полностью от него избавиться вряд ли удастся. 
&lt;br&gt;
- Предрасситанную текстуру можно интерполировать и использовать разные мипы, но результат интерполяции иногда может выглядеть странненько. &lt;/p&gt;

&lt;p&gt;==Базовый алгоритм==&lt;/p&gt;

&lt;p&gt;Рассмотрим сперва базовый алгоритм, который позволяет геометрически точно рендерить выдавленные тайлящиеся поверхности. Выдавливается двумерная геометрия, которая может выглядеть, например, так:
&lt;br&gt;
&lt;img src=&quot;https://gamedev.ru/files/images/single2dtile.png&quot; alt=&quot;2d Extruded Tile | Предрасчёт рейкаста для эффективного рендеринга травы и меха&quot; title=&quot;2d Extruded Tile&quot;&gt;
&lt;br&gt;
После выдавливания мы получим трёхмерное тело:
&lt;br&gt;
&lt;img src=&quot;https://gamedev.ru/files/images/single3dtilev2.png&quot; alt=&quot;Single extruded 3d tile | Предрасчёт рейкаста для эффективного рендеринга травы и меха&quot; title=&quot;Single extruded 3d tile&quot;&gt;
&lt;br&gt;
Здесь синим показана поверхность, из которой производится выдавливание. В финальном рендере этой поверхностью будет меш отрисовываемой травы или меха. &lt;/p&gt;

&lt;p&gt;Предрасчёт данных для рейкаста производится в 2д. Далее полученные данные будут переведены в 3д. Предрасчёт осуществляется в 3д текстуру, где первые две координаты означают (x, y) координаты внутри тайла, а третья &amp;mdash; угол, нормализованный в диапазон [0..1). В цвет текселя предрассчитанной текстуры пишется путь луча до первого пересечения и нормаль в точке пересечения. Пересечения рассчитываются в предположении бесконечно повторяемой (тайловой) геометрии. Например, один из рассчитанных лучей может выглядеть так:
&lt;br&gt;
&lt;img src=&quot;https://gamedev.ru/files/images/2draycast.png&quot; alt=&quot;2d raycast example | Предрасчёт рейкаста для эффективного рендеринга травы и меха&quot; title=&quot;2d raycast example&quot;&gt;
&lt;br&gt;
Предрасчёт вычисляется на чём угодно и каким угодно алгоритмом, так как результат достаточно посчитать один раз, сохранить в текстуру и забыть. Нет смысла особо заморачиваться оптимизацией этой стадии.&lt;/p&gt;

&lt;p&gt;Можно заметить, что полученные данные можно нехитрыми вычислениями использовать и в трёхмерном случае:
&lt;br&gt;
&lt;img src=&quot;https://gamedev.ru/files/images/3draycast.png&quot; alt=&quot;3d raycast example | Предрасчёт рейкаста для эффективного рендеринга травы и меха&quot; title=&quot;3d raycast example&quot;&gt;
&lt;br&gt;
Обратите внимание, что для расчёта трёхмерного луча OB достаточно знать луч OA(который предрассчитан) и угол между лучом OA и OB, который легко посчитать, назовём его &lt;span class=&quot;mathjax&quot;&gt;\(\alpha\)&lt;/span&gt;. Таким образом:
&lt;br&gt;
&lt;span class=&quot;mathjax&quot;&gt;\(|OB| = \frac{|OA|}{\cos \alpha}\)&lt;/span&gt; &lt;/p&gt;

&lt;p&gt;Таким образом, работа алгоритма в рантайме сводится к тому, что мы рендерим меш-внешнюю оболочку, из которой будем выдавливать геометрию, а во фрагментном шейдере вместо глубины и нормали меша (они определяются точкой O), считаем глубину и нормаль по предрассчитанным данным (точка B). То есть в шейдере мы имеем полноценные 3д координаты точки пересечения view луча с геометрией, которую можно использовать для трипланарного текстурирования, расчёта карт теней, ambient occlusion&apos;а и любых других техник, работающих с непрозрачной геометрией. &lt;/p&gt;

&lt;p&gt;==Дальнейшие улучшения==
&lt;br&gt;
На этом этапе геометрия, которую мы можем рендерить &amp;mdash; это вертикальные призмы всевозможных форм. Достаточно легко расширить алгоритм до обработки наклонных(но параллельных) призм, для этого достаточно построить косоугольную матрицу перехода из мировых координат в TBN базис поверхности(*). Текстура с предрассчитанными данными может хранить их именно в TBN-пространстве (то есть в пространстве, в котором ширина тайла по определению равна 1), тогда чтобы перевести точку пересечения обратно в мировые координаты, достаточно помножить её на обратную матрицу перехода. Этот способ позволяет даже описывать поверхности с изменяемым наклоном волокон, хоть и с некоторой погрешностью:
&lt;br&gt;

&lt;div class=&quot;video-container&quot;&gt;&lt;div class=&quot;video&quot; id=&quot;tube4&quot; data-value=&quot;7DFHOMeHIoo&quot;&gt;&lt;img data-value=&quot;7DFHOMeHIoo&quot; src=&quot;https://img.youtube.com/vi/7DFHOMeHIoo/maxresdefault.jpg&quot; onload=&quot;skif.loadYoutubeImage(event)&quot; alt=&quot;Запустить видео по клику - Как делать игры&quot; loading=&quot;lazy&quot;&gt;&lt;img class=&quot;play&quot; src=&quot;https://gamedev.ru/_img/youtube-play.svg&quot; onclick=&quot;skif.insertYoutube(4)&quot; onMouseover=&quot;skif.insertYoutube(4)&quot; alt=&quot;Запустить видео по клику - Как делать игры&quot; loading=&quot;lazy&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Погрешность в этом случае появляется, так как лучи предрассчитываются в преположении, что на протяжении луча угол наклона волокон не меняется, хотя на самом деле это не так. Это пример допущений, которые не являются 100% надёжными и корректными, однако, выполняются достаточно часто, чтобы ими был смысл пользоваться. &lt;/p&gt;

&lt;p&gt;Другой эвристикой, не являющихся полностью корректной, но дающей полезные результаты, является сдвиг волокон по мере марширования по лучу во время предрасчёта:
&lt;br&gt;

&lt;div class=&quot;video-container&quot;&gt;&lt;div class=&quot;video&quot; id=&quot;tube5&quot; data-value=&quot;DBRBZ1tZv_U&quot;&gt;&lt;img data-value=&quot;DBRBZ1tZv_U&quot; src=&quot;https://img.youtube.com/vi/DBRBZ1tZv_U/maxresdefault.jpg&quot; onload=&quot;skif.loadYoutubeImage(event)&quot; alt=&quot;Запустить видео по клику - Как делать игры&quot; loading=&quot;lazy&quot;&gt;&lt;img class=&quot;play&quot; src=&quot;https://gamedev.ru/_img/youtube-play.svg&quot; onclick=&quot;skif.insertYoutube(5)&quot; onMouseover=&quot;skif.insertYoutube(5)&quot; alt=&quot;Запустить видео по клику - Как делать игры&quot; loading=&quot;lazy&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Здесь также меняется толщина волокон &amp;mdash; чем дальше мы идём по лучу, тем толще делаются волокна, что создаёт аппроксимацию сужающихся на концах волокон вроде иголок. Ещё раз обращу внимание, что хоть подобные эвристики и дают часто удобные результаты, они иногда дают артефакты (например, при взгляде параллельно волокнам), в то время как базовый алгоритм даёт геометрически коорректный результат при любых выходных данных. &lt;/p&gt;

&lt;p&gt;Следующим шагом для уменьшения визуального тайлинга является наложение нескольких &amp;quot;слоёв&amp;quot; отрендеренной таким образом геометрии с немного разным углом и/или разным масштабом. Например, хороший результат дают два слоя под углом, равным золотому сечению, помноженному на pi, так как даёт максимально возможный период совпадения периодов слоёв. Можно использовать и более интересные техники совмещения нескольких слоёв для уменьшения тайлинга, всё зависит от доступных ресурсов и требований к качеству. &lt;/p&gt;

&lt;p&gt;Безусловно, самой сложной частью алгоритма является подбор коэффициентов и параметров, чтобы процедурно сгенерированная геометрия была похожа на то, что хочется. Например, на траву, а не на кудри и не на водоросли. Для этого понадобится определённое художественное чутьё, но заниматься подобными экспериментами может быть достаточно увлекательно, так как результаты иногда бывают неожиданно интересные. Хотя чаще просто странненькие. &lt;/p&gt;

&lt;p&gt;(*) Под TBN базисом здесь понимается не TBN базис, который типично используется для перевода нормали из текстуры в мировые координаты, а честный базис, составленный из частных производных &lt;span class=&quot;mathjax&quot;&gt;\(\frac{\vec{duv}}{\vec{dp}}\)&lt;/span&gt;. Разница в том, что в первом случае TBN базис нормализован, а во втором случае базисные векторы обозначают, на сколько меняются u и v текстурные координаты при перемещении по мировым координатам. Обращу внимание, что для целей этой статьи ошибочно полагать ортогональность или нормированность TBN-базиса, он будет точно не нормализован, так как масштаб uv не будет совпадать с мировыми координатами и не ортогонален, если волокна не перпендикулярны поверхности. Этот базис можно рассчитать разными способами, более подробно про это можно почитать, например, здесь: &lt;a href=&quot;http://www.thetenthplanet.de/archives/1180&quot; rel=&quot;nofollow&quot;&gt;http://www.thetenthplanet.de/archives/1180&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;==Как выбрать параметры текстуры предрасчёта==
&lt;br&gt;
Для предрасчёта можно использовать текстуру очень разного размера. Стоит учесть, что чем больше текстура, тем более детализированные тайлы можно в неё запечь, но тем хуже будет локальность данных и тем дороже будет операция чтения из текстуры в соседних фрагментах из-за нелокальности и недружественности к кешу. Наилучшие результаты даёт соотношение примерно 1 текселя слоя текстуры по пространственным осям к 1 пикселю на экране. 32х32 &amp;mdash; минимальный размер слоя, при котором можно с переменным успехом скрыть тайлинг, при тайлах размера 512х512 нелокальность доступа серьёзно сказывается на производительности, поэтому текстуру имеет смысл брать где-то в этом диапазоне. По третьей размерности (количество углов) можно брать от 8 до 64 в зависимости от применения. Я обычно использу текстуры 64х64х8. Особое внимание следует уделить формату текселя текстуры, так как он напрямую определяет время чтения &amp;mdash; чем меньше памяти занимает тексель, тем быстрее он будет читаться, тем быстрее будет работать шейдер. Я использую формат RGBA8, где в R компоненте хранится нормализованная глубина (по формуле 1/(1+d)), GBA &amp;mdash; трёхкомпонентная нормаль. &lt;/p&gt;

&lt;p&gt;==Обновление==
&lt;br&gt;
После написания этой статьи я ещё достаточно много работал над улучшением этого алгоритма. Основа алгоритма осталась той же самой: тот же способ кодирования информации в 2д текстуру, те же допущения. Однако, я сделал несколько серьёзных изменений:
&lt;br&gt;
- Я отказался от процедурного генерирования геометрии и вместо этого сделал запекалку мешей, экспортированных из майи. Далее я просто попросил наших художников наделать мне обычных полигональных мешей травы с текстурами и запёк их подобным образом. Алгоритм трассировки этих мешей я написал на CPU, в финальной версии он считает один меш примерно 3 дня. Думаю, лучше бы я изначально реализовал запекалку на GPU, тогда было бы гораздо быстрее итерировать между разными версиями.
&lt;br&gt;
- Пожалуй, самое визуально важное изменение &amp;mdash; я применил технику texture bombing, которая позволяет избавиться от повторяемости в запечённой текстуре. Вот здесь я тестил сам алгоритм: &lt;a href=&quot;https://www.shadertoy.com/view/tsVGRd&quot; rel=&quot;nofollow&quot;&gt;https://www.shadertoy.com/view/tsVGRd&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Вот так выглядит трава без него:
&lt;br&gt;
&lt;img src=&quot;https://drive.google.com/uc?export=view&amp;id=1YI60hV6KSlMN_kC5ntLJ9U_5NK_rFAPG&quot; alt=&quot;Изображение&quot; /&gt;
&lt;br&gt;
Вот так с ним:
&lt;br&gt;
&lt;img src=&quot;https://drive.google.com/uc?export=view&amp;id=1WKB-4vNNQSsQ0SmdWQWYFJhTKRM1ReMx&quot; alt=&quot;Изображение&quot; /&gt;
&lt;br&gt;
Скриншоты выше &amp;mdash; с отладочным освещением. В финальной версии, которая попала на ExileCon, трава выглядела примерно так:
&lt;br&gt;
&lt;img src=&quot;https://drive.google.com/uc?export=view&amp;id=1MS_3V7uwYzkte4mpRSt8vcLvoa-j50Fu&quot; alt=&quot;Изображение&quot; /&gt;
&lt;br&gt;
&lt;img src=&quot;https://drive.google.com/uc?export=view&amp;id=1T1U7jCQE268KzSy3EAAn7RDXQQ9ViH8l&quot; alt=&quot;Изображение&quot; /&gt;&lt;/p&gt;

&lt;p&gt;==Заключение==
&lt;br&gt;
В этой статье мы рассмотрели алгоритм, предназначенный для эффективной отрисовки в реалтайме сложной геометрии. Мы рассмотрели как базовую версию алгоритма, которая позволяет корректно отображать относительно узкий класс геометрий, так и расширения алгоритма, позволяющие процедурно генерировать очень широкий класс геометрий, внося контролируемую ошибку.&lt;/p&gt;&lt;/p&gt;&lt;/p&gt;</description>
</item>
<item>
  <guid isPermaLink="true">https://gamedev.ru/code/articles/realtime_gi_pt1</guid>
  <pubDate>Mon, 22 Apr 2019 06:28:14 GMT</pubDate>
  <title>На пути к эффективному алгоритму Global Illumination, часть 1</title>
  <link>https://gamedev.ru/code/articles/realtime_gi_pt1</link>
  <comments>https://gamedev.ru/code/forum/?id=243773</comments>
  <category>Графика</category>
  <category>global illumination</category>
  <category>графика</category>
  <description>
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;В этой статье мы рассмотрим небольшой исторический контекст разработки алгоритмов global illumination, а также поразмышляем, какими свойствами должен обладать оптимальный алгоритм расчёта global illumination.&lt;/p&gt;
&lt;p&gt;==Исторический экскурс==&lt;/p&gt;

&lt;p&gt;В компьютерной графике сложилась интересная ситуация &amp;mdash; в то время как для растеризации геометрии на экране уже последние несколько десятков(!) лет де-факто стандартным представлением является полигональное представление геометрии, для представления global illumination используется огромное количество самых разных, часто не имеющих ничего общего друг с другом, структур данных: light propagation volumes, sparse voxel trees, imperfect shadow maps, итп. Получается так в первую очередь потому, что полигональное представление очень эффективно справляется с задачей растеризации, в которой каждому фрагменту трёхмерной геометрии ставится в соответствие единственый фрагмент на экране, в который он проецируется. Это достигается через проецирование вершин треугольника на экранную плоскость и дальнейшей растеризацией каждого треугольника уже в экранных координатах. Этот способ исторически себя настолько хорошо себя зарекомендовал, что после того, как первые аппаратные ускорители графики реализовали эти преобразования в &amp;quot;железе&amp;quot; в начале 90-х, до момента написания этой статьи, в них по большому счёту ничего принципиально не менялось. &lt;/p&gt;

&lt;p&gt;Взглянем на основное &lt;a href=&quot;https://en.wikipedia.org/wiki/Rendering_equation&quot; rel=&quot;nofollow&quot;&gt;уравнение рендеринга&lt;/a&gt;, определяющее освещённость фрагмента:
&lt;br&gt;
&lt;span class=&quot;mathjax&quot;&gt;\(L_0(\vec x, \vec \omega_0) = L_e(\vec x) + \int_{\Omega} f(\vec x, \vec \omega_0, \vec \omega_i) L_i(\vec x, \vec \omega_i,) (\vec w_i \cdot \vec n) d\vec \omega_i\)&lt;/span&gt; (1)
&lt;br&gt;
Для простоты я выбросил из уравнения зависимость от длины волны &lt;span class=&quot;mathjax&quot;&gt;\(\lambda\)&lt;/span&gt; и от времени &lt;span class=&quot;mathjax&quot;&gt;\(t\)&lt;/span&gt;. К сожалению, это уравнение не только выглядит страшновато, но история также показала, что его исключительно трудно решать даже численно. Более того, все существующие подходы в компьютерной графике являются в той или иной степени его различными аппроксимациями. Если вы не совсем понимаете смысл этого уравнения, то ничего страшного, далее мы будем рассматривать его упрощённые формы с подробными пояснениями, пока не придём к нему в изначальном виде для моделирования полного GI. Например, растеризацию объектов без учёта освещения можно описать как уравнение рендеринга, в котором полностью пренебрегли существованием интеграла и свели его к следующему виду:
&lt;br&gt;
&lt;span class=&quot;mathjax&quot;&gt;\(L_0(\vec x, \vec \omega_0) = L_e(\vec x)\)&lt;/span&gt; (2)
&lt;br&gt;
Интуитивно это выражение можно понять следующим образом &amp;mdash; светимость фрагмента на экране определяется собственной светимостью этого самого фрагмента и больше ни от чего не зависит. &lt;/p&gt;

&lt;p&gt;Расчёт прямого освещения объектов точечных (или направленных) источников также можно свести к растеризации, добавив технологию расчёта видимости фрагментов для каждого источника (для учёта теней), что делается через алгоритмы shadow mapping&apos;а, которые хоть и улучшаются со временем, но идейно остаются теми же самыми уже много лет. Это позволяет получить более точную аппроксимацию решения уравнения рендеринга, используя одну лишь растеризацию:
&lt;br&gt;
&lt;span class=&quot;mathjax&quot;&gt;\(L_0(\vec x, \vec \omega_0) = L_e(\vec x) + \sum_{j=1}^{N}f(\vec x, \vec \omega_0, \vec \omega_j) L_j V_j(x) (\vec w_j \cdot \vec n)\)&lt;/span&gt; (3)
&lt;br&gt;
Можно заметить, что классический расчёт множества источников света аппроксимирует интеграл освещения как сумму освещения от N источников. В этой формуле под знаком суммы стоит &lt;a href=&quot;https://en.wikipedia.org/wiki/Bidirectional_reflectance_distribution_function&quot; rel=&quot;nofollow&quot;&gt;BRDF поверхности&lt;/a&gt; &lt;span class=&quot;mathjax&quot;&gt;\(f(\vec x, \vec \omega_0, \omega_j)\)&lt;/span&gt;, который определяет, какая доля энергии, падающей на поверхность в точке &lt;span class=&quot;mathjax&quot;&gt;\(x\)&lt;/span&gt; под направлением &lt;span class=&quot;mathjax&quot;&gt;\(\vec \omega_j\)&lt;/span&gt; отражается в рассеивается в направлении &lt;span class=&quot;mathjax&quot;&gt;\(\vec \omega_0\)&lt;/span&gt;. Типично для аппроксимации этого параметра используются разные модели микрорельефа поверхности вроде lambert или GGX, однако, их рассмотрение выходит за рамки этой статьи. Под &lt;span class=&quot;mathjax&quot;&gt;\(\vec \omega_j\)&lt;/span&gt; понимается направление на источник света под номером &lt;span class=&quot;mathjax&quot;&gt;\(j\)&lt;/span&gt;, &lt;span class=&quot;mathjax&quot;&gt;\(L_j\)&lt;/span&gt; &amp;mdash; его яркость, а &lt;span class=&quot;mathjax&quot;&gt;\(V_j(\vec x)\)&lt;/span&gt; &amp;mdash; функция видимости точки &lt;span class=&quot;mathjax&quot;&gt;\(\vec x\)&lt;/span&gt; для источника j, что типично реализуется через shadow mapping.&lt;/p&gt;

&lt;p&gt;Обратим внимание, что формула (3) удобно ложится на механизм растеризации, так как функцию &lt;span class=&quot;mathjax&quot;&gt;\(V_j(\vec x)\)&lt;/span&gt; для каждого источника можно рассчитать, используя растеризацию shadow map&apos;ы, а потом, используя её, растеризовать сам объект на экран, рассчитав формулу (3) в явном виде в шейдере. Из-за того, что в формуле присутствует цикл по всем источникам, она эффективнее всего работает, если этих самых источников света достаточно мало и производительность будет линейно убывать с количеством источников. &lt;/p&gt;

&lt;p&gt;Принципиальная же особенность расчёта непрямого освещения (формула (1)) от прямого (формула (3)) заключается в том, что непрямое освещение эквивалентно расчёту прямого освещения от бесконечного количества источников прямого освещения. То есть рассчитать его трудно не просто потому, что источников бесконечное количество, но ещё и потому, что производительность алгоритмов прямого освещения линейно падает с количеством источников. Один из первых подходов к аппроксимации GI &amp;mdash; reflective shadow maps. В этом подходе непрямое освещение моделируется как прямое освещение от большого количества вторичных источников света с яркостью, определяющейся их первичным освещением. То есть после расчёта первичного освещения на все фрагменты, на которое попал первичный свет, ставятся вторичные источники света и по формуле (3) рассчитывается их влияние на сцену. Уравнение рендеринга для этого подхода можно описать так:
&lt;br&gt;
&lt;span class=&quot;mathjax&quot;&gt;\(L_0(\vec x, \vec \omega_0) = L_e(\vec x) + \sum_{j=1}^{N}f(\vec x, \vec \omega_0, \vec \omega_j) L_j V_j(x) (\vec w_j \cdot \vec n) +\)&lt;/span&gt;
&lt;br&gt;
&lt;span class=&quot;mathjax&quot;&gt;\(+ \sum_{k=1}^{N_d}f(\vec x, \vec \omega_0, \vec \omega_k) L_k \cdot (\vec w_k \cdot \vec n)\)&lt;/span&gt; (4)
&lt;br&gt;
здесь &lt;span class=&quot;mathjax&quot;&gt;\(N_d\)&lt;/span&gt; &amp;mdash; количество вторичных источников, которые расставляются в область прямого освещения, а &lt;span class=&quot;mathjax&quot;&gt;\(L_k\)&lt;/span&gt; определяет их яркость.&lt;/p&gt;

&lt;p&gt;Это был одним из первых алгоритмов, которые я реализовал, это выглядело примерно так:&lt;/p&gt;

&lt;p&gt;
&lt;div class=&quot;video-container&quot;&gt;&lt;div class=&quot;video&quot; id=&quot;tube6&quot; data-value=&quot;OSEIwiYQ1jA&quot;&gt;&lt;img data-value=&quot;OSEIwiYQ1jA&quot; src=&quot;https://img.youtube.com/vi/OSEIwiYQ1jA/maxresdefault.jpg&quot; onload=&quot;skif.loadYoutubeImage(event)&quot; alt=&quot;Запустить видео по клику - Как делать игры&quot; loading=&quot;lazy&quot;&gt;&lt;img class=&quot;play&quot; src=&quot;https://gamedev.ru/_img/youtube-play.svg&quot; onclick=&quot;skif.insertYoutube(6)&quot; onMouseover=&quot;skif.insertYoutube(6)&quot; alt=&quot;Запустить видео по клику - Как делать игры&quot; loading=&quot;lazy&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Мало того, что этот подход является очень неэффективным(итерирование через сотню источников в шейдере очень долго считать), в нём также полностью игнорируется существование множителя &lt;span class=&quot;mathjax&quot;&gt;\(V_j(\vec x)\)&lt;/span&gt;, отвечающего за тени от вторичного освещения, поэтому они полностью отсутствуют.&lt;/p&gt;

&lt;p&gt;По тому, насколько скудно выглядит результат, и тому, насколько медленно он считается, можно понять, что такой подход едва ли приведёт к практичным результатам. Причина в первую очередь в том, как масштабируется такой алгоритм &amp;mdash; его производительность линейно падает с количеством вторичных источников, которых, напомню, в идеале хотелось бы бесконечно много. На практике же количество источников пропорционально области, освещённой первичным освещением. Более того, он вообще не учитывает самый сложный для расчёта член видимости, то есть в принципе не способен получать эффекты вроде ambient occlusion или любые другие тени.&lt;/p&gt;

&lt;p&gt;Конечно же, к такому выводу пришёл не я один. Огромное количество исследователей пробовали побороть уравнение рендеринга. И самое, на мой взгляд, интересное &amp;mdash; то, насколько разными путями они шли. Кто-то представлял&amp;nbsp; сцену как дискретную трёхмерную сетку и выпускал по ней лучи (vxgi), кто-то представлял сцену как множество элементарных элементов поверхности и рассчитывал их видимость для источников вторичного освщещения (imperfect shadow mapping), кто-то рассчитывал распространение светового фронта как функцию направления для сцены (light propagation volumes), кто-то предрассчитывал излучение каждого фрагмента сцены на основе его окружения (precomputed radiance transfer). Единственное, что объединяет все эти подходы &amp;mdash; что в них нет растеризации треугольников. То есть исследователи уже смирились, что для реализации GI нужно альтернативное представление сцены, которое позволит рассчитать влияние вторичного освещения более эффективно, чем через растеризацию. Вопрос лишь в том, какое это должно быть представление. На момент написания этой статьи это &amp;mdash; по-прежнему открытый и, на мой взгляд, очень острый вопрос. Сразу скажу, что я не смогу дать на него однозначного ответа. Но я потратил на его обдумывание тысячи часов и хотел бы поделиться некоторыми выводами.&lt;/p&gt;

&lt;p&gt;==На каком этапе здесь находится индустрия?==&lt;/p&gt;

&lt;p&gt;Часто если людям, даже плотно занимающимся разработкой графики, показать программу и сказать, что это &amp;mdash; unbiased screenspace GI, их первый вопрос бывает вроде &amp;quot;а как оно рендерит источники света за экраном&amp;quot;? Но господа, постойте. То есть вас не удивляет сама возможность существования unbiased solver&apos;а (напомню, unbiased solver сходится к точному решению уравнения (1)) хотя бы в screenspace&apos;е? Здесь и далее, если применяется термин unbiased в контексте screenspace, это обозначает, что метод сходится к точному решению при увеличении параметра точности метода и условии, что вся геометрия помещается на экране без загораживания. Также я буду рассматривать только вторичное освещение, так как третичное и далее считается по аналогии и не влияет на общую алгоритмическую сложность.&lt;/p&gt;

&lt;p&gt;Люди привыкли видеть демонстрации модных технологий вроде rtx или последних версий unreal engine которые выглядят так, будто в них уже давно считается unbiased освещение да ещё и не в screeenspace&apos;е, то есть, можно подумать, в этом нет ничего особо удивительного. Так вот, сразу обозначу, что ключевых вопроса здесь два &amp;mdash; масштабируемость технологии и общность.&lt;/p&gt;

&lt;p&gt;Масштабируемость
&lt;br&gt;
Разумеется, можно бросить всю вычислительную мощность последней видеокарты от nvidia, чтобы рассчитать фотореалистично выглядящий cornell box даже с самым наивным monte-carlo path tracer&apos;ом и её вычислительной мощности будет достаточно, чтобы рендерить такую сцену в реалтайме почти без шума. Это будет очень общий подход, который теоретически позволит рассчитать unbiased GI для любой сцены, но он будет масштабироваться чуть хуже, чем никак. Path tracer&apos;ы в наивной реализации настолько неэффективно используют вычислительную мощность, что нет смысла говорить об их применении для серьёзных сцен. Причём, на мой взгляд, наивно полагать, что это только сейчас, пока у GPU не хватает мощности считать path tracing в реальном времени. Мол, завтра (через N лет) видеокарты станут мощнее и вполне потянут path tracing в реальном времени. Наивное суждение, так как если к тому времени у них хватит ресурсов на path tracing сегодняшних сцен, то представьте, какие сцены на них можно будет рендерить более продвинутыми алгоритмами. Каким бы мощным железо ни становилось со временем, его всегда рациональнее всего по возможности использовать с наиболее эффективными алгоритмами вместо брутфорсных.&lt;/p&gt;

&lt;p&gt;Общность
&lt;br&gt;
Люди привыкли видеть выносящие мозг своей красотой демки rtx от nvidia, показывающие идеально выглядящие отражения:&lt;/p&gt;

&lt;p&gt;
&lt;div class=&quot;video-container&quot;&gt;&lt;div class=&quot;video&quot; id=&quot;tube7&quot; data-value=&quot;lMSuGoYcT3s&quot;&gt;&lt;img data-value=&quot;lMSuGoYcT3s&quot; src=&quot;https://img.youtube.com/vi/lMSuGoYcT3s/maxresdefault.jpg&quot; onload=&quot;skif.loadYoutubeImage(event)&quot; alt=&quot;Запустить видео по клику - Как делать игры&quot; loading=&quot;lazy&quot;&gt;&lt;img class=&quot;play&quot; src=&quot;https://gamedev.ru/_img/youtube-play.svg&quot; onclick=&quot;skif.insertYoutube(7)&quot; onMouseover=&quot;skif.insertYoutube(7)&quot; alt=&quot;Запустить видео по клику - Как делать игры&quot; loading=&quot;lazy&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;И если человеку задать вопрос: &amp;quot;У них зеркальный пол, зеркальные штормтруперы и зеркальные стены. Как ты думаешь, почему?&amp;quot;. Практически всегда люди отвечают что-то вроде : &amp;quot;Чтобы показать, как круто у них выглядят отражения!&amp;quot;. На вопрос же : &amp;quot;Как ты думаешь, они могут так же здорово рендерить что-то кроме зеркальных поверхностей?&amp;quot;, типичный ответ: &amp;quot;Ээ.. ну конечно! Наверное?&amp;quot;. Стоит отдать должное, как принципиальное ограничение технологии nvidia с успехом выдаёт за фичу. Ответ на этот вопрос &amp;mdash; нет, они не могут считать ни диффузные, ни glossy отражения достаточно эффективно. Если выписать уравнение рендеринга, которое эффективно позволяет считать rtx и подобные технологии, оно выглядит так:
&lt;br&gt;
&lt;span class=&quot;mathjax&quot;&gt;\(L_0(\vec x, \vec \omega_0) = L_e(\vec x) + f(\vec x, \vec \omega_0, \vec \omega_r) L_i(\vec x, \vec \omega_r) (\vec w_r \cdot \vec n)\)&lt;/span&gt; (4)
&lt;br&gt;
Здесь &lt;span class=&quot;mathjax&quot;&gt;\(\omega_r\)&lt;/span&gt; &amp;mdash; направление отражённого луча. Да, надо отдать должное, рейтрейсинг действительно эффективно позволяет получить значение множителя &lt;span class=&quot;mathjax&quot;&gt;\(L_i(\vec x, \vec \omega_r)\)&lt;/span&gt;, но истина в том, что этот множитель эффективен лишь при расчёте зеркальных отражений. Для аппроксимации диффузных поверхностей в рейтрейсинге используется несколько лучей. Алгоритмы, которые я буду рассматривать далее, используют в среднем около 128 плоскостей на фрагмент в реалтайме на существующем железе. Каждую плоскость можно более-менее аппроксимировать через как минимум 8 (на самом деле &amp;mdash; больше) лучей. То есть чтобы получить те же параметры сходимости, rtx или любой другой технологии, основанной на world-space лучах придётся выпустить около 1024 эффективных лучей на пиксел вместо 1. 1024 луча с использованием rtx будут работать в 1024 раза медленнее, чем железо для записи этого демо которое уже стоит как квартира его среднестатистического зрителя. И если вы сейчас подумали : &amp;quot;ну, значит надо подождать, когда видеокарты станут ещё в 1024 раза мощнее и вот тогда заживём!&amp;quot;, то именно от этого я хотел предостеречь &amp;mdash; к моменту, когда видеокарты станут в 1024 раза мощнее, на них можно будет либо гонять эту сцену с диффузными материалами и rtx, либо сцену в 1024 раза сложнее, если использовать более продвинутый алгоритм, чем rtx.&lt;/p&gt;

&lt;p&gt;Я хочу сказать, что как бы хорошо rtx ни подходил для рендеринга отражений, вовсе не факт, что он нас хоть немного приблизил к рендерингу честного GI, достаточно посмотреть на разницу между уравнениями (1) и (4). которая ничуть не уменьшилась. Как бы хорошо rtx ни выглядел для зеркальных поверхностей, у него есть принципиальные проблемы с общностью, из-за которых он в принципе неэффективен для рендеринга матовых поверхностей.&lt;/p&gt;

&lt;p&gt;Об успешности других технологий, используемых в индустрии, можно судить как минимум по их разнообразию. Если бы объективно успешная технология существовала, все бы пользовались только ей. Однако, каждый AAA двиг (а иногда и каждая новая версия) сейчас используют разные подходы к рендерингу GI и у каждого есть свои преимущества и недостатки. Если вам кто-то говорит, что реализовал unbiased gi в realtime без ограничений, не верьте &amp;mdash; вам врут. Точный GI рассчитать сейчас невозможно, вопрос в том, как меньше соврать при его расчёте и как это сделать наиболее эффективно.&lt;/p&gt;

&lt;p&gt;==Почему именно screenspace?==&lt;/p&gt;

&lt;p&gt;Сегодняшние вычислительные мощности находятся на стадии, когда в общем случае рассчитать точный GI в world space в реальном времени просто невозможно. По крайней мере нет ни одного алгоритма, даже приближающегося к этому. Однако, это &amp;mdash; очень интересный момент, так как именно сейчас у mid-end машин едва-едва хватает производительности, чтобы считать его в screenspace, если использовать ряд принципиальных свойств уравнения(1). У кого-то может сложиться впечатление, что &amp;quot;нуу, если screenspace, то это проосто&amp;quot;. Так вот, дисклеймер &amp;mdash; даже в скринспейсе это вообще не просто, иначе бы все это делали. Первая более-менее внятная реализация SSGI, которая появлась в индустрии &amp;mdash; это, пожалуй в unigine:
&lt;br&gt;
&lt;a href=&quot;https://developer.unigine.com/en/devlog/20170531-unigine-2.5&quot; rel=&quot;nofollow&quot;&gt;https://developer.unigine.com/en/devlog/20170531-unigine-2.5&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;
&lt;div class=&quot;video-container&quot;&gt;&lt;div class=&quot;video&quot; id=&quot;tube8&quot; data-value=&quot;Ya4Z-obAkWA&quot;&gt;&lt;img data-value=&quot;Ya4Z-obAkWA&quot; src=&quot;https://img.youtube.com/vi/Ya4Z-obAkWA/maxresdefault.jpg&quot; onload=&quot;skif.loadYoutubeImage(event)&quot; alt=&quot;Запустить видео по клику - Как делать игры&quot; loading=&quot;lazy&quot;&gt;&lt;img class=&quot;play&quot; src=&quot;https://gamedev.ru/_img/youtube-play.svg&quot; onclick=&quot;skif.insertYoutube(8)&quot; onMouseover=&quot;skif.insertYoutube(8)&quot; alt=&quot;Запустить видео по клику - Как делать игры&quot; loading=&quot;lazy&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Они по понятным причинам не раскрывают деталей реализации, однако, судя по их презентации с последнего GDC, волшебства они не реализовали и у их алгоритма есть серьёзные ограничения &amp;mdash; например, у их реализации сильно падает точность с увеличением радиуса действия GI, в моей же реализации радиус &amp;mdash; всегда бесконечность(что физически корректно). Доступные публикации вроде &lt;a href=&quot;https://www.in.tu-clausthal.de/fileadmin/homes/CG/data_pub/paper/SSDO_i3D09.pdf&quot; rel=&quot;nofollow&quot;&gt;https://www.in.tu-clausthal.de/fileadmin/homes/CG/data_pub/paper/SSDO_i3D09.pdf&lt;/a&gt; находятся в совершенно зачаточном состоянии, так как напрочь не умеют far-field global illumination и, следовательно, не являются unbiased.&lt;/p&gt;

&lt;p&gt;Это я к тому, что даже в screenspace со всеми его недостатками, расчёт GI &amp;mdash; вовсе не тривиальная задача и для этого на момент написания статьи по-прежнему не существует надёжных общепризнанных алгоритмов.&lt;/p&gt;

&lt;p&gt;===We need to go deeper===&lt;/p&gt;

&lt;p&gt;Есть более глубинная причина, по которой я считаю screenspace методы особенно интересными. Если вдуматься, какой именно размерности проблема, которую мы решаем? Другими словами, как именно может масштабироваться теоретически существующий оптимальный алгоритм расчёта global illumination? Для ответа на этот вопрос взглянем на самую сложную для расчёта часть уравнения (1) &amp;mdash; интеграл:
&lt;br&gt;
&lt;span class=&quot;mathjax&quot;&gt;\(\int_{\Omega} f(\vec x, \vec \omega_0, \vec \omega_i) L_i(\vec x, \vec \omega_i,) (\vec w_i \cdot \vec n) d\vec \omega_i\)&lt;/span&gt;. Этот интеграл определяет влияние окружения на фрагмент в точке &lt;span class=&quot;mathjax&quot;&gt;\(\vec x\)&lt;/span&gt;. Самый наивный способ для расчёта этого интеграла &amp;mdash; это численно разбить сферу на множество независимых лучей и от интеграла перейти к сумме, как это делает уравнение (4). Именно по такой схеме работают rtx и многие воксельные алгоритмы. Однако, этот способ не учитывает некоторые принципиальные особенности задачи, которую мы решаем, а именно:
&lt;br&gt;
&amp;mdash; точки пересечения со сценой лежащих в одной плоскости лучей, также всегда лежат в одной плоскости
&lt;br&gt;
&amp;mdash; точки пересечения почти параллельных лучей обычно лежат почти в одной плоскости
&lt;br&gt;
&amp;mdash; параллельные лучи, выпущенные почти из одной точки, обычно пересекают сцену почти в одной точке&lt;/p&gt;

&lt;p&gt;Эти свойства наводят на мысль, что решение уравнения (4) через выпускание лучей по полусфере &amp;mdash; это не оптимальный способ, так как он не использует многие принципиальные особенности моделируемоей системы.&lt;/p&gt;

&lt;p&gt;Более осязаемая количественная оценка. Рассчитаем, сколько лучей необходимо для расчёта источника вторичного освещения радиусом &lt;span class=&quot;mathjax&quot;&gt;\(r\)&lt;/span&gt; на расстоянии &lt;span class=&quot;mathjax&quot;&gt;\(R\)&lt;/span&gt; от текущего фрагмента. Если выпускать лучи по полусфере, то понадобится в среднем &lt;span class=&quot;mathjax&quot;&gt;\(N \approx \frac{2\pi}{\Omega}\)&lt;/span&gt; лучей, чтобы его разрешить, где &lt;span class=&quot;mathjax&quot;&gt;\(\Omega \approx \frac{\pi r^2}{R^2}\)&lt;/span&gt; &amp;mdash; телесный угол, под которым виден источник, то есть 
&lt;br&gt;
&lt;span class=&quot;mathjax&quot;&gt;\(N_1 \approx \frac{2R^2}{r^2} \sim \frac{1}{r^2}\)&lt;/span&gt; (5)
&lt;br&gt;
Если же выпускать лучи в экранных координатах, то количество лучей можно оценить как &lt;span class=&quot;mathjax&quot;&gt;\(N \approx {2\pi}{\alpha}\)&lt;/span&gt;, где &lt;span class=&quot;mathjax&quot;&gt;\(\alpha=\frac{r_p}{R_p}\approx\frac{r}{R_p^2}\)&lt;/span&gt; &amp;mdash; угловой размер источника в экранных координатах (&lt;span class=&quot;mathjax&quot;&gt;\(r_p\)&lt;/span&gt;, &lt;span class=&quot;mathjax&quot;&gt;\(R_p\)&lt;/span&gt; &amp;mdash; размер и расстояние до источника в экранных координатах). Также заметим, что при использовании ортогональной проекции, &lt;span class=&quot;mathjax&quot;&gt;\(R=R_p\)&lt;/span&gt;. Другими словами, в экранных координатах нам понадобится
&lt;br&gt;
&lt;span class=&quot;mathjax&quot;&gt;\(N_2 \approx \frac{2\pi R^2}{r} \sim \frac{1}{r}\)&lt;/span&gt; (6)
&lt;br&gt;
лучей, чтобы разрешить тот же самый источник. &lt;/p&gt;

&lt;p&gt;То есть если количество лучей растёт обратно пропорционально первой степени радиуса источника в экранных координатах и второй &amp;mdash; в мировых. Это очень важное замечание, указывающее на то, что задача расчёта GI может при определённых условиях масштабироваться с первой степенью точности, а не второй. Обращаю внимание на &amp;quot;может&amp;quot;, так как в image space часть информации может теряться, но иногда &lt;b&gt;может&lt;/b&gt; и нет. Вопрос в том, как использовать это свойство, чтобы воспользоваться этим самым &lt;b&gt;может&lt;/b&gt;.&lt;/p&gt;

&lt;p&gt;===Even deeper===&lt;/p&gt;

&lt;p&gt;Ещё одна физическая особенность, указывающая, что привычные трёхмерные сцены могут оказаться более двумерными, чем мы привыкли, кроется в явлении &lt;a href=&quot;https://en.wikipedia.org/wiki/Holography&quot; rel=&quot;nofollow&quot;&gt;голографии&lt;/a&gt;. Здесь под голографией понимается вовсе не любой процесс, создающий иллюзию висящего в воздухе объекта при помощи зеркал, а очень конкретный фический процесс, позволяющий сохранить на двумерной фотопластинке световое поле трёхмерного объекта. Если в двух словах, то суть явления заключается в том, что сохранённое таким образом световое поле объекта при определённых физических условиях &amp;quot;воспроизвести&amp;quot; из голограммы, что создаст световое поле идентичное тому, что испускает сам объект, даже если его убрать. Например, на википедии иллюстрацией даётся изображение мыши с разных ракурсов, полученное из одной голограммы:&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://upload.wikimedia.org/wikipedia/commons/5/5f/Holomouse2.jpg&quot; alt=&quot;Изображение&quot; /&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;&lt;/p&gt;

&lt;p&gt;Здесь можно провести параллель между представлением сцены в двумерном виде в screenspace техниках и в использовании голограмм. В то время как у gbuffer&apos;а есть очевидные недостатки потери информации о загороженных объектах и объектах за камерой, принципиальное свойство сохранения трёхмерной поверхности в двумерном gbuffer&apos;е действительно существует. А в случае голограммы этих недостатков вообще нет &amp;mdash; информация о сцене не теряется даже для загороженных участков и участков, непосредственное не попавших в &amp;quot;кадр&amp;quot;. Чтобы более наглядно представить, насколько интересным образом информация кодируется в голограмме, вдумайтесь &amp;mdash; любой фрагмент голограммы кодирует информацию сразу обо всей сцене. То есть если от голограммы ножницами отрезать половину, в ней по-прежнему можно наблюдать всю сцену, просто в худшем разрешении. Очевидно, с gbuffer&apos;ом такой трюк не прокатит.&lt;/p&gt;

&lt;p&gt;Заинтригованный этим свойством, я в исследовательских целях написал численный решатель для волновых уравнений оптики, позволяющий прямым образом моделировать процессы, происходящие в голограммах. Вот так, например, выглядит перевёрнутое изображение микки-мауса, полученное из его голограммы:&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://drive.google.com/uc?export=view&amp;id=1spPSB8121j3oAshHKmcVshzMU1w3lXBF&quot; alt=&quot;Изображение&quot; /&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;&lt;/p&gt;

&lt;p&gt;Как нетрудно заметить, результат выглядит не особо впечатляюще. Однако, у него есть несколько принципиально интересных свойств, а именно, он позволяет хранить трёхмерную сцену в двумерном представлении без потери информации о загороженных фрагментах (например, микки-мауса можно наблюдать сбоку и даже сзади). &lt;/p&gt;

&lt;p&gt;&amp;quot;Какое же это отношение имеет к рендерингу GI?&amp;quot; &amp;mdash; спросите вы? А совершенно прямое. Это всего лишь ещё одно доказательство того, что теоретически оптимальный алгоритм расчёта global illumination должен работать с представлением сцены в двумерном представлении того или иного вида и масштабироваться как масштабируется разрешение плоскости, а не трёхмерного пространства. Поэтому моя реализация GI в screenspace &amp;mdash; это всего лишь один из способов использования этого важного свойства, и я твёрдо уверен, что это можно сделать и лучше.&lt;/p&gt;

&lt;p&gt;В следующей части статьи я расскажу непосредственно о реализации своего подхода к расчёту screenspace GI. Однако, я считаю необходимым донести эти соображения, так как именно они определили направление развития моего алгоритма.&lt;/p&gt;

&lt;p&gt;==Алгоритмическая сложность трассировки одного луча==&lt;/p&gt;

&lt;p&gt;В предыдущем разделе мы установили важное свойство, согласно которому размерность проблемы global illumination должна соответствовать размерности image space, что на одно измерение меньше, чем размерность world space. Однако, существует способ снизить размерность, убрав ещё одно измерение. Дело в том, что уравнение (1) считается вовсе не для одного фрагмента на экране, а для всех. В результате этого все лучи для фрагментов, лежащих на этом самом луче, будут совпадать. Это &amp;mdash; принципиально важное наблюдение, которое используется в &lt;a href=&quot;http://wili.cc/research/lsao/lsao.pdf&quot; rel=&quot;nofollow&quot;&gt;line sweep ambient occlusion&lt;/a&gt;. К сожалению, экстраполировать этот метод на расчёт global illumination не является тривиальной задачей, однако, это теоретически возможно. Перефразируя, идея метода заключается в том, что все лучи, лежащие на одной прямой в экранном пространстве, являются общими для всех фрагментов, лежащих на этой прямой. Более того, прямой в экранном пространстве соответствует целое семейство лучей, образующих плоскость в мировом пространстве, то есть теоретически влияние их всех можно рассчитать, используя только данные с фрагментов с одной прямой.&lt;/p&gt;

&lt;p&gt;Ни один из существующих алгоритмов расчёта GI не использует это важное свойство в необходимой степени, то есть все алгоритмы решают проблему большей размерности, чем одна должна быть. Моя реализация SSGI, о которой пойдёт речь во второй части это статьи, также не использует это свойство и я это считаю огромным недостатком. Я уже реализовал не один десяток алгоритмов, пытающихся воспользоваться этим важным свойством, но это &amp;mdash; далеко не так просто, как хотелось бы, хотя потенциал у этой идеи огромен, так как позволяет рассчитывать целое семейство лучей, образующих плоскость, за один проход.&lt;/p&gt;

&lt;p&gt;==Заключение==&lt;/p&gt;

&lt;p&gt;В этой статье мы рассмотрели исторически трудный переход от компьютерной графики, основанной на растеризации, к расчёту global illumination. Также я обратил внимание на проблемы существующих подходов и на их реальные возможности, которые по сути гораздо скромнее, чем хотелось бы верить. Наконец, мы рассмотрели ряд принципиальных особенностей уравнения рендеринга, которые необходимо использовать, чтобы решать его достаточно эффективно. В следующей части статьи я надеюсь описать более подробно тот алгоритм, к которому я пришёл в итоге, в демо, которое многие из вас, вероятно, уже видели:&lt;/p&gt;

&lt;p&gt;
&lt;div class=&quot;video-container&quot;&gt;&lt;div class=&quot;video&quot; id=&quot;tube9&quot; data-value=&quot;OPFvcsQAKjc&quot;&gt;&lt;img data-value=&quot;OPFvcsQAKjc&quot; src=&quot;https://img.youtube.com/vi/OPFvcsQAKjc/maxresdefault.jpg&quot; onload=&quot;skif.loadYoutubeImage(event)&quot; alt=&quot;Запустить видео по клику - Как делать игры&quot; loading=&quot;lazy&quot;&gt;&lt;img class=&quot;play&quot; src=&quot;https://gamedev.ru/_img/youtube-play.svg&quot; onclick=&quot;skif.insertYoutube(9)&quot; onMouseover=&quot;skif.insertYoutube(9)&quot; alt=&quot;Запустить видео по клику - Как делать игры&quot; loading=&quot;lazy&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/p&gt;&lt;/p&gt;</description>
</item>
<item>
  <guid isPermaLink="true">https://gamedev.ru/code/news/?id=7667</guid>
  <pubDate>Mon, 04 Mar 2019 16:57:19 GMT</pubDate>
  <title>Ray Tracing Gem. Доступно бесплатно!</title>
  <link>https://gamedev.ru/code/news/?id=7667</link>
  <comments>https://gamedev.ru/code/forum/?id=242671</comments>
  <category>Графика</category>
  <description>
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Опубликована PDF-книга &amp;laquo;Ray Tracing Gem. High-quality and real-time rendering with DXR and other APIs&amp;raquo; на 600+ страниц, подготовленная NVIDIA и разными специалистами из области рейтрейсинга. Можно скачать отсюда:
&lt;br&gt;
&lt;a href=&quot;https://link.springer.com/book/10.1007/978-1-4842-4427-2&quot; rel=&quot;nofollow&quot;&gt;https://link.springer.com/book/10.1007/978-1-4842-4427-2&lt;/a&gt;&lt;/p&gt;</description>
</item>
<item>
  <guid isPermaLink="true">https://gamedev.ru/code/articles/vulkan_sync</guid>
  <pubDate>Tue, 26 Feb 2019 14:56:18 GMT</pubDate>
  <title>Синхронизации в Vulkan</title>
  <link>https://gamedev.ru/code/articles/vulkan_sync</link>
  <comments>https://gamedev.ru/code/forum/?id=242281</comments>
  <category>Графика</category>
  <category>Vulkan</category>
  <description>
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Одно из важных отличий Vulkan от более старых графических &lt;a href=&quot;https://gamedev.ru/code/terms/API&quot; title=&quot;API: Application Programming Interface&quot;&gt;API&lt;/a&gt;, это больший контроль над синхронизациями как с CPU, так и внутри GPU. И как всегда многопоточность и синхронизация &amp;mdash; это достаточно сложная тема. Стоит помнить, что драйвер Vulkan не обязан оптимизировать вызовы API, поэтому для максимальной производительности синхронизации должны быть расставлены наиболее оптимальным образом.&lt;/p&gt;
&lt;br&gt;
 
&lt;br&gt;
==Термины==
&lt;p&gt;Для большей совместимости с оригинальной документацией многие термины не будут переводиться, либо английская версия терминов будет указана рядом в скобках.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Host&lt;/b&gt; &amp;mdash; тот, кто использует Vulkan API, обычно это код выполняемый на процессоре (CPU).
&lt;br&gt;
&lt;b&gt;Device&lt;/b&gt; &amp;mdash; тот, кто выполняет команды, это драйвер и дискретная или интегрированная видеокарта (GPU), но например при софтварной реализации это может быть и процессор (CPU).
&lt;br&gt;
&lt;b&gt;Драйвер&lt;/b&gt; &amp;mdash; здесь под этим понимается программная и аппаратная часть скрытая за Vulkan API, в документации для этого используется термин implementation.
&lt;br&gt;
&lt;b&gt;Очередь&lt;/b&gt; (VkQueue) &amp;mdash; очередь команд, выполняемых на Device.
&lt;br&gt;
&lt;b&gt;Execution dependency&lt;/b&gt; &amp;mdash; это зависимость от порядка выполнения команд, все что начало выполняться, будет завершено до начала выполнения следующих операций.
&lt;br&gt;
&lt;b&gt;Memory dependency&lt;/b&gt; &amp;mdash; аналогично execution dependency, но еще и вся запись в память будет завершена до начала выполнения следующих операций.&lt;/p&gt;

&lt;p&gt;==Синхронизация между командами на Device==&lt;/p&gt;

&lt;p&gt;Все современные GPU выполняют команды параллельно, вызовы отрисовки, вызовы вычислительного &lt;a href=&quot;https://gamedev.ru/code/terms/Shader&quot; title=&quot;Шейдер (Shader)&quot;&gt;шейдера&lt;/a&gt; стараются занять все свободные вычислительные ядра GPU, единственное что может им помешать &amp;mdash; это наличие зависимостей между командами &amp;mdash; синхронизация.&lt;/p&gt;

&lt;p&gt;GPU содержит конвейер, который разделен на этапы, на каждом этапе выполняется только определенная операция. Некоторые этапы выполняются последовательно, например фрагментный шейдер (fragment shader) выполняется всегда после вершинного шейдера (vertex shader), а вершинный шейдер &amp;mdash; после чтения из вершинного буфера (vertex input). Этапы рисования, вычислений (compute shader) и копирования (transfer) всегда запускаются с начала конвейера (top of pipe) и завершаются в конце (bottom of pipe).&amp;nbsp; В документации используется понятия logically earlier и logically latest, они означают этапы, расположенные выше и ниже по схеме от выбранного этапа, и не включают в себя этапы, выполняемые параллельно. (&lt;a href=&quot;https://www.khronos.org/registry/vulkan/specs/1.1/html/chap6.html#synchronization-pipeline-stages&quot; rel=&quot;nofollow&quot;&gt;глава 6.1.2 pipeline stages&lt;/a&gt;)
&lt;br&gt;
&lt;img src=&quot;https://gamedev.ru/files/images/allstages.png&quot; alt=&quot;vulkan-all-stages | Синхронизации в Vulkan&quot; title=&quot;vulkan-all-stages&quot;&gt;&lt;/div&gt;&lt;/p&gt;

&lt;p&gt;Для установки зависимости между разными командами или между этапами конвейера используется команда &lt;b&gt;vkCmdPipelineBarrier&lt;/b&gt;. Параметр srcStageMask указывает какие этапы должны завершиться до того, как начнут выполняться этапы, указанные в dstStageMask, то есть определяют execution dependency.&lt;/p&gt;

&lt;p&gt;На каждом этапе есть доступ только к некоторым видам кэша, они хранят промежуточные значения при записи, либо сохраняют значения для быстрого чтения. В некоторых случаях, рассмотренных ниже, кэш сбрасывается неявно, здесь же рассмотрим явный сброс кэша.(&lt;a href=&quot;https://www.khronos.org/registry/vulkan/specs/1.1/html/chap6.html#synchronization-access-masks&quot; rel=&quot;nofollow&quot;&gt;глава 6.1.3 access mask&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Функция vkCmdPipelineBarrier принимает массивы из VkMemoryBarrier, VkBufferMemoryBarrier и VkImageMemoryBarrier, где srcAccessMask и dstAccessMask определяют memory dependency &amp;mdash; какие данные из кэша должны быть видимы (srcAccessMask) и где эти данные будут использоваться (dstAccessMask). VkMemoryBarrier создает global memory barrier, который затрагивает все последующие этапы конвейера и все последующие команды внутри очереди (VkQueue). VkBufferMemoryBarrier и VkImageMemoryBarrier позволяют явно указать диапазон данных для которого сбрасывается кэш и дает возможность драйверу выполнять другие команды, которые не зависят от этого диапазона данных.&lt;/p&gt;

&lt;p&gt;Операции чтения могут выполняться параллельно, запись после чтения требует только execution dependency, то есть параметры srcAccessMask и dstAccessMask указывать необязательно. Запись после записи и чтение после записи требует memory dependency &amp;mdash; параметр srcAccessMask обязательно должен содержать флаг, указывающий где происходила запись, а параметр dstAccessMask &amp;mdash; флаг, где будет происходить чтение.&lt;/p&gt;

&lt;p&gt;VkImageMemoryBarrier содержит поля oldLayout и newLayout которые нужны для &lt;a href=&quot;https://www.khronos.org/registry/vulkan/specs/1.1/html/chap6.html#synchronization-image-layout-transitions&quot; rel=&quot;nofollow&quot;&gt;image layout transition&lt;/a&gt;. Image layout нужен чтобы оптимизировать доступ к данным текстуры для некоторых видов операций чтения и записи, так например VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL оптимизирует данные текстуры для чтения в шейдере, а VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL позволяет драйверу сжимать данные при рисовании в текстуру, что увеличивает пропускную способность памяти, но переход из сжатого формата в несжатый формат, например в VK_IMAGE_LAYOUT_GENERAL, приводит к разжатию и потере времени.&lt;/p&gt;

&lt;p&gt;VkBufferMemoryBarrier и VkImageMemoryBarrier содержат поля srcQuueFamilyIndex и dstQueueFamilyIndex для операций &lt;a href=&quot;https://www.khronos.org/registry/vulkan/specs/1.1/html/chap6.html#synchronization-queue-transfers&quot; rel=&quot;nofollow&quot;&gt;queue family ownership transfer&lt;/a&gt;, если передача между очередями не нужна, то указывается флаг VK_QUEUE_FAMILY_IGNORED, подробнее передача между очередями разобрана в примере с асинхронными вычислениями.&lt;/p&gt;

&lt;p&gt;Функция vkCmdPipelineBarrier не дает прямого контроля над механизмом инвалидации кэша, так запись из кэша в глобальную память может произойти и без вызова синхронизации, и пропущенный в этом месте барьер никак не отразится на работе программы, но на другом GPU, на другом драйвере или просто при других обстоятельствах пропущенный барьер приведет к неправильной работе программы. Слои валидации не отслеживают насколько правильно настроены memory dependencies, это достаточно сложная задача, но слои валидации проверяют image layout transition и queue ownership transfer, что уже неплохо.&lt;/p&gt;

&lt;p&gt;События (VkEvent) работают аналогично барьеру, но разделены на две части &amp;mdash; сигнал и ожидание, также как и барьер, события работают только внутри одной очереди и&amp;nbsp; позволяют более явно указать зависимость между командами.&lt;/p&gt;

&lt;p&gt;Проход рендера (VkRenderPass) объединяет в себе зависимости для текстур, в которые идет рисование, до и после прохода рендера, а также зависимости между отдельными этапами рисования (subpass). Зависимости указываются через VkSubpassDependency, по аналогии с барьерами. Барьеры внутри прохода рендера разрешены, но для них существуют ограничения: нужно указать VkSubpassDependency, где srcSubpass и dstSubpass равны и параметры барьера должны совпадать с тем, что было указано в VkSubpassDependency. (&lt;a href=&quot;https://www.khronos.org/registry/vulkan/specs/1.1/html/chap7.html#renderpass&quot; rel=&quot;nofollow&quot;&gt;глава 7 render pass&lt;/a&gt;, &lt;a href=&quot;https://www.khronos.org/registry/vulkan/specs/1.1/html/chap6.html#synchronization-pipeline-barriers-subpass-self-dependencies&quot; rel=&quot;nofollow&quot;&gt;subpass self-dependency&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Команда &lt;b&gt;vkCmdWriteTimestamp&lt;/b&gt; создает execution barrier, что может препятствовать распараллеливанию команд, не стоит использовать эту команду слишком часто, а точное время выполнения каждой команды может показать только специализированный профайлер.&lt;/p&gt;

&lt;p&gt;==Порядок выполнения команд на Device==&lt;/p&gt;

&lt;p&gt;В документации явно определен только порядок в котором команды, записанные на стороне Host, будут прочитаны на стороне Device, это &lt;a href=&quot;https://www.khronos.org/registry/vulkan/specs/1.1/html/chap6.html#synchronization-implicit&quot; rel=&quot;nofollow&quot;&gt;submission order&lt;/a&gt;. Команды vkQueueSubmit в пределах одной очереди читаются на стороне Device в том же порядке, в каком были вызваны на стороне Host. Каждый командный буфер будет прочитан только после прочтения предыдущего командного буфера в батче, либо последнего командного буфера предыдущего батча. Команды внутри командного буфера читаются в том порядке, в котором они были записаны в буфер. Порядок, в котором выполняются команды, определяется только через зависимости, созданные барьерами и событиями. Командный буфер не создает никаких дополнительных синхронизаций, при переходе на следующий командный буфер происходит только смена состояний (пайпалайны, дескрипторы и так далее).&lt;/p&gt;

&lt;p&gt;На схеме красные линии между командами &amp;mdash; зависимости для ресурсов (memory dependency), красная полоса &amp;mdash; глобальный барьер (global execution barrier).
&lt;br&gt;
&lt;img src=&quot;https://gamedev.ru/files/images/subord3.png&quot; alt=&quot;submission-order | Синхронизации в Vulkan&quot; title=&quot;submission-order&quot;&gt;&lt;/div&gt;&lt;/p&gt;

&lt;p&gt;Для прохода рендера (VkRenderPass) существуют дополнительные правила: каждый отдельный подпроход (subpass) может выполняться параллельно с другими и в любом порядке, если между ними не установлена зависимость через VkSubpassDependency. Внутри подпрохода (subpass) команды рисования выполняются в соответствии с порядком генерации примитивов (&lt;a href=&quot;https://www.khronos.org/registry/vulkan/specs/1.1/html/chap19.html#drawing-primitive-order&quot; rel=&quot;nofollow&quot;&gt;primitive order&lt;/a&gt;) и порядком растеризации (&lt;a href=&quot;https://www.khronos.org/registry/vulkan/specs/1.1/html/chap24.html#primrast-order&quot; rel=&quot;nofollow&quot;&gt;rasterization order&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;==Синхронизация между очередями и между батчами==&lt;/p&gt;

&lt;p&gt;Зависимости между батчами внутри одной очереди или между разными очередями, а также с presentation engine, устанавливается через семафоры (&lt;b&gt;VkSemaphore&lt;/b&gt;). На схеме стрелками указаны зависимости, созданные семафорами.
&lt;br&gt;
&lt;img src=&quot;https://gamedev.ru/files/images/sem1.png&quot; alt=&quot;vulkan-sem1 | Синхронизации в Vulkan&quot; title=&quot;vulkan-sem1&quot;&gt;&lt;/div&gt;&lt;/p&gt;

&lt;p&gt;Семафор может быть только в двух состояниях &amp;mdash; сигнальном и не сигнальном. Если передается в функции vkQueueSubmit и vkQueueBindSparse в качестве параметра pSignalSemaphores, то ожидает завершения батча на стороне Device и переходит в сигнальное состояние. Если передается в качестве параметра pWaitSemaphores, то блокирует выполнение батча, пока семафор не перейдет в сигнальное состояние, после этого сбрасывает семафор в не сигнальное состояние. Команда vkAcquireNextImageKHR переводит семафор в сигнальное состояние, когда presentation engine завершил работу с изображением и можно его использовать для рисования нового кадра. Несколько батчей не могут ожидать сигнала от одно и того же семафора.
&lt;br&gt;
&lt;img src=&quot;https://gamedev.ru/files/images/sem-sync.png&quot; alt=&quot;semaphore | Синхронизации в Vulkan&quot; title=&quot;semaphore&quot;&gt;&lt;/p&gt;

&lt;p&gt;Между операцией сигнала семафора и ожиданием создается &lt;b&gt;memory dependency&lt;/b&gt; &amp;mdash; все данные из кэша будут записаны в глобальную память и все команды в очереди завершат выполнение до начала этапов указанных в pWaitDstStageMask. (&lt;a href=&quot;https://www.khronos.org/registry/vulkan/specs/1.1/html/chap6.html#synchronization-semaphores-signaling&quot; rel=&quot;nofollow&quot;&gt;глава 6.4.1 semaphore signaling&lt;/a&gt;, &lt;a href=&quot;https://www.khronos.org/registry/vulkan/specs/1.1/html/chap6.html#synchronization-semaphores-waiting&quot; rel=&quot;nofollow&quot;&gt;глава 6.4.2 semaphore waiting&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Функция &lt;b&gt;vkQueuePresentKHR&lt;/b&gt; дополнительно гарантирует, что все записи в текстуры свопчейна, на которые указывают индексы (pImageIndices) будут видимы при выводе изображения на экран.&lt;/p&gt;

&lt;p&gt;==Синхронизация между Host и Device==&lt;/p&gt;

&lt;p&gt;Для этого есть явный примитив синхронизации VkFence, а также неявная синхронизация при вызове некоторых функций.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;VkFence&lt;/b&gt; передается в функции vkQueueSubmit, vkQueueBindSparse, vkAcquireNextImageKHR, при завершении выполнения функций fence переводится в сигнальное состояние и находится в нем до явного вызова vkResetFences, дождаться завершения команд можно вызовом блокирующей функции vkWaitForFences. Команды vkQueueWaitIdle, vkDeviceWaitIdle работают схожим образом, но ожидают завершения абсолютно всех команд в очереди и на всем GPU устройстве соответственно. Вызовы vkWaitForFences, vkQueueWaitIdle, vkDeviceWaitIdle создают global memory dependency, значит все изменения на Device будут видимы для всех последующих командах на Device и на Host.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;VkEvent&lt;/b&gt; может быть переведен в сигнальное состояние на стороне Host, тогда блокировка произойдет на стороне Device при вызове vkCmdWaitEvents, где в srcStageMask указано VK_PIPELINE_STAGE_HOST_BIT, это можно использовать в редких случаях, когда необходимо передать данные на Device уже после начала выполнения командного буфера. В этом случае нужно явно указать memory dependency между Host и Device добавив VkMemoryBarrier, где в srcAccessMask содержится VK_ACCESS_HOST_WRITE_BIT.&lt;/p&gt;

&lt;p&gt;Команда &lt;b&gt;vkQueueSubmit&lt;/b&gt; делает неявную синхронизацию при которой все изменения памяти на Host становятся видимы на Device, что аналогично вызову vkFlushMappedMemoryRanges для всех изменений памяти (&lt;a href=&quot;https://www.khronos.org/registry/vulkan/specs/1.1/html/chap6.html#synchronization-submission-host-writes&quot; rel=&quot;nofollow&quot;&gt;глава 6.9&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Команда &lt;b&gt;vkFlushMappedMemoryRanges&lt;/b&gt; делает видимым на Device все изменения памяти на Host, но только для заданных диапазонов. Для памяти выделенной на куче с флагом VK_MEMORY_PROPERTY_COHERENT_BIT вызов этой функции не имеет эффекта, так как все изменения сразу же отправляются на Device.&lt;/p&gt;

&lt;p&gt;Команда &lt;b&gt;vkInvalidateMappedMemoryRanges&lt;/b&gt; делает видимым на Host все изменения памяти на Device, но только для заданных диапазонов.&lt;/p&gt;

&lt;p&gt;==Синхронизация при вызове команд на Host==&lt;/p&gt;

&lt;p&gt;Vulkan позволяет обращаться к API из любого потока, но требует чтобы доступ к некоторым данным осуществлялся последовательно. Для этого подойдет mutex, spinlock и другие примитивы синхронизации. Для архитектур с relaxed memory ordering, например ARM, требуется инвалидация кэша. При использовании std::mutex это происходит автоматически, но если вы используете самописный spinlock на атомарных операциях, то об этом стоит помнить. (&lt;a href=&quot;https://www.khronos.org/registry/vulkan/specs/1.1/html/chap2.html#fundamentals-threadingbehavior&quot; rel=&quot;nofollow&quot;&gt;глава 2.6&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;Ссылки&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.khronos.org/registry/vulkan/specs/1.1/html/chap1.html#introduction&quot; rel=&quot;nofollow&quot;&gt;Документация по vulkan&lt;/a&gt;
&lt;br&gt;
&lt;a href=&quot;https://github.com/KhronosGroup/Vulkan-Docs/wiki/Synchronization-Examples&quot; rel=&quot;nofollow&quot;&gt;Примеры синхронизаций&lt;/a&gt;
&lt;br&gt;
&lt;a href=&quot;https://gpuopen.com/vulkan-barriers-explained/&quot; rel=&quot;nofollow&quot;&gt;Объяснение барьеров от AMD&lt;/a&gt;
&lt;br&gt;
&lt;a href=&quot;http://cpp-rendering.io/barriers-vulkan-not-difficult/&quot; rel=&quot;nofollow&quot;&gt;Еще одна попытка объяснить барьеры&lt;/a&gt;
&lt;br&gt;
&lt;a href=&quot;https://mynameismjp.wordpress.com/2018/03/06/breaking-down-barriers-part-1-whats-a-barrier/&quot; rel=&quot;nofollow&quot;&gt;Очень подробный разбор устройства GPU, работы драйвера, синхронизации и так далее&lt;/a&gt; &amp;mdash; стоит прочитать все 6 частей
&lt;br&gt;
&lt;a href=&quot;https://gpuopen.com/understanding-gpu-context-rolls/&quot; rel=&quot;nofollow&quot;&gt;Объясняют context rolls&lt;/a&gt; &amp;mdash; ограниченное количество команд, которые могут выполняться параллельно.
&lt;br&gt;
&lt;a href=&quot;https://github.com/philiptaylor/vulkan-sync/blob/master/memory.md&quot; rel=&quot;nofollow&quot;&gt;vulkan-sync&lt;/a&gt; &amp;mdash; более детально описано как работает память, кэш, сжатие текстур и так далее.&lt;/p&gt;

&lt;p&gt;Теперь, зная о всех сложностях синхронизации, стоит задуматься, а нужно ли вам это? В качестве альтернатив есть множество оберток, которые упрощают использование графических api, ценой некоторых ограничений функционала и небольшого замедления на CPU.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/GPUOpen-LibrariesAndSDKs/V-EZ&quot; rel=&quot;nofollow&quot;&gt;Vulkan-EZ&lt;/a&gt; &amp;mdash; разработка AMD, автоматизирует расстановку пайплайн барьеров и сохраняет прямой доступ к vulkan api.
&lt;br&gt;
&lt;a href=&quot;https://github.com/azhirnov/FrameGraph&quot; rel=&quot;nofollow&quot;&gt;FrameGraph&lt;/a&gt; &amp;mdash; разработано автором статьи, абстрагирует от Vulkan, заменяет все синхронизации на зависимости между батчами и зависимости между тасками внутри батча.
&lt;br&gt;
&lt;a href=&quot;https://github.com/NVIDIAGameWorks/Falcor&quot; rel=&quot;nofollow&quot;&gt;Falcor&lt;/a&gt; &amp;mdash; более высокоуровневая абстракция от Nvidia, поддерживает DX12, DXR, Vulkan, предназначен для прототипирования.
&lt;br&gt;
&lt;a href=&quot;https://github.com/Themaister/Granite&quot; rel=&quot;nofollow&quot;&gt;Granite&lt;/a&gt; &amp;mdash; рендер граф, автоматизирует расстановку барьеров между рендер пассами.
&lt;br&gt;
&lt;a href=&quot;https://github.com/DiligentGraphics/DiligentEngine&quot; rel=&quot;nofollow&quot;&gt;DiligentEngine&lt;/a&gt; &amp;mdash; абстракция над всеми графическими API, поддерживает автоматическую и ручную расстановку синхронизаций. 
&lt;br&gt;
 
&lt;br&gt;
Далее идет подробный разбор примеров.
&lt;br&gt;
&lt;a href=&quot;https://gamedev.ru/code/articles/vulkan_sync?page=2&quot;&gt;2. синхронизации между Host и Device, семафоры, многопоточность&lt;/a&gt;
&lt;br&gt;
&lt;a href=&quot;https://gamedev.ru/code/articles/vulkan_sync?page=3&quot;&gt;3. примеры барьеров&lt;/a&gt;
&lt;br&gt;
&lt;a href=&quot;https://gamedev.ru/code/articles/vulkan_sync?page=4&quot;&gt;4. синхронизация между этапами конвейера&lt;/a&gt;
&lt;br&gt;
&lt;a href=&quot;https://gamedev.ru/code/articles/vulkan_sync?page=5&quot;&gt;5. асинхронные вычисления&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;a href=&quot;https://gamedev.ru/code/articles/vulkan_sync?page=2&quot;&gt;Продолжение&lt;/a&gt;&lt;/p&gt;</description>
</item>
<item>
  <guid isPermaLink="true">https://gamedev.ru/code/tip/IntelliSenseForAll</guid>
  <pubDate>Tue, 20 Nov 2018 19:35:26 GMT</pubDate>
  <title>Подсветка кода и IntelliSense в Visual Studio для любых типов файлов.</title>
  <link>https://gamedev.ru/code/tip/IntelliSenseForAll</link>
  <category>Общее</category>
  <category>IntelliSense</category>
  <category>Visual Studio</category>
  <description>
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Привет всем! Сейчас я поделюсь с вами способом добавления кастомных типов исходников в MS Visual Studio.&lt;/p&gt;
&lt;p&gt;Для примера мы сделаем так, чтобы файлы с расширением &lt;b&gt;*.m&lt;/b&gt; и &lt;b&gt;*.mm&lt;/b&gt; (Objective-C) открывались с подсветкой кода и для них работал &lt;b&gt;IntelliSense&lt;/b&gt;.&lt;/p&gt;

&lt;p&gt;1) Зайдите в &lt;b&gt;Tools-&amp;gt;Options...&lt;/b&gt; Раскройте &lt;b&gt;Text Editor&lt;/b&gt; и выберите пункт &lt;b&gt;File Extension&lt;/b&gt;
&lt;br&gt;
&amp;nbsp; &amp;nbsp; В этом окне в поле &lt;b&gt;Extension:&lt;/b&gt; введите &lt;b&gt;m&lt;/b&gt;, в списке &lt;b&gt;Editor:&lt;/b&gt; выберите &lt;b&gt;Microsoft Visual C++&lt;/b&gt; и нажмите &lt;b&gt;Add&lt;/b&gt;.
&lt;br&gt;
&amp;nbsp; &amp;nbsp; Повторите для расширения &lt;b&gt;mm&lt;/b&gt;.
&lt;br&gt;
&amp;nbsp; &amp;nbsp; Нажмите &lt;b&gt;ОК&lt;/b&gt;&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://gamedev.ru/files/images/screen_01.gif&quot; alt=&quot;screen_01 | Подсветка кода и IntelliSense в Visual Studio для любых типов файлов.&quot; title=&quot;screen_01&quot;&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;p&gt;2) Закройте все открытые копии &lt;b&gt;Visual Studio&lt;/b&gt;.
&lt;br&gt;
3) &lt;b&gt;Start -&amp;gt; Run&lt;/b&gt; (или &lt;b&gt;Win + R&lt;/b&gt;) и введите &lt;b&gt;regedit&lt;/b&gt; и нажмите &lt;b&gt;OK&lt;/b&gt;.
&lt;br&gt;
4) Пройдите по разделам &lt;b&gt;HKEY_CURRENT_USER-&amp;gt;Software-&amp;gt;Microsoft-&amp;gt;VCExpress-&amp;gt;9.0-&amp;gt;Languages-&amp;gt;Language Services-&amp;gt;C/C++&lt;/b&gt;
&lt;br&gt;
&amp;nbsp; &amp;nbsp; Здесь &lt;u&gt;дважды кликните&lt;/u&gt; на &lt;b&gt;NCB Default C/C++ Extensions&lt;/b&gt; и допишите в &lt;b&gt;&lt;i&gt;конец строки (!!!)&lt;/i&gt;&lt;/b&gt; наши расширения &lt;b&gt;(.m;.mm;)&lt;/b&gt;
&lt;br&gt;
&amp;nbsp; &amp;nbsp; обратите внимание, что все расширения отделяются друг от друга точкой с запятой ( &lt;b&gt;;&lt;/b&gt; )
&lt;br&gt;
&amp;nbsp; &amp;nbsp; нажмите &lt;b&gt;OK&lt;/b&gt; и закройте редактор реестра.&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://gamedev.ru/files/images/screen_02.gif&quot; alt=&quot;screen_02 | Подсветка кода и IntelliSense в Visual Studio для любых типов файлов.&quot; title=&quot;screen_02&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;p&gt;5) Удалите &lt;b&gt;*.ncb&lt;/b&gt; файл из папки с вашим проектом.
&lt;br&gt;
6) Откройте Ваш проект и откройте любой Objective-C файл. Теперь у вас для него должна работать подсветка С++ кода, а также IntelliSense.&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://gamedev.ru/files/images/screen_03.gif&quot; alt=&quot;screen_03 | Подсветка кода и IntelliSense в Visual Studio для любых типов файлов.&quot; title=&quot;screen_03&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;p&gt;Таким образом, очень удобно добавлять подсветку и поддержку IntelliSense в файлы CUDA, OpenCL, GLSL, HLSL и т.д.&lt;/p&gt;

&lt;p&gt;P.S. Для автоматизации процесса можно создать файл *.reg примерно следующего содержания (дан пример для VSExpress 2008):&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;overflow full&quot;&gt;&lt;pre&gt;Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Microsoft\VCExpress\9.0\Languages\Language Services\C/C++]
&amp;quot;NCB Default C/C++ Extensions&amp;quot;=&amp;quot;.cpp;.cxx;.c;.cc;.h;.hh;.hxx;.hpp;.inl;.tlh;.tli;.m;.mm;&amp;quot;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;bound&quot;&gt;</description>
</item>
<item>
  <guid isPermaLink="true">https://gamedev.ru/code/articles/vulkan_raytracing_part2</guid>
  <pubDate>Sun, 18 Nov 2018 18:48:20 GMT</pubDate>
  <title>Пишем простой рейтрейсер используя Vulkan Raytracing</title>
  <link>https://gamedev.ru/code/articles/vulkan_raytracing_part2</link>
  <comments>https://gamedev.ru/code/forum/?id=240273</comments>
  <category>Графика</category>
  <category>графика</category>
  <category>raytracing</category>
  <category>rtx</category>
  <category>Vulkan</category>
  <category>рейтрейсинг</category>
  <description>
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Всем привет! Сегодня я расскажу вам, как получить результат, изображенный на заглавной картинке к этой статье, используя Vulkan Raytracing.&lt;/p&gt;
&lt;p&gt;==Часть 0. Приветствие==&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://gamedev.ru/files/images/rtxon_part2.png&quot; alt=&quot;rtxON_part2 | Пишем простой рейтрейсер используя Vulkan Raytracing&quot; title=&quot;rtxON_part2&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;p&gt;В &lt;a href=&quot;https://gamedev.ru/code/articles/vulkan_raytracing&quot;&gt;прошлый раз&lt;/a&gt;, мы с вами рассмотрели, что же из себя представляет Vulkan Ray Tracing, и как с ним работать. Итогом той статьи стало простейшее приложение, создающее, тем не менее, сцену, пайплайн, шейдеры, и выводящее на экран результат трассировки лучей по этой сцене.&lt;/p&gt;

&lt;p&gt;И хоть наш треугольник и получился на порядок реалистичнее растеризованного, все же хотелось бы увидеть рейтрейсинг во всей его красе, ведь не зря мы потратили деньги на RTX видеокарты ;)
&lt;br&gt;
Именно этому и будет посвящена данная статья.&lt;/p&gt;

&lt;p&gt;&lt;i&gt;Хотелось бы сразу отметить - цель статьи показать как использовать Vulkan для построения полноценного рейтрейсера, показать основные архитектурные моменты, и дать несколько советов по написанию эффективных шейдеров для рейтрейсинга.&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;==Часть 1. Кролики и чайники==&lt;/p&gt;

&lt;p&gt;Первым делом, нам понадобится сама сцена. Я взял на себя смелость подготовить переосмысленную версию классической иконы рейтрейсинга &amp;mdash; сцены Тернера Уиттеда &lt;i&gt;“Зеркальный и стеклянный шары над куском линолеума”&lt;/i&gt;. В нашем случае сцена называется &amp;mdash; &lt;i&gt;“Хромированный чайник и стеклянный кролик на мраморном полу”&lt;/i&gt;.&lt;/p&gt;

&lt;p&gt;Модель кролика (Stanford Bunny) была позаимствована отсюда (&lt;a href=&quot;https://casual-effects.com/g3d/data10/index.html#mesh3&quot; rel=&quot;nofollow&quot;&gt;https://casual-effects.com/g3d/data10/index.html#mesh3&lt;/a&gt;). Модель чайника используется та, что встроена в 3DS Max. Модель пола была мастерски изготовлена автором статьи собственноручно. Текстуры взяты с сайта &lt;a href=&quot;https://texturehaven.com&quot; rel=&quot;nofollow&quot;&gt;https://texturehaven.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Для загрузки сцены используется библиотека &lt;i&gt;tinyobjloader&lt;/i&gt;. Для загрузки текстур используется всеми любимая &lt;i&gt;stb_image&lt;/i&gt;.&lt;/p&gt;

&lt;p&gt;Также, для более удобной работы со сценой и ее ресурсами были добавлены вспомогательные структуры &lt;i&gt;RTAccelerationStructure&lt;/i&gt;, &lt;i&gt;RTMesh&lt;/i&gt;, &lt;i&gt;RTMaterial&lt;/i&gt;, &lt;i&gt;RTScene&lt;/i&gt;.&lt;/p&gt;

&lt;p&gt;Код, по большей части, остается без изменений, только вместо одной Bottom Level Acceleration Structure (BLAS), теперь у нас их несколько, а соответственно и несколько &lt;i&gt;VkGeometryNV&lt;/i&gt; и &lt;i&gt;VkGeometryInstance&lt;/i&gt;. Важно также отметить, что полю &lt;i&gt;instanceId&lt;/i&gt; каждого инстанса мы теперь присваиваем порядковый номер объекта в сцене. Это поможет нам в будущем обращаться к его атрибутам. Создав для каждого объекта сцены свой BLAS и instance, мы строим Top Level Acceleration Sctructure (TLAS). Наша сцена готова.&lt;/p&gt;

&lt;p&gt;==Часть 2. Камера, мотор!==&lt;/p&gt;

&lt;p&gt;Для нашего треугольника нам хватало ортографической проекции, где все лучи шли параллельно. Чтобы видеть нашу новую сцену во всей ее красе, нам понадобится перспективная камера. Мы не будем заниматься моделированием реальной линзы, а остановимся на простой камере-обскуре (pinhole camera). В такой модели камеры наши лучи выходят из одной точки (позиции наблюдателя) и расходятся, образуя пирамиду.&lt;/p&gt;

&lt;p&gt;Формирование лучей для каждого пиксела нашего “экрана” в таком случае является очень простым: отправная точка, это всегда позиция наблюдателя, а конечная точка &amp;mdash; проекция на дальнюю плоскость отсечения (основание пирамиды).&lt;/p&gt;

&lt;p&gt;Я добавил в состав фреймворка класс Camera, реализующий все необходимые нам методы, и позволяющий управлять камерой с помощью пользовательского ввода.&lt;/p&gt;

&lt;p&gt;Для передачи параметров камеры в шейдер используется Uniform Buffer Object (UBO) следующего содержания:&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;struct&lt;/span&gt; UniformParams {
    vec4 camPos;
    vec4 camDir;
    vec4 camUp;
    vec4 camSide;
    vec4 camNearFarFov;
};&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;В шейдере же, для построения луча была добавлена функция &lt;i&gt;CalcRayDir&lt;/i&gt;, которая для заданного пиксела экрана находит необходимое направление луча, используя ориентацию камеры и угол обзора.&lt;/p&gt;

&lt;p&gt;Теперь же, если запустить наше приложение, то мы увидим следующую картину.&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://habrastorage.org/webt/iq/uy/3m/iquy3mhznkyq8qlmh9s-1_p2fji.png&quot; alt=&quot;Изображение&quot; /&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;p&gt;Теперь вместо одного цветного треугольника, у нас на экране несколько сотен тысяч цветных треугольников. Хвала рейтрейсингу!&lt;/p&gt;

&lt;p&gt;==Часть 2. Сталь и мрамор==&lt;/p&gt;

&lt;p&gt;Разноцветные треугольники это, конечно же, хорошо, но как насчет текстур? К счастью, текстурирование треугольников мало чем отличается от такового в растеризации &amp;mdash; нам все так же нужны текстурные координаты каждой вершины треугольника, их интерполированное значение для интересующего нас пиксела, а также сама текстура и сэмплер (sampler).&lt;/p&gt;

&lt;p&gt;Для интерполяции текстурных координат (а также любых вершинных атрибутов) нам и пригодятся те самые барицентрические координаты, которые мы и выводили до сих пор. Но где же нам взять вершинные атрибуты? Для растеризации мы складываем вершинные атрибуты в вершинный буфер, и далее конвейер все делает за нас, но в случае рейтрейсинга нужно заниматься этим самим. Хорошая новость в том, что это совсем не сложно!&lt;/p&gt;

&lt;p&gt;Для передачи атрибутов в шейдер нам пригодятся Shader Storage Buffer Object (SSBO,&amp;nbsp; или StructuredBuffer в терминах DirectX). Складываем вершинные атрибуты в SSBO и передаем в шейдер, вроде ничего сложного, но как узнать какие именно вершины нам нужны? Для начала нам нужно узнать в какой именно треугольник мы попали, и поможет нам в этом &lt;i&gt;&lt;b&gt;gl_PrimitiveID&lt;/b&gt;&lt;/i&gt;, который содержит порядковый номер треугольника в данном объекте. Уже лучше, но чаще всего наша геометрия индексирована, чтобы избежать дублирования данных, а значит нам понадобятся также индексы, которые мы передадим через SSBO.&lt;/p&gt;

&lt;p&gt;Итак, у нас есть номер треугольника, по которому мы можем прочитать индексы, с помощью которых мы читаем вершинные атрибуты, проинтерполировав которые, мы, наконец, можем обращаться к текстуре. Но как нам узнать к какой именно? Для этого нам пригодится еще один SSBO буфер, хранящий в себе индекс текстуры для каждого треугольника объекта. Для чтения из него нам пригодится знакомый уже нам &lt;i&gt;gl_PrimitiveID&lt;/i&gt;.&lt;/p&gt;

&lt;p&gt;Итак, давайте запишем, какие в итоге буферы нам понадобятся:&lt;ul&gt;&lt;li&gt;&lt;i&gt;MaterialsIDs&lt;/i&gt; (SSBO, индекс материала для каждого треугольника)&lt;/li&gt;&lt;li&gt;&lt;i&gt;FacesBuffer&lt;/i&gt; (SSBO, индексы вершин)&lt;/li&gt;&lt;li&gt;&lt;i&gt;AttribsBuffer&lt;/i&gt; (SSBO, вершинные атрибуты)&lt;/li&gt;&lt;li&gt;&lt;i&gt;Texture&lt;/i&gt; (sampler2D, текстура)&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;Но ведь у нас несколько объектов в сцене, как быть с этим? К счастью для этого существует расширение &lt;i&gt;&lt;b&gt;VK_EXT_descriptor_indexing&lt;/b&gt;&lt;/i&gt;, добавляющее много вкусностей и послаблений для дескрипторов, но самое главное для нас &amp;mdash; возможность при создании разметки набора дескрипторов (descriptor set layout) указать, что этих самых дескрипторов неопределенное количество. Таким образом мы можем в рантайме решать размерности массивов передаваемых ресурсов, что просто идеально для нашей ситуации!&lt;/p&gt;

&lt;p&gt;Разбирать подробно я это расширение не буду, это выходит за рамки данной статьи, так что для дальнейшего ознакомления можете пройти по этой ссылочке:
&lt;br&gt;
&lt;a href=&quot;https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html/vkspec.html#VK_EXT_descriptor_indexing&quot; rel=&quot;nofollow&quot;&gt;https://www.khronos.org/registry/vulkan/specs/1.1-extensions/html&amp;hellip; ptor_indexing&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Помните, как мы при создании инстансов в качестве id указывали порядковый номер объекта? Так вот, он то нам теперь и пригодится, чтобы брать из массивов нужные буферы! Для этого нам понадобится встроенная переменная &lt;i&gt;&lt;b&gt;gl_InstanceCustomIndexNV&lt;/b&gt;&lt;/i&gt;, которая как раз и содержит то самое значение. Вот так будет выглядеть наш код текстурирования:&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;overflow full&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; vec3 barycentrics = vec3&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1.0&lt;/span&gt;f - HitAttribs.x - HitAttribs.y, HitAttribs.x, HitAttribs.y&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; uint matID = MatIDsArray&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;nonuniformEXT&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;gl_InstanceCustomIndexNV&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;.MatIDs&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;gl_PrimitiveID&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; uvec4 face = FacesArray&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;nonuniformEXT&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;gl_InstanceCustomIndexNV&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;.Faces&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;gl_PrimitiveID&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
VertexAttribute v0 = AttribsArray&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;nonuniformEXT&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;gl_InstanceCustomIndexNV&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;.VertexAttribs&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;face.x&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
VertexAttribute v1 = AttribsArray&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;nonuniformEXT&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;gl_InstanceCustomIndexNV&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;.VertexAttribs&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;face.y&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;
VertexAttribute v2 = AttribsArray&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;nonuniformEXT&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;gl_InstanceCustomIndexNV&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;.VertexAttribs&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;int&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;face.z&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;;

&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; vec2 uv = BaryLerp&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;v0.uv.xy, v1.uv.xy, v2.uv.xy, barycentrics&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; vec3 texel = textureLod&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;TexturesArray&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;nonuniformEXT&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;matID&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;, uv, &lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;.rgb;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;bound&quot;&gt;&lt;p&gt;Как видите, все оказывается совсем не сложно, и очень даже гибко. Запустим наше приложение и увидим следующую картинку:&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://habrastorage.org/webt/sv/yu/si/svyusiy1zodv4vtl_apwkdzpaqa.png&quot; alt=&quot;Изображение&quot; /&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;p&gt;Уже намного лучше, но чего-то не хватает… Ах да, освещение!&lt;/p&gt;

&lt;p&gt;==Часть 3. Да будет свет!==&lt;/p&gt;

&lt;p&gt;Давайте добавим простейшее освещение, классику компьютерной графики &amp;mdash; диффузная модель освещения Ламберта. Согласно этой модели, освещение рассеивается равномерно по полусфере, а освещенность диктуется только плотностью светового потока, которая обратно пропорциональна углу потока света к поверхности.&lt;/p&gt;

&lt;p&gt;Или, проще говоря, всеми любимый мистер &lt;i&gt;N dot L&lt;/i&gt;.&lt;/p&gt;

&lt;p&gt;И тут начинают проявляться достоинства рейтрейсинга перед растеризацией &amp;mdash; тени! Точка находится в тени, если она не “видит” напрямую источник света, и это очень легко сделать с помощью рейтрейсинга &amp;mdash; достаточно лишь пустить луч в направлении источника света и посмотреть, не попадется ли нам чего по пути. Если нашли пересечение, значит источник света закрыт и мы “в тени”. Если же пересечения не было &amp;mdash; значит мы можем считать освещение.&lt;/p&gt;

&lt;p&gt;Для этого, при нахождении первичного пересечения, нам нужно построить новый луч, и вызвать &lt;i&gt;traceNV&lt;/i&gt; еще раз, для проверки “видимости” источника света. Делать это можно и в Hit шейдере, но рекомендуется все вызовы &lt;i&gt;traceNV&lt;/i&gt; производить в Raygen шейдере, так как это позволяет планировщику (scheduler) работать с максимальной эффективностью.&lt;/p&gt;

&lt;p&gt;Еще одна оптимизация &amp;mdash; использовать RayPayload как можно меньшего размера, а также специализированные Hit и Miss шейдеры. Для “теневых” лучей нам в качестве RayPayload понадобится всего одно значение: было ли пересечение, или нет. Соответственно в Hit шейдере мы будем отмечать, что пересечение было, и в Miss шейдере, что не было.&lt;/p&gt;

&lt;p&gt;Давайте дополним наш код Raygen шейдера:&lt;/p&gt;
&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; vec3 hitColor = PrimaryRay.colorAndDist.rgb;
&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; hitDistance = PrimaryRay.colorAndDist.w;
&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; vec3 hitNormal = PrimaryRay.normal.xyz;
&lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; lighting = &lt;span class=&quot;digit&quot;&gt;1.0&lt;/span&gt;f;
&lt;span class=&quot;comment&quot;&gt;// if we hit something&lt;/span&gt;
&lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;hitDistance &lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; {
    &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; vec3 hitPos = origin + direction * hitDistance;
    &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; vec3 toLight = normalize&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;Params.sunPosAndAmbient.xyz&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
     &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; vec3 shadowRayOrigin = hitPos + hitNormal * &lt;span class=&quot;digit&quot;&gt;0.01&lt;/span&gt;f;
     &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; uint shadowRayFlags = gl_RayFlagsOpaqueNV | gl_RayFlagsTerminateOnFirstHitNV;
    &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; uint shadowRecordOffset = &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;;
    &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; uint shadowMissIndex = &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;;
     traceNV&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;Scene,
             rayFlags,
             cullMask,
             shadowRecordOffset,
             stbRecordStride,
             shadowMissIndex,
             shadowRayOrigin,
             &lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f,
             toLight,
             tmax,
             SWS_LOC_SHADOW_RAY&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
     &lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;ShadowRay.distance &lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; {
        lighting = Params.sunPosAndAmbient.w;
    } &lt;span class=&quot;key&quot;&gt;else&lt;/span&gt; {
        lighting = max&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;Params.sunPosAndAmbient.w, dot&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;hitNormal, toLight&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
    }
}&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;i&gt;Заметьте, что для “теневых” лучей мы указываем флаг gl_RayFlagsTerminateOnFirstHitNV. Таким образом, мы остановим трассировку при первом же пересечении, без поиска ближайшего. Ведь нам важен сам факт наличия пересечения.&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;Таким образом, мы проверяем, было ли первичное пересечение, или мы ударились в “небо”. Если пересечение было, то восстанавливаем координаты точки пересечения (ведь мы знаем расстояние до точки пересечения от начальной точки луча), получаем направление на источник света, и вызываем &lt;i&gt;traceNV&lt;/i&gt;, указывая в качестве шейдеров наши специализированные “теневые” шейдеры, а также расположение PayLoad для “теневых” лучей.&lt;/p&gt;

&lt;p&gt;&lt;i&gt;Заметьте, что для задания отправной точки нашего луча мы немного смещаем ее вдоль нормали. Это сделано для избежания нежелательного “самозатенения”. Также для этого можно использовать значение tmin отличное от нуля.&lt;/i&gt;&lt;/p&gt;

&lt;p&gt;После этого мы проверяем было ли пересечение, и если не было, то считаем освещение по модели Ламберта. Если же пересечение было, то в качестве освещения возьмем константное значение окружающего света (ambient light).&lt;/p&gt;

&lt;p&gt;Не смотря на простоту этой модели, благодаря наличию теней, мы получим довольно симпатичную картинку.&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://habrastorage.org/webt/lw/jt/s4/lwjts4_vjq9tcqr5qirpnq6hv1y.png&quot; alt=&quot;Изображение&quot; /&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;p&gt;==Часть 4. Зеркало==&lt;/p&gt;

&lt;p&gt;Как вы уже могли заметить, рейтрейсинг это простой, но, в то же время, очень мощный инструмент. С помощью простого механизма запуска луча и проверки результатов пересечения можно реализовать множество фичей, реализовать которые с помощью растеризации или сложно, или и вовсе невозможно.&lt;/p&gt;

&lt;p&gt;Давайте рассмотрим, например, отражения. Современные растерные рендеры научились многим трюкам для построения приемлемых отражений, но все они далеки от реалистичности и делают довольно много допущений.&lt;/p&gt;

&lt;p&gt;С помощью рейтрейсинга, мы можем получить отражения очень легко, достаточно просто отразить пришедший луч и отследить его пересечение. Давайте попробуем.&lt;/p&gt;

&lt;p&gt;Для начала определимся, что отражающими свойствами у нас будет обладать чайник, ведь он легко может быть хромированным. Для этого мы в Hit шейдере будем записывать в RayPayload флаг, означающий попали ли мы в чайник или нет (так как я создавал сцену, я знаю что порядковый номер чайника = 2).&lt;/p&gt;

&lt;p&gt;Теперь, все что нам нужно сделать в Raygen шейдере, это проверить попали ли мы в чайник, и вместо “теневого” луча, выпустить еще один первичный луч, отразив направление текущего луча, и использовать цвет в точке пересечения в качестве отражения.&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; isTeapot = PrimaryRay.normal.w;
&lt;span class=&quot;comment&quot;&gt;// if teapot - let&apos;s reflect!&lt;/span&gt;
&lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;isTeapot &lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; {
    &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; vec3 hitPos = origin + direction * hitDistance + hitNormal * &lt;span class=&quot;digit&quot;&gt;0.01&lt;/span&gt;f;
    &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; vec3 reflectedDir = reflect&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;direction, hitNormal&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
     traceNV&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;Scene,
             rayFlags,
             cullMask,
             primaryRecordOffset,
             stbRecordStride,
             primaryMissIndex,
             hitPos,
             &lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f,
             reflectedDir,
             tmax,
             SWS_LOC_PRIMARY_RAY&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
}&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Мы используем тот же RayPayload и те же шейдеры, что и для первичных лучей, ведь мы, по сути, просто продолжаем трассировку первичного луча.
&lt;br&gt;
Теперь мы можем насладиться видом хромированного чайника.&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://habrastorage.org/webt/sz/qv/cw/szqvcwad20_ytkg8iarobafzdrm.png&quot; alt=&quot;Изображение&quot; /&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;p&gt;Как вы, наверное, могли заметить, отражение на нашем чайнике какое-то странное. На отражении пола нет тени, а также боковая и верхняя ручки чайника отражаются текстурированными. Это происходит потому, что после нахождения пересечения отраженного луча, мы просто забираем получившийся цвет и на этом останавливаемся.&lt;/p&gt;

&lt;p&gt;Во-первых, нам как минимум нужно рассчитать освещение в точке пересечения. А во-вторых, если мы снова попали в зеркальную поверхность, то нужно отразить луч и продолжить трассировку. Попробуйте поставить два зеркала напротив друг-друга, и вы увидите “бесконечное” отражение. Мы себе, конечно же, бесконечное отражение позволить не можем, но, как минимум несколько уровней отражений вполне можем осилить.&lt;/p&gt;

&lt;p&gt;==Часть 5. Зацикливаемся==&lt;/p&gt;

&lt;p&gt;Все мы знаем, что рекурсия это плохо. Но рекурсия на GPU еще хуже. И, хоть Vulkan Raytracing и предоставляет нам возможности для организации рекурсивной трассировки, производительности ради стоит ее, по возможности, избегать.&lt;/p&gt;

&lt;p&gt;В нашем случае, вполне можно обойтись и обычным циклом. На каждой итерации мы трассируем луч, проверяем куда попали, и решаем:&lt;ul&gt;&lt;li&gt;Если попали в “небо” &amp;mdash; прерываем цикл&lt;/li&gt;&lt;li&gt;Если попали в чайник &amp;mdash; строим отраженный луч и продолжаем&lt;/li&gt;&lt;li&gt;Если попали оставшуюся часть сцены &amp;mdash; считаем освещение и прерываем цикл&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;Императивным путем я подобрал максимальное количество итераций равное 10. Это позволяет иметь достаточное количество переотражений, чтобы создавать правдоподобную картинку, при этом сохраняя хорошую производительность.&lt;/p&gt;

&lt;p&gt;Изменим наш шейдер в соответствии с изложенным алгоритмом:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;overflow full&quot;&gt;&lt;pre&gt;vec3 finalColor = vec3&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
&lt;span class=&quot;key&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;key&quot;&gt;int&lt;/span&gt; i = &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;; i &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; SWS_MAX_RECURSION; ++i&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; {
    traceNV&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;Scene,
             rayFlags,
             cullMask,
             primaryRecordOffset,
             stbRecordStride,
             primaryMissIndex,
             origin,
             tmin,
             direction,
             tmax,
             SWS_LOC_PRIMARY_RAY&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;

    &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; vec3 hitColor = PrimaryRay.colorAndDist.rgb;
    &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; hitDistance = PrimaryRay.colorAndDist.w;

    &lt;span class=&quot;comment&quot;&gt;// if hit background - quit&lt;/span&gt;
    &lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;hitDistance &lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; {
        finalColor += hitColor;
        &lt;span class=&quot;key&quot;&gt;break&lt;/span&gt;;
    } &lt;span class=&quot;key&quot;&gt;else&lt;/span&gt; {
        &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; vec3 hitNormal = PrimaryRay.normal.xyz;
        &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; isTeapot = PrimaryRay.normal.w;

        &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; vec3 hitPos = origin + direction * hitDistance;

        &lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;isTeapot &lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; {
            &lt;span class=&quot;comment&quot;&gt;// our teapot is mirror, so continue&lt;/span&gt;

            origin = hitPos + hitNormal * &lt;span class=&quot;digit&quot;&gt;0.01&lt;/span&gt;f;
            direction = reflect&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;direction, hitNormal&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
        } &lt;span class=&quot;key&quot;&gt;else&lt;/span&gt; {
            &lt;span class=&quot;comment&quot;&gt;// we hit diffuse primitive - simple lambertian&lt;/span&gt;

            &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; vec3 toLight = normalize&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;Params.sunPosAndAmbient.xyz&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
            &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; vec3 shadowRayOrigin = hitPos + hitNormal * &lt;span class=&quot;digit&quot;&gt;0.01&lt;/span&gt;f;

            traceNV&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;Scene,
                     rayFlags,
                     cullMask,
                     shadowRecordOffset,
                     stbRecordStride,
                     shadowMissIndex,
                     shadowRayOrigin,
                     &lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f,
                     toLight,
                     tmax,
                     SWS_LOC_SHADOW_RAY&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;

            &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; lighting = 
                &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;ShadowRay.distance &lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; ? Params.sunPosAndAmbient.w : max&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;Params.sunPosAndAmbient.w, dot&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;hitNormal, toLight&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;

            finalColor += hitColor * lighting;

            &lt;span class=&quot;key&quot;&gt;break&lt;/span&gt;;
        }
    }
}&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;bound&quot;&gt;&lt;p&gt;Результат &amp;mdash; правдоподобные отражения:&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://habrastorage.org/webt/0z/eo/5x/0zeo5xsg9liiesrnrwq7y02hovi.png&quot; alt=&quot;Изображение&quot; /&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;p&gt;==Часть 6. Стекло и финал==&lt;/p&gt;

&lt;p&gt;Наш рейтрейсер начинает обретать черты взрослого трассировщика. Теперь, имея на руках универсальный цикл трассировки, мы можем расширять функционал, добавляя новые материалы и более реалистичные модели освещения. Довольно небольшими изменениями можно получить полноценный трассировщик путей (path tracer) для расчета реалистичных изображений.&lt;/p&gt;

&lt;p&gt;Давайте, напоследок, добавим еще одну фичу рейтрейсинга &amp;mdash; преломления. Вещь, практически нереализуемая стандартной растеризацией. Но, благодаря нашему циклу трассировки, мы можем легко получить реалистичные многоуровневые преломления. Давайте сделаем нашего кролика стеклянным!&lt;/p&gt;

&lt;p&gt;Для этого введем константы порядковых номеров объектов нашей сцены, и в шейдере пересечения будем записывать в RayPayload номер объекта с которым пересеклись.&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; objId = &lt;span class=&quot;key&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;gl_InstanceCustomIndexNV&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
PrimaryRay.normalAndObjId = vec4&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;normal, objId&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Подберем индекс преломления для нашего кролика. Я выбрал обычное стекло, с индексом преломления равным 1.52
&lt;br&gt;
Так как функция refract принимает соотношение индексов преломления двух сред, а в нашем случае это воздух / стекло, потому финальное значение равно 1.0 / 1.52&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; kBunnyRefractionIndex = &lt;span class=&quot;digit&quot;&gt;1.0&lt;/span&gt;f / &lt;span class=&quot;digit&quot;&gt;1.52&lt;/span&gt;f;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Теперь добавим в наш цикл проверку на попадание в кролика:&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;objectId == OBJECT_ID_TEAPOT&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; {
    &lt;span class=&quot;comment&quot;&gt;// our teapot is mirror, so reflect and continue&lt;/span&gt;

    origin = hitPos + hitNormal * &lt;span class=&quot;digit&quot;&gt;0.001&lt;/span&gt;f;
    direction = reflect&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;direction, hitNormal&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
} &lt;span class=&quot;key&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;objectId == OBJECT_ID_BUNNY&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; {
    &lt;span class=&quot;comment&quot;&gt;// our bunny is glass, so refract and continue&lt;/span&gt;

    &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; NdotD = dot&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;hitNormal, direction&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
    &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; vec3 refrNormal = &lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;NdotD &lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; ? -hitNormal : hitNormal;

    origin = hitPos + direction * &lt;span class=&quot;digit&quot;&gt;0.001&lt;/span&gt;f;
    direction = refract&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;direction, refrNormal, kBunnyRefractionIndex&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
}&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Так как преломленный луч заходит “внутрь” объекта, нам нужно отслеживать это и “переворачивать” нормаль, чтобы получать правильный результат преломления.&lt;/p&gt;

&lt;p&gt;Давайте полюбуемся нашим стеклянным кроликом.&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://habrastorage.org/webt/mp/bu/az/mpbuaz93jwozluhg-xanjpenzgk.png&quot; alt=&quot;Изображение&quot; /&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;p&gt;Мы с вами прошли путь от разноцветного треугольника, до сцены с несколькими объектами, текстурированными, и реализующими отражающие и преломляющие поверхности. На этой основе уже можно строить более или менее серьезный рейтрейсер/пастрейсер.&lt;/p&gt;

&lt;p&gt;В следующей статье я постараюсь рассмотреть гибридный рендеринг, совмещающий растеризацию и рейтрейсинг.&lt;/p&gt;

&lt;p&gt;Исходный код к статье находится здесь:
&lt;br&gt;
&lt;a href=&quot;https://github.com/iOrange/rtxON/tree/Version_2_2&quot; rel=&quot;nofollow&quot;&gt;https://github.com/iOrange/rtxON/tree/Version_2_2&lt;/a&gt;&lt;/p&gt;</description>
</item>
<item>
  <guid isPermaLink="true">https://gamedev.ru/code/articles/vulkan_raytracing</guid>
  <pubDate>Sun, 04 Nov 2018 15:10:58 GMT</pubDate>
  <title>Введение в Vulkan Raytracing</title>
  <link>https://gamedev.ru/code/articles/vulkan_raytracing</link>
  <comments>https://gamedev.ru/code/forum/?id=239989</comments>
  <category>Графика</category>
  <category>графика</category>
  <category>raytracing</category>
  <category>rtx</category>
  <category>Vulkan</category>
  <category>рейтрейсинг</category>
  <description>
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;В какое время мы живем! Казалось, еще недавно мы часами ждали пока CPU просчитает знаменитое изображение за авторством Тернера Уиттеда, а сегодня нам доступны потребительские модели видеокарт с аппаратной поддержкой рейтрейсинга! Любой программист трехмерной графики скажет, что он мечтал об этом дне, когда мы, наконец, можем трассироваться в мировом пространстве в реальном времени.&lt;/p&gt;
&lt;p&gt;==Часть 0. Подводка==&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://gamedev.ru/files/images/whitted_hi-res_01.jpg&quot; alt=&quot;Whitted | Введение в Vulkan Raytracing&quot; title=&quot;Whitted&quot;&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;p&gt;Что же, давайте разберемся что для этого нам нужно и как начать использовать вверенную нам технологию.&lt;/p&gt;

&lt;p&gt;На момент написания статьи специализированное аппаратное решение для рейтрейсинга на GPU есть только у компании Nvidia &amp;mdash; имя ей RTX. Компания AMD делает ставку на свой открытый фреймворк Radeon Rays, который, по сути, является набором готовых вычислительных шейдеров (compute shaders).&lt;/p&gt;

&lt;p&gt;В данной статье мы далее будем рассматривать решение от компании Nvidia. Поддерживается аппаратный рейтрейсинг видеокартами на базе архитектур Volta и Turing. Со стороны графических АПИ аппаратный рейтрейсинг поддерживается DirectX 12 (DirectX Raytracing, DXR) и Vulkan (посредством расширения VK_NVX_raytracing). Важно отметить, что оба этих АПИ мало чем отличаются, т.к. оба были спроектированы компанией Nvidia. Это значит, что все рассмотренное в статье будет справедливо для обоих АПИ.&lt;/p&gt;

&lt;p&gt;Автор статьи остановил свой выбор на Vulkan, и тому 2 причины:&lt;ol&gt;&lt;li&gt;Банальная: автор знаком с Vulkan и не особо знаком с DirectX 12&lt;/li&gt;&lt;li&gt;Техническая: DXR (на момент написания статьи) требует определенных сборок WIndows 10, что вносит некий дискомфорт.&lt;/li&gt;&lt;/ol&gt;&lt;/p&gt;

&lt;p&gt;==Часть 1. Вводная==&lt;/p&gt;

&lt;p&gt;Для работы с рейтрейсингом были добавлены следующие элементы и понятия:&lt;ul&gt;&lt;li&gt;Acceleration structures &amp;mdash; специальный объект, инкапсулирующий в себя внутреннее представление геометрии для трассировки. Можете представить себе это как некое дерево описывающих объемов (BVH) для ускорения поиска пересечения луча и геометрии.&lt;/li&gt;&lt;li&gt;Таблица привязки шейдеров (Shader Binding Table, SBT) &amp;mdash; структура данных, позволяющая вам передать АПИ несколько шейдеров для трассировки (и/или отдельных ее этапов), и затем динамически в самых шейдерах вызывать шейдеры из этой таблицы.&lt;/li&gt;&lt;li&gt;Новая команда запуска трассировки (vkCmdTraceRaysNVX), очень похожая на команду запуска вычислительных шейдеров (vkCmdDispatch).&lt;/li&gt;&lt;li&gt;Новый конвейер (pipeline) для трассировки, умеющий работать с таблицей шейдеров.&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;Давайте коротко рассмотрим по отдельности все эти нововведения.&lt;/p&gt;

&lt;p&gt;===Acceleration structures.===&lt;/p&gt;

&lt;p&gt;Итак, первым делом посмотрим, что же такое acceleration structure (AS) и как с ними работать.&lt;/p&gt;

&lt;p&gt;Чтобы как можно быстрее находить пересечения с геометрией нам нужна некая структура данных, имеющая пространственную информацию о геометрии, и позволяющая быстро отбросить те части сцены, которые заведомо не пересекутся с лучом.&lt;/p&gt;

&lt;p&gt;Как простейший пример можно представить себе иерархию из ограничивающих объемов (BVH) представленных параллелепипедами. Таким образом тестируя луч на пересечение с параллелепипедами, мы можем сократить тест пересечения с геометрией до небольшого набора примитивов, не перебирая всю сцену.&lt;/p&gt;

&lt;p&gt;Оба наши АПИ предоставляют 2 уровня AS: &lt;i&gt;BLAS&lt;/i&gt; и &lt;i&gt;TLAS&lt;/i&gt;.
&lt;br&gt;
&lt;i&gt;BLAS&lt;/i&gt; &amp;mdash; это Bottom Level Acceleration Structure (ускоряющая структура нижнего уровня), которая и содержит, собственно говоря, геометрию.
&lt;br&gt;
&lt;i&gt;TLAS&lt;/i&gt; &amp;mdash; Top Level Acceleration Structure (ускоряющая структура верхнего уровня) - это уже структура содержащая в себе одну или несколько структур нижнего уровня, а также информацию об их трансформации.&lt;/p&gt;

&lt;p&gt;Размещаются BLAS внутри TLAS посредством инстанцирования (instancing), благодаря чему можно дублировать BLAS, указывая инстансу ссылку на BLAS и отдельную трансформацию.&lt;/p&gt;

&lt;p&gt;===Shader Binding Table.===&lt;/p&gt;

&lt;p&gt;Что же такое Shader Binding Table (SBT) и с чем его едят?&lt;/p&gt;

&lt;p&gt;Для начала давайте рассмотрим какие новые шейдерные стадии (shader stages) у нас добавились:&lt;ul&gt;&lt;li&gt;&lt;i&gt;Raygen&lt;/i&gt; &amp;mdash; шейдер отвечающий за генерацию лучей и вызов трассировки (функция traceNVX в GLSL). Обязателен.&lt;/li&gt;&lt;li&gt;&lt;i&gt;Intersection&lt;/i&gt; &amp;mdash; шейдер реализующий проверку пересечения луча и геометрии. Позволяет реализовать рейтрейсинг пользовательских примитивов (например, геометрии заданной сплайнами). Необязателен, по умолчанию будет использована реализация пересечения луча с треугольником.&lt;/li&gt;&lt;li&gt;&lt;i&gt;Any hit&lt;/i&gt; и &lt;i&gt;Closest hit&lt;/i&gt; &amp;mdash; шейдеры вызывающиеся при позитивном результате проверки на пересечение с примитивом. Так как примитивы не всегда будут отсортированы вдоль луча, может быть зарегистрировано несколько пересечений. Any hit шейдер будет вызван для всех из них, в то время как Closest hit шейдер будет вызван в самом конце, когда после нахождения всех возможных пересечений будет выбрано ближайшее. Any hit не обязателен, Closest hit обязателен.&lt;/li&gt;&lt;li&gt;&lt;i&gt;Miss &lt;/i&gt; &amp;mdash; шейдер выполняющийся если пересечение не было найдено (в пределах [tmin; tmax]). Можно использовать для возврата цвета неба, например. Необязателен.&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;Как видим, было добавлено целых 5 дополнительных шейдерных стадий, с помощью которых можно реализовать практически все существующие методы трассировки.
&lt;br&gt;
Эти шейдеры разбиваются на 3 функциональные подмножества:&lt;ul&gt;&lt;li&gt;&lt;i&gt;Raygen&lt;/i&gt; &amp;mdash; сюда входит одноименный шейдер. Этот шейдер может быть только один на каждый старт трассировки (вызов &lt;i&gt;vkCmdTraceRaysNVX&lt;/i&gt;)&lt;/li&gt;&lt;li&gt;&lt;i&gt;Intersection&lt;/i&gt; &amp;mdash; сюда входят шейдеры Intersection, Any hit и Closest hit.&lt;/li&gt;&lt;li&gt;&lt;i&gt;Miss&lt;/i&gt; &amp;mdash; в этом подмножестве только один шейдер Miss.&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;Теперь можем разобраться для чего нам SBT. Давайте представим себе простейший рейтрейсер, который считает только прямое освещение (direct lighting).
&lt;br&gt;
В простейшем случае он может выглядеть так:&lt;/p&gt;
&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;for each pixel on screen {
  generate ray
  trace ray
  if wasHit(ray) {
    lighting = 0
    for each light {
      generate secondaryRay
      trace secondaryRay
      If not wasHit(secondaryRay) {
        lighting += calcLighting(light)
      }
    }
    ray.color *= lighting
  }
  resultImage[pixel] = ray.color
}&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Здесь мы видим применение Raygen шейдера для генерации лучей, Closest hit и Miss шейдеров для получения цвета в точке пересечения (или неба в случае промаха). Closest hit шейдер может содержать относительно сложный код для чтения необходимой информации и расчета цвета в точке пересечения. Miss шейдер, к примеру, может рассчитывать цвет из карты окружения.&lt;/p&gt;

&lt;p&gt;Это шейдеры для, так называемых, основных лучей (primary rays). В случае нахождения пересечения с геометрией, мы генерируем вторичные лучи (secondary rays) для проверки видимости источников света в точке пересечения (если источник света не виден &amp;mdash; значит мы в тени). Для лучшей производительности мы можем использовать более простые шейдеры Closest hit и Miss, которые могут просто выставлять флаг было ли пересечение или нет.&lt;/p&gt;

&lt;p&gt;Именно для этого функция traceNVX в шейдере принимает в качестве параметра индекс группы внутри подмножества. Сами же шейдеры, собранные в группы, лежат в таблице SBT.
&lt;br&gt;
Запутанно? Поначалу может быть, сейчас попробую объяснить приведя SBT для нашего примера сверху.&lt;/p&gt;

&lt;p&gt;Итак у нас 5 шейдеров: 1 Raygen, 2 Closest hit и еще 2 Miss. Как мы уже знаем, они принадлежат к разным подмножествам. Давайте соберем группы для наших шейдеров и сформируем SBT.&lt;/p&gt;

&lt;p&gt;Первым у нас идет Raygen шейдер, он будет в первой группе #0. Далее идут 2 Closest hit шейдера, они займут группы #1 и #2 соответственно. Оставшиеся 2 Miss шейдера займут группы #3 и #4.&lt;/p&gt;

&lt;p&gt;Чтобы было еще нагляднее, давайте мы добавим Any hit шейдер для вторичных лучей (ведь нам не обязательно находить ближайшее пересечение, важен сам факт блокировки источника света). Этот шейдер войдет в группу #2 ибо он принадлежит к подмножеству Intersection шейдеров, как и Closest hit.&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://gamedev.ru/files/images/sbt.gif&quot; alt=&quot;SBT | Введение в Vulkan Raytracing&quot; title=&quot;SBT&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;p&gt;Как видно из схематического изображения &amp;mdash; для того чтобы функция &lt;i&gt;traceNVX&lt;/i&gt; вызвала шейдеры для первичных лучей, нам надо указать индексы соответствующих групп внутри соответствующего подмножества. Шейдеры для обработки пересечений для первичных лучей находятся в группе #1, которая, в свою очередь, лежит первой в своем подмножестве, а значит ее индекс &amp;mdash; 0. То же справедливо и для группы #3, в которой находится шейдер промаха для первичных лучей.&lt;/p&gt;

&lt;p&gt;Для вторичных лучей нам, соответственно, нужны группы #2 и #4, индексы которых 1 и 1.&lt;/p&gt;

&lt;p&gt;Как вы уже могли видеть из примера, каждая Intersection группа может содержать от одного до 3 шейдеров (Intersection, Closest hit и Any hit).&lt;/p&gt;

&lt;p&gt;Данный механизм позволяет иметь набор специализированных шейдеров для разных этапов вашего рейтрейсера, и какие из них в каком случае использовать решаете вы сами, что очень удобно.&lt;/p&gt;

&lt;p&gt;===vkCmdTraceRaysNVX и Raytracing pipeline.===&lt;/p&gt;

&lt;p&gt;Мы с вами разобрались с самой сложной и запутанной частью, осталось лишь бегло рассмотреть оставшиеся 2 элемента.&lt;/p&gt;

&lt;p&gt;&lt;i&gt;vkCmdTraceRaysNVX&lt;/i&gt; очень похожа по своей сути на &lt;i&gt;vkCmdDispatch&lt;/i&gt;. Разница в том, что &lt;i&gt;vkCmdTraceRaysNVX&lt;/i&gt; запускает исполнение по двухмерной сетке и без разбиения на группы (во всяком случае, не явно). А также в качестве параметров принимает стартовые смещения для каждой группы шейдеров в буфере содержащим SBT.&lt;/p&gt;

&lt;p&gt;&lt;i&gt;Raytracing pipeline&lt;/i&gt; хранит в себе соотношения шейдеров и их групп. То есть, если SBT &amp;mdash; это буфер в котором лежат шейдеры, то Raytracing pipeline &amp;mdash; это описание какой из шейдеров в этом буфере к какой группе относится.&lt;/p&gt;

&lt;p&gt;==Часть 2. Код==&lt;/p&gt;

&lt;p&gt;Переходим непосредственно к коду. Пора применить полученные знания на практике. Сразу хочу оговориться &amp;mdash; данная статья посвящена только рассмотрению нового функционала рейтрейсинга, и рассчитана на людей знакомых с Vulkan, шейдерами и программированием графики в целом. Перед продолжением рекомендую пройти боевое крещение Vulkan’ом с помощью замечательных уроков&amp;nbsp; &lt;a href=&quot;https://vulkan-tutorial.com&quot; rel=&quot;nofollow&quot;&gt;https://vulkan-tutorial.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Начнем со своеобразного “Hello, World!” в мире графики &amp;mdash; разноцветного треугольника!&lt;/p&gt;

&lt;p&gt;Давайте рассмотрим основные этапы:&lt;ul&gt;&lt;li&gt;Создать сцену: сгенерировать геометрию треугольника, создать для него BLAS, создать инстанс с единичной матрицей трансформации, создать TLAS на основе нашего инстанса.&lt;/li&gt;&lt;li&gt;Загрузить шейдеры и создать Raytracing pipeline, указав какой шейдер в какой группе располагается.&lt;/li&gt;&lt;li&gt;Создать SBT и загрузить в него информацию о шейдерах из Raytracing pipeline.&lt;/li&gt;&lt;li&gt;Создать необходимый набор дескрипторов (descriptor set) для двух наших ресурсов &amp;mdash; наш TLAS по которому мы будем трассироваться, а также результирующее изображение (result image) в которое мы будем записывать посчитанный цвет для каждого пиксела.&lt;/li&gt;&lt;li&gt;После этого все что нам остается &amp;mdash; это заполнить командный буфер (command buffer), указав pipeline, descriptor set и вызвав &lt;i&gt;vkCmdTraceRaysNVX&lt;/i&gt;.&lt;/li&gt;&lt;/ul&gt;&lt;/p&gt;

&lt;p&gt;Что же, звучит просто. Давайте кодить!&lt;/p&gt;

&lt;p&gt;Начнем с создания сцены с треугольником. Для этого нам понадобятся вершины и индексы.&lt;/p&gt;
&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; vertices&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; = {
        &lt;span class=&quot;digit&quot;&gt;0.25&lt;/span&gt;f, &lt;span class=&quot;digit&quot;&gt;0.25&lt;/span&gt;f, &lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f,
        &lt;span class=&quot;digit&quot;&gt;0.75&lt;/span&gt;f, &lt;span class=&quot;digit&quot;&gt;0.25&lt;/span&gt;f, &lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f,
        &lt;span class=&quot;digit&quot;&gt;0.50&lt;/span&gt;f, &lt;span class=&quot;digit&quot;&gt;0.75&lt;/span&gt;f, &lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f
    };

&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; uint32_t indices&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt; = { &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;2&lt;/span&gt; };&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Загрузим их в соответствующие вершинный и индексный буферы. Это очень удобно, ибо при указании геометрии для построения AS вы можете использовать те же буферы, что и для отрисовки растеризацией.&lt;/p&gt;

&lt;p&gt;Дальше нам необходимо заполнить соответствующие поля новой структуры &lt;i&gt;VkGeometryNVX&lt;/i&gt;, указав вершинный и индексный буферы, количество и размер вершин, а также формат вершин и индексов. Все эти параметры совершенно такие же, как и для отрисовки растеризацией. Временно отложим нашу геометрию до момента построения AS.&lt;/p&gt;

&lt;p&gt;Для создания AS нам, как это принято в Vulkan, нужно выделить под нее память. Сначала создаем новый объект AS функцией &lt;i&gt;vkCreateAccelerationStructureNVX&lt;/i&gt;. Здесь мы можем указать какой тип AS мы хотим создать &amp;mdash; верхнего, или нижнего уровня. Чтобы узнать сколько и какой памяти нам нужно выделить, введена новая функция &lt;i&gt;vkGetAccelerationStructureMemoryRequirementsNVX&lt;/i&gt;.
&lt;br&gt;
Далее мы привязываем выделенную память к AS функцией &lt;i&gt;vkBindAccelerationStructureMemoryNVX&lt;/i&gt;.&lt;/p&gt;

&lt;p&gt;Наша AS готова к постройке. Для построения AS драйверу понадобится временная память для работы, и ее выделить мы должны тоже сами (как и везде в Vulkan, собственно). Чтобы узнать требования к этому буферу нам понадобится функция &lt;i&gt;vkGetAccelerationStructureScratchMemoryRequirementsNVX&lt;/i&gt;.&lt;/p&gt;

&lt;p&gt;Так как построение AS происходит на GPU, нам понадобится командный буфер. Теперь, имея все это на руках, мы можем строить нашу AS.&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;overflow full&quot;&gt;&lt;pre&gt;vkCmdBuildAccelerationStructureNVX&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;commandBuffer,
                                   VK_ACCELERATION_STRUCTURE_TYPE_BOTTOM_LEVEL_NVX,
                                   &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, VK_NULL_HANDLE, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;,
                                   &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;, &amp;amp;geometry,
                                   &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, VK_FALSE,
                                   mScene.bottomLevelAS&lt;span class=&quot;bracket&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;]&lt;/span&gt;.accelerationStructure, VK_NULL_HANDLE,
                                   scratchBuffer.GetBuffer&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;

vkCmdPipelineBarrier&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;commandBuffer,
                     VK_PIPELINE_STAGE_RAYTRACING_BIT_NVX, 
                     VK_PIPELINE_STAGE_RAYTRACING_BIT_NVX,
                     &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;,
                     &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;, &amp;amp;memoryBarrier, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;,
                     &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;bound&quot;&gt;&lt;p&gt;Обратите внимание, что здесь мы явно указываем нашу ранее созданную геометрию, и количество. В нашем случае это всего 1 треугольник.&lt;/p&gt;

&lt;p&gt;Теперь надо построить AS верхнего уровня (TLAS). Как мы уже знаем, TLAS, по сути, является контейнером для одной или множества BLAS, которые размещаются в нем посредством инстансинга.&amp;nbsp; Для этого нам нужно заполнить структуру &lt;i&gt;VkGeometryInstance&lt;/i&gt; для каждого экземпляра BLAS, указав соответствующий AS и матрицу трансформации. Получившийся массив инстансов нужно загрузить в буфер.&lt;/p&gt;

&lt;p&gt;Теперь нам нужно повторить все те же самые шаги, что и для построения BLAS. Отличия будут в параметрах вызываемых функций. Так при вызове &lt;i&gt;vkCreateAccelerationStructureNVX&lt;/i&gt; мы укажем, что хотим создать AS верхнего уровня, а при вызове &lt;i&gt;vkCmdBuildAccelerationStructureNVX&lt;/i&gt; вместо геометрий надо указать буфер с инстансами.&lt;/p&gt;

&lt;p&gt;Следующим шагом будет загрузка шейдеров и создание конвейера (raytracing pipeline). Для нашего примера нам понадобится 3 шейдера: &lt;u&gt;ray_gen.glsl&lt;/u&gt;, &lt;u&gt;ray_chit.glsl&lt;/u&gt; и &lt;u&gt;ray_miss.glsl&lt;/u&gt;. Для каждого из них нам понадобится своя стадия:&lt;/p&gt;
&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;vulkanhelpers::Shader rayGenShader, rayChitShader, rayMissShader;
rayGenShader.LoadFromFile&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;sShadersFolder + &lt;span class=&quot;string&quot;&gt;&amp;quot;ray_gen.bin&amp;quot;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;.c_str&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
rayChitShader.LoadFromFile&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;sShadersFolder + &lt;span class=&quot;string&quot;&gt;&amp;quot;ray_chit.bin&amp;quot;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;.c_str&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
rayMissShader.LoadFromFile&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;sShadersFolder + &lt;span class=&quot;string&quot;&gt;&amp;quot;ray_miss.bin&amp;quot;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;.c_str&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;

std::vector&lt;span class=&quot;bracket&quot;&gt;&amp;lt;&lt;/span&gt;VkPipelineShaderStageCreateInfo&lt;span class=&quot;bracket&quot;&gt;&amp;gt;&lt;/span&gt; shaderStages&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;{
    rayGenShader.GetShaderStage&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;VK_SHADER_STAGE_RAYGEN_BIT_NVX&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;,
    rayChitShader.GetShaderStage&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;VK_SHADER_STAGE_CLOSEST_HIT_BIT_NVX&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;,
    rayMissShader.GetShaderStage&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;VK_SHADER_STAGE_MISS_BIT_NVX&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;
}&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Далее нам нужно будет указать номера групп для каждого из них. В нашем случае все просто: всего 3 группы с номерами 0, 1 и 2 соответственно.&lt;/p&gt;

&lt;p&gt;Также здесь мы создадим набор описателей шейдерных ресурсов (descriptor set). Этих ресурсов у нас 2: это наша TLAS, представляющая сцену для трассировки, и изображение (image) для результатов трассировки.&lt;/p&gt;

&lt;p&gt;Теперь заполним структуру &lt;i&gt;VkRaytracingPipelineCreateInfoNVX&lt;/i&gt; указав наши шейдерные стадии и номера их групп, а также наш &lt;i&gt;descriptor set&lt;/i&gt;, и вызовом функции &lt;i&gt;vkCreateRaytracingPipelinesNVX&lt;/i&gt; создадим конвейер для трассировки.&lt;/p&gt;

&lt;p&gt;Последним шагом будет создание таблицы привязки шейдеров (SBT). Как уже говорилось ранее &amp;mdash; SBT это всего лишь буфер в котором лежат группы шейдеров (точнее, ссылки на них). Соответственно нам нужно создать буфер размером “&lt;i&gt;количество_групп * размер группы&lt;/i&gt;”. Размер группы можно узнать запросив устройство (device) заполнить нам структуру &lt;i&gt;VkPhysicalDeviceRaytracingPropertiesNVX&lt;/i&gt;.&lt;/p&gt;

&lt;p&gt;Создав буфер, заполним его группами шейдеров которые мы указали при создании конвейера. Для этого нам понадобиться функция &lt;i&gt;vkGetRaytracingShaderHandlesNVX&lt;/i&gt;:&lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;overflow full&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;void&lt;/span&gt;* mem = mShaderBindingTable.Map&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;shaderBindingTableSize&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
vkGetRaytracingShaderHandlesNVX&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;mDevice, mRTPipeline, &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, numGroups, shaderBindingTableSize, mem&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
mShaderBindingTable.Unmap&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;bound&quot;&gt;&lt;p&gt;Итак, у нас все готово для запуска трассировки! Для этого существует команда &lt;i&gt;vkCmdTraceRaysNVX&lt;/i&gt;.
&lt;br&gt;
В качестве параметров она принимает буфер SBT, а также смещения до начальной группы каждого из подмножеств шейдеров.&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;void&lt;/span&gt; vkCmdTraceRaysNVX&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;VkCommandBuffer commandBuffer,
                       VkBuffer        raygenShaderBindingTableBuffer,
                       VkDeviceSize    raygenShaderBindingOffset,
                       VkBuffer        missShaderBindingTableBuffer,
                       VkDeviceSize    missShaderBindingOffset,
                       VkDeviceSize    missShaderBindingStride,
                       VkBuffer        hitShaderBindingTableBuffer,
                       VkDeviceSize    hitShaderBindingOffset,
                       VkDeviceSize    hitShaderBindingStride,
                       uint32_t        width,
                       uint32_t        height&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Как видим, мы можем использовать разные буферы для разных групп. В нашем случае SBT буфер один.
&lt;br&gt;
Raygen шейдер у нас располагается в нулевой группе, и потому его смещение (offset) равно 0.
&lt;br&gt;
Miss шейдер у нас располагается во второй группе, и потому его смещение = “2 * размер_группы“.
&lt;br&gt;
Hit shader у нас находится в первой группе, и его смещение = размер_группы.&lt;/p&gt;

&lt;p&gt;Последними двумя параметрами идут ширина и высота “экрана”. Raygen шейдер будет вызван один раз для каждого “пиксела”, итого &lt;i&gt;width * height&lt;/i&gt; раз.&lt;/p&gt;

&lt;p&gt;Нам осталось разобраться с кодом шейдеров и можем запускать наше замечательное приложение.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;ray_gen.glsl&lt;/b&gt;&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;#version &lt;span class=&quot;digit&quot;&gt;460&lt;/span&gt;
#extension GL_NVX_raytracing : require

layout&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;set = &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, binding = &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; uniform accelerationStructureNVX Scene;
layout&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;set = &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;, binding = &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;, rgba8&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; uniform image2D ResultImage;

layout&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;location = &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; rayPayloadNVX vec3 ResultColor;

&lt;span class=&quot;key&quot;&gt;void&lt;/span&gt; main&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; {
    &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; vec2 uv = vec2&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;gl_LaunchIDNVX.xy&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; / vec2&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;gl_LaunchSizeNVX.xy - &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;

    &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; vec3 origin = vec3&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;uv.x, &lt;span class=&quot;digit&quot;&gt;1.0&lt;/span&gt;f - uv.y, -&lt;span class=&quot;digit&quot;&gt;1.0&lt;/span&gt;f&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
    &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; vec3 direction = vec3&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f, &lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f, &lt;span class=&quot;digit&quot;&gt;1.0&lt;/span&gt;f&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;

    &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; uint rayFlags = gl_RayFlagsNoneNVX;
    &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; uint cullMask = &lt;span class=&quot;digit&quot;&gt;0xFF&lt;/span&gt;;
    &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; uint sbtRecordOffset = &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;;
    &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; uint sbtRecordStride = &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;;
    &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; uint missIndex = &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;;
    &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; tmin = &lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f;
    &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; tmax = &lt;span class=&quot;digit&quot;&gt;10.0&lt;/span&gt;f;
    &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;int&lt;/span&gt; payloadLocation = &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;;

    traceNVX&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;Scene,
             rayFlags,
             cullMask,
             sbtRecordOffset,
             sbtRecordStride,
             missIndex,
             origin,
             tmin,
             direction,
             tmax,
             payloadLocation&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;

    imageStore&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;ResultImage, ivec2&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;gl_LaunchIDNVX.xy&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;, vec4&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;ResultColor, &lt;span class=&quot;digit&quot;&gt;1.0&lt;/span&gt;f&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
}&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Строка “&lt;i&gt;#extension GL_NVX_raytracing : require&lt;/i&gt;” говорит компилятору что мы хотим использовать расширение для рейтрейсинга.&lt;/p&gt;

&lt;p&gt;Также вы могли заметить несколько новых типов и глобальных переменных.&lt;/p&gt;

&lt;p&gt;Новый тип ресурса &lt;i&gt;accelerationStructureNVX&lt;/i&gt; позволяет передать шейдеру наш TLAS представляющий сцену, по которой будет вестись трассировка.
&lt;br&gt;
&lt;i&gt;rayPayloadNVX&lt;/i&gt; является объявлением данных луча, которые будут передаваться дальше в шейдеры пересечения и промаха. В нашем случае это простой &lt;i&gt;vec3&lt;/i&gt; для цвета.&lt;/p&gt;
&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; vec2 uv = vec2&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;gl_LaunchIDNVX.xy&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; / vec2&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;gl_LaunchSizeNVX.xy - &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Здесь мы вычисляем нормализованные координаты пиксела на нашем “экране”. &lt;i&gt;gl_LaunchIDNVX&lt;/i&gt; содержит в себе индекс текущего потока исполнения, подобно &lt;i&gt;gl_GlobalInvocationID&lt;/i&gt; в compute шейдерах.
&lt;br&gt;
&lt;i&gt;gl_LaunchSizeNVX&lt;/i&gt; содержит в себе те самые width и height что мы передавали в &lt;i&gt;vkCmdTraceRaysNVX&lt;/i&gt;.
&lt;br&gt;
Поделив одно на другое мы получим нормализованные координаты в “экранном пространстве”. Это нужно нам, ибо именно в них мы задали координаты вершин нашего треугольника.
&lt;br&gt;
Мы используем эти координаты для получения отправной точки нашего луча.&lt;/p&gt;
&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; vec3 origin = vec3&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;uv.x, &lt;span class=&quot;digit&quot;&gt;1.0&lt;/span&gt;f - uv.y, -&lt;span class=&quot;digit&quot;&gt;1.0&lt;/span&gt;f&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Мы “переворачиваем” Y, потому как в координатной системе экрана Vulkan ось Y направлена вниз. Если этого не сделать, то наше изображение будет выглядеть “вверх ногами”.
&lt;br&gt;
Также мы немного “отодвигаемся назад”, указывая –1 как Z координату, чтобы треугольник был перед нами.&lt;/p&gt;
&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; vec3 direction = vec3&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f, &lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f, &lt;span class=&quot;digit&quot;&gt;1.0&lt;/span&gt;f&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Направление луча ставим прямо вперед вдоль оси Z.&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; uint rayFlags = gl_RayFlagsNoneNVX;
&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; uint cullMask = &lt;span class=&quot;digit&quot;&gt;0xFF&lt;/span&gt;;
&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; uint sbtRecordOffset = &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;;
&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; uint sbtRecordStride = &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;;
&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; uint missIndex = &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;;
&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; tmin = &lt;span class=&quot;digit&quot;&gt;0.0&lt;/span&gt;f;
&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;float&lt;/span&gt; tmax = &lt;span class=&quot;digit&quot;&gt;10.0&lt;/span&gt;f;
&lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;key&quot;&gt;int&lt;/span&gt; payloadLocation = &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Флаги луча и маску отсечения мы пока оставим стандартными, и вернемся к ним, когда будем делать более продвинутую трассировку.&lt;/p&gt;

&lt;p&gt;Параметры &lt;i&gt;sbtRecordOffset&lt;/i&gt; и &lt;i&gt;sbtRecordStride&lt;/i&gt; нужны для задания индекса и размера группы шейдеров пересечения в нашем SBT. Как определяются эти индексы мы с вами разобрали ранее.&lt;/p&gt;

&lt;p&gt;Параметр &lt;i&gt;missIndex&lt;/i&gt; определяет индекс группы miss шейдера.&lt;/p&gt;

&lt;p&gt;Параметры &lt;i&gt;tmin&lt;/i&gt; и &lt;i&gt;tmax&lt;/i&gt; задают отрезок на луче, внутри которого будет происходить поиск пересечения. Можете представлять это себе как &lt;i&gt;znear&lt;/i&gt; и &lt;i&gt;zfar&lt;/i&gt; плоскости отсечения.&lt;/p&gt;

&lt;p&gt;И, наконец, &lt;i&gt;payloadLocation&lt;/i&gt; определяет расположение данных луча. Это позволяет иметь несколько наборов данных луча для разных шейдеров, и указывать функции трассировки какой набор в каком случае использовать.&lt;/p&gt;

&lt;p&gt;В нашем случае у нас только один набор &lt;i&gt;ResultColor&lt;/i&gt;, и его &lt;i&gt;location&lt;/i&gt; равен 0.&lt;/p&gt;

&lt;p&gt;После вызова функции &lt;i&gt;traceNVX&lt;/i&gt;, в данных луча (в нашем случае это &lt;i&gt;ResultColor&lt;/i&gt;) будут лежать те значения, которые туда записали шейдеры пересечения или промаха.&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;imageStore&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;ResultImage, ivec2&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;gl_LaunchIDNVX.xy&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;, vec4&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;ResultColor, &lt;span class=&quot;digit&quot;&gt;1.0&lt;/span&gt;f&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;В нашем случае мы просто записываем получившийся цвет в результирующее изображение, используя &lt;i&gt;gl_LaunchIDNVX&lt;/i&gt; в качестве координат пиксела.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;ray_chit.glsl&lt;/b&gt;&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;overflow full&quot;&gt;&lt;pre&gt;#version &lt;span class=&quot;digit&quot;&gt;460&lt;/span&gt;
#extension GL_NVX_raytracing : require

layout&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;location = &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; rayPayloadInNVX vec3 ResultColor;
layout&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;location = &lt;span class=&quot;digit&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; hitAttributeNVX vec2 HitAttribs;

&lt;span class=&quot;key&quot;&gt;void&lt;/span&gt; main&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; {
    &lt;span class=&quot;key&quot;&gt;const&lt;/span&gt; vec3 barycentrics = vec3&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;1.0&lt;/span&gt;f - HitAttribs.x - HitAttribs.y, HitAttribs.x, HitAttribs.y&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
    ResultColor = vec3&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;barycentrics&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
}&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;bound&quot;&gt;&lt;p&gt;Здесь мы видим уже новый идентификатор &lt;i&gt;rayPayloadInNVX&lt;/i&gt;, который указывает на входные данные луча. Location их должен совпадать с таковым указанным при вызове функции &lt;i&gt;traceNVX&lt;/i&gt;.
&lt;br&gt;
&lt;i&gt;hitAttributeNVX&lt;/i&gt; позволяет шейдеру принять данные о пересечении, которые определяет шейдер поиска пересечения (intersection shader). Стандартный встроенный шейдер пересечения с треугольником передает только барицентрические координаты точки пересечения с треугольником. Используя их, можно получить любые интересующие нас данные просто интерполируя значения вершин треугольника.&lt;/p&gt;

&lt;p&gt;В нашем случае мы же просто записываем барицентрические координаты в выходной цвет.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;ray_miss.glsl&lt;/b&gt;&lt;/p&gt;

&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;#version &lt;span class=&quot;digit&quot;&gt;460&lt;/span&gt;
#extension GL_NVX_raytracing : require

layout&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;location = &lt;span class=&quot;digit&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; rayPayloadInNVX vec3 ResultColor;

&lt;span class=&quot;key&quot;&gt;void&lt;/span&gt; main&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt; {
    ResultColor = vec3&lt;span class=&quot;bracket&quot;&gt;(&lt;wbr&gt;&lt;/span&gt;&lt;span class=&quot;digit&quot;&gt;0.412&lt;/span&gt;f, &lt;span class=&quot;digit&quot;&gt;0.796&lt;/span&gt;f, &lt;span class=&quot;digit&quot;&gt;1.0&lt;/span&gt;f&lt;span class=&quot;bracket&quot;&gt;)&lt;/span&gt;;
}&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Данный шейдер похож на сильно упрощенную копию шейдера пересечения, в котором мы ничего не считаем, а просто возвращаем константный цвет. Таким образом мы задаем цвет фона нашего треугольника.&lt;/p&gt;

&lt;p&gt;Скомпилировать эти шейдеры можно утилитой &lt;i&gt;glslangValidator&lt;/i&gt;, входящей в состав Vulkan SDK.&lt;/p&gt;

&lt;p&gt;После успешной компиляции приложения и шейдера мы наконец можем полюбоваться нашим треугольником неземной красоты.&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://gamedev.ru/files/images/135394_1541208412_screenshot_opt.png&quot; alt=&quot;rtx_hello_triangle | Введение в Vulkan Raytracing&quot; title=&quot;rtx_hello_triangle&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;p&gt;Согласитесь, никакая растеризация не позволит получить такой натуральный и физически-корректный треугольник ;)&lt;/p&gt;

&lt;p&gt;Что же, поздравляю! Теперь вы тоже официально RTX ON!&lt;/p&gt;

&lt;p&gt;Исходный код проекта:
&lt;br&gt;
&lt;a href=&quot;https://github.com/iOrange/rtxON/tree/happy_triangle_fixed&quot; rel=&quot;nofollow&quot;&gt;https://github.com/iOrange/rtxON/tree/happy_triangle_fixed&lt;/a&gt;&lt;/p&gt;</description>
</item>
<item>
  <guid isPermaLink="true">https://gamedev.ru/code/terms/USD</guid>
  <pubDate>Tue, 05 Jun 2018 03:11:57 GMT</pubDate>
  <title>Universal Scene Description (USD)</title>
  <link>https://gamedev.ru/code/terms/USD</link>
  <category>Графика</category>
  <category>Pixar</category>
  <category>форматы</category>
  <description>
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Universal Scene Description (USD)&lt;/strong&gt; &amp;mdash; файловый формат описания трехмерной сцены, разработанный компанией Pixar.&lt;/p&gt;

&lt;p&gt;Вся сцена может быть сохранена в одном файле или в нескольких. Данные из одного файла могут ссылаться на данные в других. USD формат может описывать иерархию 3D сцены, трехмерные геометрические объекты, 3D камеры, освещение, материалы для шейдинга, включая для физически реалистичного просчета (PBR), объектную анимацию, скелетную анимацию и скиннинг.&lt;/p&gt;

&lt;p&gt;Трехмерная геометрия может быть описана с сохранением полигональной топологии для последующей обработки, например, для просчета сглаженной геометрии Subdiv.&lt;/p&gt;

&lt;p&gt;Формат может быть представлен в бинарном или в текстовым виде. Наряду с расширением .usd (оба случая) используются расширения .usda для текстового представления и .usdc для бинарного.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;USDZ&lt;/strong&gt; &amp;mdash; формат файла, где вся сцена в usd файлах и файлы с текстурами помещаются в один файл &amp;mdash; не компрессированный zip-архив.&lt;/p&gt;

&lt;p&gt;USD формат можно расширять другими форматами, уже есть плагины для Alembic и Houdini.&lt;/p&gt;

&lt;p&gt;Для работы с USD предоставляется &lt;a href=&quot;https://gamedev.ru/code/terms/SDK&quot; title=&quot;SDK: Software Development Kit&quot;&gt;SDK&lt;/a&gt; на C++ и Python.&lt;/p&gt;

&lt;p&gt;Страница формата:
&lt;br&gt;
&lt;a href=&quot;http://graphics.pixar.com/usd/docs/Introduction-to-USD.html&quot; rel=&quot;nofollow&quot;&gt;http://graphics.pixar.com/usd/docs/Introduction-to-USD.html&lt;/a&gt;&lt;/p&gt;

&lt;div style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;https://gamedev.ru/files/images/usd.jpg&quot; alt=&quot;USD | Universal Scene Description (USD)&quot; title=&quot;USD&quot;&gt;&lt;/div&gt;&lt;/div&gt;</description>
</item>
<item>
  <guid isPermaLink="true">https://gamedev.ru/code/articles/gen_alg_in_gamedev</guid>
  <pubDate>Tue, 20 Mar 2018 07:05:25 GMT</pubDate>
  <title>Генетические алгоритмы в разработке игр.</title>
  <link>https://gamedev.ru/code/articles/gen_alg_in_gamedev</link>
  <comments>https://gamedev.ru/code/forum/?id=234513</comments>
  <category>Общее</category>
  <description>
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Привет детишки, сегодня я хочу рассказать вам сказку про генетические алгоритмы и их применение в игрострое. Простыми словами мы рассмотрим теорию и практику, а в качестве примера напишем небольшую утилиту для балансировки игры. &lt;/p&gt;
&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://gamedev.ru/files/images/evolv.jpg&quot; alt=&quot;EVOLV | Генетические алгоритмы в разработке игр.&quot; title=&quot;EVOLV&quot;&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;p&gt;Уровень подготовки: 
&lt;br&gt;
Если за плечами уже есть змейка и тетрис - читаем смело. Если нет, ничего страшного, расширяем кругозор.
&lt;br&gt;
Также желательны базовые знание LUA для понимания кода. &lt;/p&gt;

&lt;p&gt;Итак поехали!&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Теоретическая часть.&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;Наверняка многие уже не раз слышали про генетические алгоритмы и с чем их едят.
&lt;br&gt;
Что далеко ходить, у нас даже есть небольшая статья в разделе термины &lt;a href=&quot;https://gamedev.ru/code/terms/Genetic_Algorithm&quot; title=&quot;Генетический алгоритм&quot;&gt;Генетический алгоритм&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Но сегодня разговор не о теоретической, а практической стороне вопроса, как все это безобразие можно пустить в дело.&lt;/p&gt;

&lt;p&gt;Для начала попробую простыми словами рассказать, что же из себя представляет этот хитрый алгоритм.&lt;/p&gt;

&lt;p&gt;Если максимально упрощенно генетический алгоритм - ничто иное, как подбор коэффициентов для решения уравнения.
&lt;br&gt;
Вся соль кроется в способе подбора этих параметров, тут то мы и вспоминаем про название алгоритма.
&lt;br&gt;
Представим искомые коэффициенты как гены и вспомним, как работает эволюция.
&lt;br&gt;
Еще в далекие времена Дарвин говорил про естественный отбор, что всегда выживает самый приспособленный. Вот так же и у нас.&lt;/p&gt;

&lt;p&gt;алгоритм до неприличия прост:
&lt;br&gt;
1-создаем новое поколение популяции
&lt;br&gt;
2-отбираем самых приспособленных, остальных удаляем
&lt;br&gt;
3-из оставшихся выводим(селекционируем) новое поколение и снова пункт 2
&lt;br&gt;
И все это происходит до тех пор, поке не будет найдено решение задачи.&lt;/p&gt;

&lt;p&gt;Простой пример: уравнение X + Y = 100, здесь X и Y можно представить как гены.&lt;/p&gt;

&lt;p&gt;Создаем 4 особи со случайным набором ген:
&lt;br&gt;
( X = 2, Y = 2 )
&lt;br&gt;
( X = 20, Y = 2 )
&lt;br&gt;
( X = 30, Y = 50 )
&lt;br&gt;
( X = 22, Y = 44 )&lt;/p&gt;

&lt;p&gt;Проверяем приспособленность, для этого считаем насколько переменные приближают нас к решению уравнения:
&lt;br&gt;
| 2 + 2 | / 100 =&amp;nbsp; 0.04
&lt;br&gt;
| 20 + 2 | / 100 =&amp;nbsp; 0.22
&lt;br&gt;
| 30 + 50 | / 100 =&amp;nbsp; 0.8
&lt;br&gt;
| 22 + 44 | / 100 =&amp;nbsp; 0.66&lt;/p&gt;

&lt;p&gt;На основе лучшей пары создаем следующее поколение, также добавляем в него и лучших особей из предыдущего.
&lt;br&gt;
У нас крайне простой случай, у каждой особи всего по 2 гена.
&lt;br&gt;
Скрещиваем ( X1, Y1 ) и ( X2, Y2 ) и получаем в итоге ( X1, Y2 ) и ( X2, Y1 )&lt;/p&gt;

&lt;p&gt;новое поколение:
&lt;br&gt;
( X = 30, Y = 50 )&amp;nbsp; &amp;nbsp; родитель 1
&lt;br&gt;
( X = 22, Y = 44 )&amp;nbsp; &amp;nbsp; родитель 2
&lt;br&gt;
( X = 30, Y = 44)&amp;nbsp; &amp;nbsp; потомок 1
&lt;br&gt;
( X = 22, Y = 50)&amp;nbsp; &amp;nbsp; потомок 2&lt;/p&gt;

&lt;p&gt;И казалось бы, вот уже мы скачем верхом на эволюционном процессе и все задачи нам по плечу, но не все так просто.
&lt;br&gt;
На данном примере видно, что уже через 1 итерацию - новых генов взять неоткуда, а из старых уже найдена лучшая комбинация. 
&lt;br&gt;
Решения достигнуто не было, и система работает без изменений. 
&lt;br&gt;
Тут то нам на помощь приходят мутации, они не такие красивые как скажем в сталкере, но зато очень даже эффективные.&lt;/p&gt;

&lt;p&gt;Суть мутации проста: добавить новых генов при формировании следующей популяции.
&lt;br&gt;
Будь это совершенно новая особь или изменение случайных генов потомка, уже детали реализации.&amp;nbsp; &amp;nbsp; &lt;/p&gt;

&lt;p&gt;Но, как говорится и это еще не все! При большом количестве генов система порой приходит к равновесию, так и не найдя решения. 
&lt;br&gt;
И даже мутации из за малого удельного веса не спасают ситуацию.&lt;/p&gt;

&lt;p&gt;В качестве аналогии можно привести новичка, пришедшего в большую компанию.
&lt;br&gt;
У него горят глаза, полно энергии, идей, инновационные технологии.
&lt;br&gt;
Он кричит: “Я знаю как правильно, как нам улучшить компанию, достигнуть успеха, даже есть опыт” 
&lt;br&gt;
А с другой стороны - бывалые дяди, которые всю жизнь тут трудятся и знают, как делать дела. 
&lt;br&gt;
У них связи, авторитет, они сидят на откатах и не хотят ничего менять. 
&lt;br&gt;
И они ему говорят: “Ты кто такой ваще, ты нам мать что ли, че учить то нас вздумал, уйди,
&lt;br&gt;
не мешай работать” А дальше система либо его ломает, либо он уходит.&lt;/p&gt;

&lt;p&gt;И чтобы эволюция не буксовала на месте - нужны встряски. Как с динозаврами например. 
&lt;br&gt;
Как только происходит застой ( появился стабильный генотип ), всей системе нужна встряска. 
&lt;br&gt;
Удаление большей части популяции или даже полностью всей и создание недостающих особей со случайным набором генов.&lt;/p&gt;

&lt;p&gt;Опять же аналогия с компанией. 
&lt;br&gt;
Большие компании поделили рынок поровну и всем достается понемногу, рынок не развивается. 
&lt;br&gt;
И тут приходят стартапы, состоящие из тех самых молодчиков с горящими глазами. 
&lt;br&gt;
Конечно только один из ста в итоге останется на плаву, а остальные потонут, но тот кто сможет - сорвет куш.&lt;/p&gt;

&lt;p&gt;Итак ребятишки, давайте подведем итог, что же сегодня мы узнали.
&lt;br&gt;
Для работы генетического алгоритма нужны следующие вещи: 
&lt;br&gt;
-описать решение задачи набором параметров( генов ) назовем это описание особью.
&lt;br&gt;
-определить критерии успешности особи( мера правильности решения задачи )
&lt;br&gt;
-провести эволюционный отбор при помощи 3х методов: скрещивание, мутация и встряска.&lt;/p&gt;

&lt;p&gt;&lt;b&gt;Практическая часть.&lt;/b&gt;&lt;/p&gt;

&lt;p&gt;Формулы и циферки эт конечно хорошо, но как говорится ближе к телу.
&lt;br&gt;
Есть задача, с которой часто встречаются, как разработчики, так и игроки, а именно расчет билда.&lt;/p&gt;

&lt;p&gt;Для простоты примем, что игра пошаговая.
&lt;br&gt;
Есть монстр, у него нет хп и мы будем измерять максимально урон, который герой может нанести пока не погибнет. 
&lt;br&gt;
Каждые 3 хода монстр атакует игрока, с 20% шансом крита.&lt;/p&gt;

&lt;p&gt;Есть игрок со следующими параметрами: шкалы здоровья и силы, реген здоровья и силы, атака и защита.
&lt;br&gt;
Также у игрока есть экипировка, которая влияет на параметры.&lt;/p&gt;

&lt;p&gt;В качестве генов здесь будет выступать экипировка, что и в каком слоте находится у героя.&lt;/p&gt;

&lt;p&gt;руки:
&lt;br&gt;
меч1( атака 5, требует силы 5 )
&lt;br&gt;
меч2( атака 4, требует силы 3 )
&lt;br&gt;
меч3( атака 8, требует силы 10 )
&lt;br&gt;
щит1( защита 3, требует силы 2&amp;nbsp; )
&lt;br&gt;
щит2( защита 5, требует силы 3&amp;nbsp; )&lt;/p&gt;

&lt;p&gt;туловище:
&lt;br&gt;
доспех1( защита 5, атака -2 )
&lt;br&gt;
доспех2( защита 3, атака 0 )
&lt;br&gt;
доспех3( защита 1, реген здоровья 1 )&lt;/p&gt;

&lt;p&gt;голова:
&lt;br&gt;
шлем( защита 2 )
&lt;br&gt;
шапка силы( реген силы 1 )
&lt;br&gt;
шапка здоровья( реген здоровья 1 )&lt;/p&gt;

&lt;p&gt;макс здоровье 500
&lt;br&gt;
макс сила 100
&lt;br&gt;
реген здоровья 0
&lt;br&gt;
реген силы 5
&lt;br&gt;
атака 1
&lt;br&gt;
защита 1&lt;/p&gt;

&lt;p&gt;Монстр атакует за раз на 25 единиц, во время крита на 45, шанс крита 20%.
&lt;br&gt;
Игрок атакует каждый ход, когда хватает силы.&lt;/p&gt;

&lt;p&gt;Кодить все это дело будем на луа. Почему, да это быстро и весело :)&lt;/p&gt;

&lt;p&gt;Для начала пропишем параметры персонажа, монстра и экипировки.&lt;/p&gt;
 &lt;div id=&quot;spoilerHead11&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(11, 1)&quot;&gt;+ Входные данные&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;spoiler11&quot; style=&quot;display:none;&quot;&gt;&lt;div class=&quot;bound overflow&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(11, 0)&quot;&gt;&amp;minus; Скрыть&lt;/span&gt;
&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;PlayerDefaultParam = {
  HealthMax = 500,
  Health = 500,
  HealthRegen = 0,
  EnergyMax = 100,
  Energy = 100,
  EnergyRegen = 5,
  Attack = 1,
  Defence = 1,
  AttackEnergyUse = 0,
}

MonstrParam = {
  AtkDelay = 3,
  Attack = 25,
  AttackCrit = 45,
  CritChancePersent = 20,
}

ArmEquip = {
  {
    Name = &amp;quot;sword1&amp;quot;,
    Param = {
      Attack = 5,
      AttackEnergyUse = 5,
    }
  },
  {
    Name = &amp;quot;sword2&amp;quot;,
    Param = {
      Attack = 4,
      AttackEnergyUse = 3,
    }
  },  
  {
    Name = &amp;quot;sword3&amp;quot;,
    Param = {
      Attack = 8,
      AttackEnergyUse = 10,
    }
  },
  {
    Name = &amp;quot;shield1&amp;quot;,
    Param = {
      Defence = 3,
      AttackEnergyUse = 2,
    }
  },  
  {
    Name = &amp;quot;shield2&amp;quot;,
    Param = {
      Defence = 5,
      AttackEnergyUse = 3,
    }
  },
}

BodyEquip = {
  {
    Name = &amp;quot;armor1&amp;quot;,
    Param = {
      Defence = 5,
      AttackEnergyUse = 5,
    }
  },
  {
    Name = &amp;quot;armor2&amp;quot;,
    Param = {
      Defence = 3,
    }
  },  
  {
    Name = &amp;quot;armor3&amp;quot;,
    Param = {
      Defence = 1,
      HealthRegen = 1,
    }
  },
}

HeadEquip = {
  {
    Name = &amp;quot;helmet_armor&amp;quot;,
    Param = {
      Defence = 2,
    }
  },
  {
    Name = &amp;quot;helmet_energy&amp;quot;,
    Param = {
      EnergyRegen = 1,
    }
  },  
  {
    Name = &amp;quot;helmet_health&amp;quot;,
    Param = {
      HealthRegen = 1,
    }
  },
}&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;bound overflow&quot;&gt;&lt;p&gt;Теперь когда с входными данными закончено - идем дальше.
&lt;br&gt;
Первым делом нам нужно сгенерировать особь( то есть создать случайный билд )&lt;/p&gt;

&lt;p&gt;Для этого напишем небольшую функцию:&lt;/p&gt;
 &lt;div id=&quot;spoilerHead12&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(12, 1)&quot;&gt;+ CreateRandomBuild()&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;spoiler12&quot; style=&quot;display:none;&quot;&gt;&lt;div class=&quot;bound overflow&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(12, 0)&quot;&gt;&amp;minus; Скрыть&lt;/span&gt;
&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;function CreateRandomBuild()
  local Result = {}
  Result.Param = {}

  for ParamName, Value in pairs( PlayerDefaultParam ) do
    Result.Param[ ParamName ] = Value
  end

  Result.Equipment = {
    {
      EquipTable = ArmEquip,
      EquipIndex = math.random( #ArmEquip ),
    },
    {
      EquipTable = ArmEquip,
      EquipIndex = math.random( #ArmEquip ),
    },
    {
      EquipTable = BodyEquip,
      EquipIndex = math.random( #BodyEquip ),
    },
    {
      EquipTable = HeadEquip,
      EquipIndex = math.random( #HeadEquip ),
    },
  }

  SetEquipmentToBuild( Result )

  return Result
end&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;bound overflow&quot;&gt;&lt;p&gt;Копируем параметры заданные по умолчанию в&amp;nbsp; новый билд)
&lt;br&gt;
Дальше устанавливаем в слоты&amp;nbsp; экипировку.
&lt;br&gt;
Здесь вместо готовой таблицы устанавливаем ссылку на исходную таблицу и генерируем в ее пределах случайный индекс. 
&lt;br&gt;
Это нам понадобится для селекции.
&lt;br&gt;
Перед тем как вернуть созданный билд, добавляем параметры из экипировки в параметры самого билда. 
&lt;br&gt;
Это делается небольшой функцией:&lt;/p&gt;
 &lt;div id=&quot;spoilerHead13&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(13, 1)&quot;&gt;+ SetEquipmentToBuild( Build )&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;spoiler13&quot; style=&quot;display:none;&quot;&gt;&lt;div class=&quot;bound overflow&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(13, 0)&quot;&gt;&amp;minus; Скрыть&lt;/span&gt;
&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;function SetEquipmentToBuild( Build )
  for _, Equipment in pairs( Build.Equipment ) do
    local EquipmentParam = Equipment.EquipTable[ Equipment.EquipIndex ].Param

    for ParamName, Value in pairs( EquipmentParam ) do
      Build.Param[ ParamName ] = Build.Param[ ParamName ] + Value
    end
  end  
end&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;bound overflow&quot;&gt;&lt;p&gt;Теперь нужен способ скрещивания особей, так как гены у нас, это просто индексы массива,
&lt;br&gt;
 то скрещивание будет представлять из себя случайный выбор гена у одной из особей родителей. 
&lt;br&gt;
Ну и чтоб ускорить эволюцию немного, одна из особей( доминантная ) будет иметь больший шанс( 6 из 10 ) передать свои гены. &lt;/p&gt;

 &lt;div id=&quot;spoilerHead14&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(14, 1)&quot;&gt;+ CrossingBuils( Build1, Build2 )&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;spoiler14&quot; style=&quot;display:none;&quot;&gt;&lt;div class=&quot;bound overflow&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(14, 0)&quot;&gt;&amp;minus; Скрыть&lt;/span&gt;
&lt;/div&gt;
&lt;div class=&quot;overflow full&quot;&gt;&lt;pre&gt;function CrossingBuils( Build1, Build2 )
  local Result = {}
  Result.Param = {}

  for ParamName, Value in pairs( PlayerDefaultParam ) do
    Result.Param[ ParamName ] = Value
  end

  Result.Equipment = {}
  for I = 1, #Build1.Equipment do
    local DominantGeneChance = 6 -- 6/10
    if math.random( 10 ) &amp;lt;= DominantGeneChance then
      Result.Equipment[ I ] = {
        EquipTable = Build1.Equipment[ I ].EquipTable,
        EquipIndex = Build1.Equipment[ I ].EquipIndex,
      }
    else
      Result.Equipment[ I ] = {
        EquipTable = Build2.Equipment[ I ].EquipTable,
        EquipIndex = Build2.Equipment[ I ].EquipIndex,
      }
    end
  end

  local MutationChance = 2 -- 2/10
  if math.random( 10 ) &amp;lt;= MutationChance and Build1 ~= Build2 then
    local Ind = math.random( #Result.Equipment )
    Result.Equipment[ Ind ].EquipIndex = math.random( #Result.Equipment[ Ind ].EquipTable )
  end

  SetEquipmentToBuild( Result )

  return Result
end&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;bound&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;bound overflow&quot;&gt;&lt;p&gt;Также во избежание вырождения популяции добавим шанс мутации ( 2 из 10 )
&lt;br&gt;
В нашем случае мутация - случайное изменение одного из генов( слота экипировки )
&lt;br&gt;
Добавляем параметры экипировки к билду и готово!&lt;/p&gt;

&lt;p&gt;Еще нам понадобится функция оценки жизнеспособности билда, пишем:&lt;/p&gt;
 &lt;div id=&quot;spoilerHead15&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(15, 1)&quot;&gt;+ TestBattle( Build )&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;spoiler15&quot; style=&quot;display:none;&quot;&gt;&lt;div class=&quot;bound overflow&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(15, 0)&quot;&gt;&amp;minus; Скрыть&lt;/span&gt;
&lt;/div&gt;
&lt;div class=&quot;overflow full&quot;&gt;&lt;pre&gt;function TestBattle( Build )
  local TotalDamage = 0  
  local BattleStep = 0

  while Build.Param.Health &amp;gt; 0 do
    BattleStep = BattleStep + 1

    if Build.Param.Energy &amp;gt; Build.Param.AttackEnergyUse then
      Build.Param.Energy = Build.Param.Energy - Build.Param.AttackEnergyUse
      TotalDamage = TotalDamage + Build.Param.Attack
    end

    if BattleStep % MonstrParam.AtkDelay == 0 then
      local MonstrAttack = MonstrParam.Attack
      if math.random( 100 ) &amp;lt;= MonstrParam.CritChancePersent then
        MonstrAttack = MonstrParam.AttackCrit
      end

      Build.Param.Health = Build.Param.Health - math.max( 0, MonstrAttack - Build.Param.Defence )
    end

    Build.Param.Health = math.min( Build.Param.HealthMax, Build.Param.Health + Build.Param.HealthRegen )
    Build.Param.Energy = math.min( Build.Param.EnergyMax, Build.Param.Energy + Build.Param.EnergyRegen )

    if BattleStep &amp;gt; 9999 then
      break
    end
  end

  return TotalDamage, BattleStep
end&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;bound&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;bound overflow&quot;&gt;&lt;p&gt;Оценивать будем по максимальному нанесенному урону, также не лишним будет знать - сколько на это потребовалось шагов.&lt;/p&gt;

&lt;p&gt;На каждом шаге мы делаем следующие вещи: 
&lt;br&gt;
-Если достаточно энергии - атакуем.
&lt;br&gt;
-Если ход кратный трем, получаем урон от монстра( с 20% шансом крита ).
&lt;br&gt;
-Проводим регенерацию здоровья и энергии.
&lt;br&gt;
-Проверяем, вдруг билд получился круче монстра, ограничивая все это дело 10000 итераций.&lt;/p&gt;

&lt;p&gt;В итоге&amp;nbsp; возвращаем нанесенный урон и продолжительность битвы.&lt;/p&gt;

&lt;p&gt;Итак все приготовления сделаны и пришло время окунуться в увлекательный мир генетических алгоритмов. Вы готовы дети!?&lt;/p&gt;

&lt;p&gt;А вот и сам генетический алгоритм, подгреб к нашему празднику невежества и знаний:&lt;/p&gt;
 &lt;div id=&quot;spoilerHead16&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(16, 1)&quot;&gt;+ GenTest()&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div id=&quot;spoiler16&quot; style=&quot;display:none;&quot;&gt;&lt;div class=&quot;bound overflow&quot;&gt;&lt;span class=&quot;splink&quot; onclick=&quot;javascript:showSpoiler(16, 0)&quot;&gt;&amp;minus; Скрыть&lt;/span&gt;
&lt;/div&gt;
&lt;div class=&quot;overflow full&quot;&gt;&lt;pre class=&quot;small&quot;&gt;function GenTest()
  local PopulationCount = 16
  Builds = {}
  for I = 1, PopulationCount do
    Builds[ I ] = CreateRandomBuild()
  end

  local GenerationCount = 50
  for GenerationInd = 1, GenerationCount do
    BuildsBattleResult = {}
    for I = 1, #Builds do  
      local TotalDamage, BattleStep = TestBattle( Builds[ I ] )
      
      BuildsBattleResult[ I ] = { 
        Build = Builds[ I ],
        Damage = TotalDamage,
        BattleLenght = BattleStep,
       }
    end

    table.sort( BuildsBattleResult, function( A, B ) return A.Damage &amp;gt; B.Damage end )
    
    local NewBuilds = {}
    local ParentCount = PopulationCount / 2
    for I = 1, ParentCount do
      table.insert( NewBuilds, CrossingBuils( BuildsBattleResult[ I ].Build, BuildsBattleResult[ I ].Build ) )
      table.insert( NewBuilds, CrossingBuils( BuildsBattleResult[ I ].Build, BuildsBattleResult[ I + ParentCount ].Build ) )
    end

    Builds = NewBuilds
    print( &amp;quot;GenerationInd &amp;quot; .. GenerationInd .. &amp;quot;    dmg &amp;quot; .. BuildsBattleResult[ 1 ].Damage .. &amp;quot;\tsteps\t&amp;quot; .. BuildsBattleResult[ 1 ].BattleLenght )
  end

  for _, Build in pairs( BuildsBattleResult[ 1 ].Build.Equipment ) do
    print( Build.EquipTable[ Build.EquipIndex ].Name )
  end
end&lt;/pre&gt;&lt;/div&gt;
&lt;div class=&quot;bound&quot;&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;bound overflow&quot;&gt;&lt;p&gt;Создаем таблицу и заполняем случайными билдами. Размер популяции был выбран в 16 особей.
&lt;br&gt;
Далее вся популяция проходит проверку боем и сортируется по нанесенному урону.&lt;/p&gt;

&lt;p&gt;Переходим к селекции. Гены лучшей половины популяции переходят в следующее поколение. 
&lt;br&gt;
После чего начинаем скрещивать успешную половину с теми, кто отбор так и не прошел, в этот же момент может происходить мутация.&lt;/p&gt;

&lt;p&gt;В итоге мы получаем количество особей, что было в начале. И опять бой, селекция, бой… 
&lt;br&gt;
Казалось бы все этом может длиться вечно, но на небольшом количестве генов лучшая комбинация находится довольно быстро, 
&lt;br&gt;
поэтому у нас стоит ограничение в 50 циклов. После чего можно подводить итоги.&lt;/p&gt;

&lt;p&gt;Для запуска всего этого мракобесия осталось только взвести рандомизатор по текущему времени и запустить симуляцию.&lt;/p&gt;
&lt;div class=&quot;overflow pre&quot;&gt;&lt;pre&gt;math.randomseed( os.time() ) 
GenTest()&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Сохраняем , запускаем, затаив дыхание ждем результата.&lt;/p&gt;

&lt;p&gt;И вуаля! Видим, что самый хороший билд это: два вторых меча, доспех номер 3 и шапка жизни :)&lt;/p&gt;

&lt;p&gt;Запустив симуляцию еще несколько раз подряд - видно что, этот билд является стабильным, но все же иногда есть небольшие отклонения. 
&lt;br&gt;
Это получается за счет небольшого количества поколений и рандома связанного с критическими атаками монстра. 
&lt;br&gt;
При увеличении количества поколений до 500 - билд получается всегда один и тот же. 
&lt;br&gt;
Поэтому для балансировки важно правильно подобрать количество итераций.&lt;/p&gt;

&lt;p&gt;Вот сводный график 3х запусков моделирования. Зависимость нанесенного урона от поколения особей. 
&lt;br&gt;
&lt;img src=&quot;https://gamedev.ru/files/images/evolv_gr.png&quot; alt=&quot;EVOLV_gr | Генетические алгоритмы в разработке игр.&quot; title=&quot;EVOLV_gr&quot;&gt;
&lt;br&gt;
Высокий старт всех случаев обусловлен тем, что это результаты лучшей особи первого поколения. 
&lt;br&gt;
Основная часть эволюции происходит в течении первых 20 циклов, после уже видна стабилизация.&lt;/p&gt;

&lt;p&gt;Снизив количество поколений до 10, можно увидеть и другие жизнеспособные варианты. 
&lt;br&gt;
Что интересно, в них ни разу не было щитов, а значит они нуждаются в ребалансе. 
&lt;br&gt;
Та же при помощи этого алгоритма можно определить среднее время жизни монстра, 
&lt;br&gt;
а если немного переделать, то появится возможность сталкивать билды друг с другом. &lt;/p&gt;

&lt;p&gt;&lt;b&gt;Выводы.&lt;/b&gt;
&lt;br&gt;
Подводя итог, у нас получилось за небольшое время сделать довольно гибкий инструмент для балансировки игры, 
&lt;br&gt;
который поможет проверить, работают ли все как задумано, или есть места которые стоит поправить.&lt;/p&gt;

&lt;p&gt;Правда в примере не был использован механизм встряски, из за малого количества генов, его эффект был бы просто незаметен. 
&lt;br&gt;
Надеюсь&amp;nbsp; еще расскажу вам об этом, но это уже будет совсем другая история.&lt;/p&gt;

&lt;p&gt;А пока всего хорошего и спасибо за рыбу :)&lt;/p&gt;</description>
</item>
<item>
  <guid isPermaLink="true">https://gamedev.ru/code/news/DirectX_Raytracing_DXR</guid>
  <pubDate>Tue, 20 Mar 2018 04:43:27 GMT</pubDate>
  <title>Microsoft анонсировала DirectX Raytracing (DXR)</title>
  <link>https://gamedev.ru/code/news/DirectX_Raytracing_DXR</link>
  <comments>https://gamedev.ru/code/forum/?id=234507</comments>
  <category>Графика</category>
  <category>DirectX</category>
  <category>DXR</category>
  <category>EA</category>
  <category>GDC</category>
  <category>Microsoft</category>
  <category>NVIDIA</category>
  <category>raytracing</category>
  <category>Unity</category>
  <category>Unreal</category>
  <description>
&lt;p&gt;&lt;/p&gt;

&lt;p&gt;На проходящей в Сан-Франциско конференции разработчиков игр (GDC) компания Microsoft анонсировала поддержку в DirectX 12 технологии &lt;strong&gt;DirectX Raytracing (DXR)&lt;/strong&gt;. Новый программный интерфейс позволяет производить просчет рейтрейсинга при помощи поддержки этой технологии в железе.&lt;/p&gt;

&lt;p&gt;Рейтрейсинг позволяет достичь расчета реалистичного освещения, теней и материалов. Классические технологии вывода графики в компьютерных играх основаны на растеризации, но, несмотря на большой прогресс в алгоритмах, реалистичность генерируемых изображений все равно имеет ряд проблем. Растеризация в целом работает по другому принципу, чем визуальное восприятие человека. Рейтрейсинг, в свою очередь, достаточно близко описывает поведение физического процесса.&lt;/p&gt;

&lt;div style=&quot;text-align: center&quot;&gt;&lt;img src=&quot;https://gamedev.ru/files/images/seed-screenshot.jpg&quot; alt=&quot;SEED-screenshot | Microsoft анонсировала DirectX Raytracing (DXR)&quot; title=&quot;SEED-screenshot&quot;&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;На первых парах DXR может использоваться для замены таких техник просчета графики, как screen space reflection, затем DXR может взять на себя вычисления global illumination, а в итоге вообще заменить собой растеризацию при выводе трехмерных сцен.&lt;/p&gt;

&lt;p&gt;Microsoft уверяет, что технология DXR уже может работать на видеокартах, доступных на рынке. По заявлению компании DirectX Raytracing уже поддерживают Electronic Arts в своем движке Frostbite и SEED, Remedy Games в Northlight, Epic в Unreal Engine и Unity в Unity3D. Ряд других компаний также занимаются внедрением DXR.&lt;/p&gt;

&lt;p&gt;Уже сейчас доступен &lt;a href=&quot;http://forums.directxtech.com/index.php?topic=5860.0&quot; rel=&quot;nofollow&quot;&gt;экспериментальный DXR SDK&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Подробнее о самой технологии смотрите в блоге компании:
&lt;br&gt;
&lt;a href=&quot;https://blogs.msdn.microsoft.com/directx/2018/03/19/announcing-microsoft-directx-raytracing/&quot; rel=&quot;nofollow&quot;&gt;https://blogs.msdn.microsoft.com/directx/2018/03/19/announcing-mi&amp;hellip; x-raytracing/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;
&lt;br&gt;
Демонстрация Real-time Raytracing Experiment using DXR (DirectX Raytracing) в проекте PICA PICA на движке SEED:&lt;/p&gt;

&lt;p&gt;
&lt;div class=&quot;video-container&quot;&gt;&lt;div class=&quot;video&quot; id=&quot;tube10&quot; data-value=&quot;LXo0WdlELJk&quot;&gt;&lt;img data-value=&quot;LXo0WdlELJk&quot; src=&quot;https://img.youtube.com/vi/LXo0WdlELJk/maxresdefault.jpg&quot; onload=&quot;skif.loadYoutubeImage(event)&quot; alt=&quot;Запустить видео по клику - Как делать игры&quot; loading=&quot;lazy&quot;&gt;&lt;img class=&quot;play&quot; src=&quot;https://gamedev.ru/_img/youtube-play.svg&quot; onclick=&quot;skif.insertYoutube(10)&quot; onMouseover=&quot;skif.insertYoutube(10)&quot; alt=&quot;Запустить видео по клику - Как делать игры&quot; loading=&quot;lazy&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;Remedy Games в Northlight:&lt;/p&gt;

&lt;p&gt;
&lt;div class=&quot;video-container&quot;&gt;&lt;div class=&quot;video&quot; id=&quot;tube11&quot; data-value=&quot;70W2aFr5-Xk&quot;&gt;&lt;img data-value=&quot;70W2aFr5-Xk&quot; src=&quot;https://img.youtube.com/vi/70W2aFr5-Xk/maxresdefault.jpg&quot; onload=&quot;skif.loadYoutubeImage(event)&quot; alt=&quot;Запустить видео по клику - Как делать игры&quot; loading=&quot;lazy&quot;&gt;&lt;img class=&quot;play&quot; src=&quot;https://gamedev.ru/_img/youtube-play.svg&quot; onclick=&quot;skif.insertYoutube(11)&quot; onMouseover=&quot;skif.insertYoutube(11)&quot; alt=&quot;Запустить видео по клику - Как делать игры&quot; loading=&quot;lazy&quot;&gt;&lt;/div&gt;&lt;/div&gt;&lt;/p&gt;&lt;/p&gt;</description>
</item>
<item>
  <guid isPermaLink="true">https://gamedev.ru/code/terms/verge3d</guid>
  <pubDate>Fri, 08 Dec 2017 16:33:18 GMT</pubDate>
  <title>Verge3D</title>
  <link>https://gamedev.ru/code/terms/verge3d</link>
  <comments>https://gamedev.ru/code/forum/?id=233343</comments>
  <category>Графика</category>
  <category>3dweb</category>
  <category>HTML5</category>
  <category>веб</category>
  <category>WebGL</category>
  <description>
&lt;p&gt;&lt;/p&gt;

&lt;/div&gt;
&lt;div class=&quot;full&quot;&gt;&lt;img src=&quot;https://gamedev.ru/files/images/spinner.jpg&quot; alt=&quot;Verge3D Spinner | Verge3D&quot; title=&quot;Verge3D Spinner&quot;&gt;&lt;/div&gt;&lt;!--full end--&gt;
&lt;div class=&quot;bound overflow&quot;&gt;
&lt;br&gt;
&lt;p&gt;Verge3D - трёхмерный движок для веба, использующий технологию WebGL. Основан на Three.js, от которого отличается наличием реалистичных материалов, визуальным редактором логики Puzzles, средой создания приложений и более тесной интеграцией с программами моделирования Blender и 3ds Max. 14 декабря 2017 года в рамках проекта был запущен облачный сервис Verge3D Network для публикации и распространения 3D-приложений.&lt;/p&gt;

&lt;p&gt;Движок платный, имеется неограниченная по времени и функционалу &lt;a href=&quot;https://www.soft8soft.com/get-verge3d/&quot; rel=&quot;nofollow&quot;&gt;Trial версия&lt;/a&gt;. Исходный код открыт, но предоставляется только корпоративным подписчикам.&lt;/p&gt;

&lt;p&gt;Обзорная &lt;a href=&quot;https://gamedev.ru/community/verge3d/articles/verge3d_review&quot;&gt;статья&lt;/a&gt; на странице сообщества.&lt;/p&gt;

&lt;p&gt;Официальный сайт &lt;a href=&quot;https://www.soft8soft.com/ru/verge3d&quot; rel=&quot;nofollow&quot;&gt;&lt;a href=&quot;http://www.soft8soft.com/ru/verge3d&quot; rel=&quot;nofollow&quot;&gt;www.soft8soft.com/ru/verge3d&lt;/a&gt;&lt;/a&gt;&lt;/p&gt;</description>
</item>
</channel>
</rss>
