Вычислительные шейдеры в Dagon

С переходом на OpenGL 4.3 в движке появляется возможность использовать вычислительные шейдеры. Они довольно удобно легли поверх уже существующией шейдерной системы — добавились всего два новых класса, ComputeProgram (аналог ShaderProgram) и ComputeShader, наследующий от Shader. Механизм привязки параметров целиком остается тот же самый, никаких изменений, за исключением способа привязки текстур — для этого есть метод ComputeShader.bindImageTexture. Привязываются обычные текстуры Dagon, как-то специально их готовить не нужно. Чтобы создать свой шейдер, нужно наследовать от ComputeShader, создать программу и параметры, а в bindParameters, как обычно, задать им значения:

class TestComputeShader: ComputeShader
{
   protected:
    String cs;
    ShaderParameter!Color4f _fillColor;

   public:
    Texture outputTexture;
    Color4f fillColor = Color4f(1.0f, 1.0f, 1.0f, 1.0f);

    this(Owner owner)
    {
        cs = Shader.load("data/test.comp.glsl");
        ComputeProgram p = New!ComputeProgram(cs, this);
        super(p, owner);

        _fillColor = createParameter!Color4f("fillColor");
    }

    ~this()
    {
        cs.free();
    }

    override void bindParameters(GraphicsState* state)
    {
        _fillColor = fillColor;

        if (outputTexture)
            bindImageTexture(0, outputTexture, TextureAccessMode.Write);

        super.bindParameters(state);
    }

    void run()
    {
        if (outputTexture)
            super.run(outputTexture.width, outputTexture.height);
    }
}

TestComputeShader cs = New!TestComputeShader(assetManager);
cs.outputTexture = myTexture;
cs.fillColor = Color4f(0.0f, 0.5f, 1.0f, 1.0f);
cs.run();

Пример шейдера, который заполняет текстуру «синусной плазмой» заданного цвета:

#version 430
layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in;

layout(rgba8, binding = 0) writeonly uniform image2D outputTexture;

uniform vec4 fillColor;

void main()
{
    ivec2 coord = ivec2(gl_GlobalInvocationID.xy);
    const float scale = 0.25;
    float value = 0.5f + 
        0.25f * cos(coord.x * scale) + 
        0.25f * cos(coord.y * scale);
    vec3 color = fillColor.rgb * value;
    imageStore(outputTexture, coord, vec4(color, 1.0));
}

ComputeShader.run уже ставит барьер памяти, в приложении это делать не нужно. Есть методы dispatch и barrier для запуска вручную.

Dagon 0.30.0 и 0.31.0

Выпустил подряд две версии движка. В ядро Dagon внесен фреймворк многопоточности и обмена сообщениями, о котором я подробно писал в предыдущем посте. EventManager.userEventQueue переименовано в EventManager.outboxEventQueue, EventManager.numUserEvents — в EventManager.numOutboxEvents. Также теперь рекомендуется использовать EventManager.queueEvent вместо EventManager.addUserEvent, EventManager.queueFileChangeEvent вместо EventManager.generateFileChangeEvent, EventManager.queueLogEvent вместо EventManager.asyncLog.

Заметно улучшен пакет dagon.collision, хотя он пока и далек от продакшн-уровня. Исправлены баги в модуле BVH, добавлена реализация GeomTriangle.boundingBox, а также экспериментальный алгоритм проверки столкновений GJK (dagon.collision.gjk). EPA пока не поддерживается, так что функция gjkTest не возвращает информацию о контакте — основным алгоритмом проверки столкновений остается MPR. Метод CollisionShape.supportPointGlobal теперь просто CollisionShape.supportPoint.

В Dagon 0.31.0 я продолжил улучшение пакета core. Добавлены свойства Application.path и Application.directory — соответственно, полный путь к исполняемому файлу и папка, в которой он лежит. Под Windows доступно свойство Application.hwnd для получения дескриптора окна игры. VFS теперь монтирует в качестве последнего источника данных папку, где хранится приложение, а не рабочую папку. Благодаря этому можно в командной строке запускать приложение не из текущей папки.

Экспериментальная фича: поддержка ввода с графических планшетов (пока только под Windows через Wintab). Абстрактный интерфейс InputDevice для добавления в EventManager кастомных устройств ввода. Новые типы событий EventType.PenMotion, EventType.JoystickAxisMotion, EventType.LocaleChange.

В deferred-рендер добавлена поддержка перспективных теневых карт (PSM) для конусных источников света.

Dagon 0.31.0 является последней версией, использующей OpenGL 4.0 — со следующей движок переходит на 4.3, что позволит добавить поддержку вычислительных шейдеров.

Конкурентное программирование в Dagon

Идея добавить в EventManager поддержку конкурентности/асинхронности не давала мне покоя еще со времен DGL, и вот, наконец, мне это удалось. Точнее, асинхронный слой удобно лег поверх стандартной событийной шины, и для этого не пришлось менять в ней практически ничего.

(далее…)

Обновления

Dagon 0.29.0

В очередной версии Dagon добавлены эффекты ретро-рендеринга — снаппинг вершин (SimpleRenderPass.retroVertexSnapping) и пикселизация (PresentRenderer.pixelization, PresentRenderer.pixelSize). SimpleRenderer теперь поддерживает свечение, туман и фоновые объекты. Исправлен баг с неправильным рендерингом упрощенной тени в SimpleRenderer.

В модуль dagon.graphics.texture добавлены функции конвертации текстурных форматов между различными API: dxgiFormatToGLFormat, vkFormatToGLFormat, glFormatToVkFormat.

Исправлены некоторые важные баги, в том числе интерполяция кадров в GLTFPose и GLTFBlendedPose, а также, благодаря переходу на новую версию dlib, очень неприятный баг с повреждением памяти в загрузчике glTF.

dlib 1.3.3

Багфикс-релиз, исправляющий повреждение памяти в декодере JSON из-за неправильного поведения лексера.

PN Reloaded 2.7.0

К этой версии редактора прилагается плагин PNScript, реализующий поддержку скриптов на JavaScript (на основе Node.js). Скрипты работают как текстовые фильтры, с их помощью можно реализовать форматирование, умный поиск, деобфускацию, различные алгоритмы анализа данных и т.д. Можно устанавливать NPM-пакеты, что дает практически неограниченные возможности обработки текстов. Автоматизация самого редактора (создание макросов) пока не поддерживается.

Также добавлена опция «Load ASCII files as UTF-8».

Сборку можно скачать на странице релиза.

Dagon 0.28.0

В этом году обновления Dagon выходят одно за другим — дело в том, что я решил увеличить частоту релизов, так как добавляемые улучшения мало связаны между собой и обычно не ломают обратную совместимость. Движок постепенно приближается к версии 1.0, после чего набор базовых фич будет стабилизирован, и я уже не буду вносить серьезных изменений в API.

В новом релизе добавлен оператор тональной компрессии Khronos PBR Neutral, а также новый extra-шейдер OceanShader, реализующий волны Герстнера. Исправлен баг с передачей неправильного буфера глубины в фильтр Depth of Field. Главный конфиг приложения (settings.conf) теперь поддерживает опции рендеринга — см. шаблон конфига в репозитории. Добавлено свойство PositionSync.mask для взвешенной/выборочной привязки к осям.

Шаблон проекта DUB (init-exec) теперь генерирует файлы settings.conf, input.conf и locales/en_US.lang.

До этого еще выходил небольшой патч Dagon 0.27.1, в котором я исправил баги в загрузчике KTX/KTX2 и добавил поддержку сохранения текстур в KTX2 (функция saveTextureToKTX2).

Шейдер океана

В следующей версии Dagon будет новый extra-шейдер OceanShader, реализующий волны Герстнера. Волны смещаются так, что объект воды можно двигать в плоскости XZ вместе с камерой — получается эффект бесконечного океана.

Пример:

auto oceanMesh = New!ShapePlane(150, 150, 100, assetManager);

auto waterMaterial = addMaterial();
waterMaterial.roughnessFactor = 0.0f;
waterMaterial.blendMode = Transparent;
waterMaterial.useCulling = false;
waterMaterial.textureScale = Vector2f(0.2f, 0.2f);
waterMaterial.shader = New!OceanShader(assetManager);

auto eWaterPlane = addEntity();
eWaterPlane.drawable = oceanMesh;
eWaterPlane.material = waterMaterial;
auto psyncWater = New!PositionSync(eventManager, eWaterPlane, camera);
psyncWater.mask = Vector3f(1.0f, 0.0f, 1.0f);

Dagon 0.27.0

Выпустил новую версию движка. В Dagon 0.27 наконец-то появились тени от позиционных источников света (то есть, для всех, кроме Sun) — это реализовано техникой двойного параболоида, которая значительно эффективнее классического подхода с теневой кубической картой. Чтобы включить тень, ничего особенного делать не надо, просто light.shadowEnabled = true;.

Также добавил префильтрацию кубических карт — свертку с использованием GGX BRDF под различные значения шероховатости. Чтобы сконвертировать равнопромежуточную карту в кубическую, а затем отфильтровать, нужно сделать так:

uint resolution = 1024;
Texture cubemap = generateCubemap(resolution, aEnvmap.texture, null);
Texture prefilteredCubemap = prefilterCubemap(resolution, cubemap, assetManager);
Delete(cubemap);

Появилась поддержка анизотропной фильтрации текстур (если поддерживается расширение GL_EXT_texture_filter_anisotropic). Включается следующим образом:

texture.useAnisotropicFiltering = true;
texture.anisotropy = texture.maxAnisotropy;

Добавлена поддержка поля extras для большинства объектов в загрузчике glTF (кроме материалов и текстур).

Еще одна новая фича — поддержка локализации приложений (dagon.core.i18n). Локали — файлы *.lang — загружаются из папки locales. Например, чтобы добавить русскую локаль, нужно добавить файл locales/ru_RU.lang.

Синтаксис файлов точно такой же, как у конфигов. Например, можно сделать так:

hello_world: "Привет, мир!";

В приложении:

string text = application.translate("hello_world");

Приложение сначала пытается загрузить locales/en_US.lang, затем локализацию, выбранную в зависимости от текущего системного языка и региона, перезаписывая дефолтные английские значения. Либо пользователь может явно указать нужную ему локаль в settings.conf:

locale: "ru_RU";

Тени от позиционных источников света

Фича, которую я запланировал на Trello очень давно и сумел реализовал только сейчас. Теперь в Dagon любой позиционный источник света (AreaSphere, AreaTube, Spot) может иметь тени — это реализовано техникой двойного параболоида (dual paraboloid shadow mapping), где геометрия проецируется на две текстуры глубины, с двух сторон относительно источника света.

Dagon 0.26.0

На днях вышла новая версия движка! Как я уже писал ранее, в этой версии все загрузчики ресурсов в Dagon используют единую виртуальную файловую систему — Application.vfs. В связи с этим класс VirtualFileSystem был значительно актуализирован и расширен. В частности, теперь движок из коробки поддерживает загрузку ресурсов из папки с данными (C:\Users\AppData\Roaming\<appDataFolder> под Windows, где <appDataFolder> — кастомное имя папки, которое указывается при создании приложения Dagon). Кроме того, главный конфиг приложения (ранее Game.config) переехал в класс Application. Появился новый метод Application.showConsoleWindow для переключения видимости окна консоли под Windows.

В EventManager наконец-то доведена до ума проверка однократного нажатия и отпускания клавиш/кнопок мыши/кнопок контроллера (EventManager.keyDown, EventManager.keyUp и т.д.). Эта функциональность добавляет небольшой оверхед, поэтому она по умолчанию отключена — нужно ее включать свойством EventManager.trackUpDownState. Как следствие, InputManager.getButtonUp и InputManager.getButtonDown теперь также работают корректно. Добавлено свойство EventManager.application, чтобы можно было получить доступ к объекту приложения из менеджера событий.

Добавлен новый аппаратно-ускоряемый генератор кубических карт — функция dagon.graphics.texproc.generateCubemap, эффективная замена методу Texture.createFromEquirectangularMap. Эта функция конвертирует равнопромежуточные карты окружения в кубические, что позволяет избежать некоторых неприятных артефактов первых. Благодаря переносу на GPU, теперь эта процедура выполняется очень быстро. Данный компонент будет развиваться и дальше — в следующих версиях будет поддержка префильтрации, о которой я писал в предыдущем посте.

Предрассчитанная таблица BRDF LUT теперь поставляется вместе с движком в папке data/__internal/textures/brdf.dds. Эта текстура загружается автоматически и доступна как DeferredRenderer.brdf. В случае использования отложенного рендера, рекомендуется активировать ее следующим образом:

environment.ambientBRDF = game.deferredRenderer.brdf;

Это нужно для физически обоснованного расчета зеркальных отражений от карты окружения. Таблица хранит значения функции Смита для различных сочетаний шероховатости и углов падения.

GLTFPose и GLTFBlendedPose теперь обновляют матрицы трансформации объектов Entity, ассоциированных с костями glTF. Это позволяет, например, использовать Entity.positionAbsolute для получения позиции кости в мировом пространстве.

Добавлен новый модуль dagon.extra.verlet с реализацией position-based динамики для симуляции веревок и цепей.

Префильтрация кубических карт

В дополнение к простому конвертеру из равнопромежуточных карт окружения в кубические карты, в Dagon появилась поддержка предварительной фильтрации (prefiltering) — то есть, свертки кубических карт с использованием GGX BRDF под различные значения шероховатости. Результаты свертки сохраняются в mip-уровнях. Этот процесс является одной из ключевых оптимизаций PBR, поскольку избавляет от необходимости вычислять интеграл облученности в реальном времени. Префильтрация осуществляется очень быстро на GPU, поэтому ее, в принципе, можно делать каждый раз при запуске приложения (но лучше, конечно, однократно с кэшированием в файл — скоро добавлю и такую возможность). Преимуществом является то, что теперь для этого необязательно использовать сторонние утилиты типа IBLBaker — движок все делает сам.

Реализация основана на классическом методе (выборка по последовательности ван дер Корпута-Хаммерсли), описанном в статье «Real Shading in Unreal Engine 4» (Брайан Карис, Epic Games, SIGGRAPH 2013).