Шейдеры

Шейдеры — это программы, используемые для реализации графических эффектов, выполняющихся на GPU. SS14 использует пиксельные (фрагментные) шейдеры для реализации поэлементных эффектов спрайтов, включая невидимость; и оверлейных эффектов, включая космические наркотики, опьянение, слепоту и искривление пространства сингулярностью.

Определение шейдера

У каждого шейдера есть YAML-прототип, хранящийся в Resources/Prototypes/Shaders. У всех шейдеров есть поля:

  • type: должно быть shader
  • id: уникальный ID прототипа (строка), идентифицирующий этот шейдер
  • kind: либо canvas, либо shader (см. ниже)

Шейдеры canvas

Это шейдер с предустановленными опциями, похожий на CanvasItemMaterial из Godot. У этого шейдера есть два обязательных свойства:

  • blend_mode: Способ отрисовки объекта поверх сцены. Они такие же, как эквивалент для Godot. Разве что названия немного отличаются. Возможные значения: mix, add, subtract, multiply и premultiplied_alpha
  • light_mode: Способ взаимодействия объекта со светом. Опять же эквивалентен варианту Godot, со значениями normal, unshaded и light_only.

Например, шейдер unshaded, который используется для отрисовки освещённой части компьютерных дисплеев и индикаторов путём маскирования операций освещения, определяется так:

- type: shader
  id: unshaded
  kind: canvas
  light_mode: unshaded

Шейдер canvas на самом деле просто исходный шейдер по умолчанию (см. /Shaders/Internal/default-sprite.swsl) с применёнными опциями освещения, смешивания и трафарета.

Шейдеры source

Это пользовательские шейдеры, написанные на Space Wizard Shader Language (SWSL). У шейдеров source есть одно обязательное свойство:

  • path: Путь к исходному файлу шейдера SWSL относительно каталога Resources/.

Например:

- type: shader
  id: GreyscaleFullscreen
  kind: source
  path: "/Textures/Shaders/greyscale_fullscreen.swsl"

Пользовательские шейдеры обычно хранятся в файлах .swsl в Resources/Textures/Shaders.

SWSL

Space Wizard Shader Language (SWSL) основан на языке шейдеров Godot. Большинство различий совместимости между Godot и SWSL обрабатываются автоматически, кроме следующих различий:

  • Вы ОБЯЗАНЫ убедиться, что все числовые типы (например, float, vec3) имеют квалификатор точности highp или lowp. См. эту статью о том, почему.

  • Избегайте имён переменных, которые являются зарезервированными словами в распространённых спецификациях шейдеров. Ваш компьютер может их проигнорировать, но на других машинах они сломаются. Смотрите раздел с ключевыми словами (3.8) здесь

Поскольку это 2D-игра, используется только фрагментный шейдер, т. е. шейдер состоит как минимум из:

void fragment() {
    COLOR = vec4(r, g, b, a);
}

Доступные переменные (фрагментные шейдеры)

ИмяТипОписание
FRAGCOORDhighp vec4Координаты внутри фрагмента.
COLORlowp vec4Итоговый цвет пикселя. (Думайте об этом как о возвращаемом значении фрагментного шейдера.)
lightMapsampler2DКарта освещения текущего фрагмента (применяется автоматически base-default.frag)
modulatehighp vec4Цвет отрисовки (применяется автоматически base-default.frag)
SCREEN_PIXEL_SIZEhighp vec2Размер одного пикселя в локальных единицах.
TIMEhighp floatКоличество секунд с момента запуска игры.

Параметры трафаретного теста

Шейдеры поддерживают трафаретные операции. Это продвинутая функция рендеринга, которая может быть полезна для некоторых вещей, если вы знаете, что делаете. Эта функция близко имитирует параметры трафарета, как они представлены в OpenGL (и, насколько я могу судить, в Vulkan тоже). См. вики OpenGL, если нужна справка.

Параметры трафарета определяются в отдельном объекте stencil. Например:

- type: shader
  id: stencilDraw
  kind: canvas
  stencil:
    ref: 1
    op: Keep
    func: NotEqual

Действительно, параметры трафарета не зависят от kind шейдера.

Возможные варианты:

  • ref: Опорное значение для сравнения / записи, передаваемое вторым параметром glStencilFunc.
    • По умолчанию 0.
  • op: Операция, применяемая к буферу трафарета, если трафаретный тест пройден. Задать можно только операцию при успехе (третий параметр glStencilOp).
    • Варианты: Keep, Zero, Replace, IncrementClamp, IncrementWrap, DecrementClamp, DecrementWrap, Invert
    • По умолчанию Keep.
  • func: Функция сравнения, используемая для проверки прохождения теста. (первый параметр glStencilFunc).
    • Варианты: Always, Never, Less, LessOrEqual, Greater, GreaterOrEqual, NotEqual, Equal.
    • По умолчанию Always.
  • readMask: Маска, используемая при чтении из буфера трафарета (третий параметр glStencilFunc).
    • По умолчанию все 1.
  • writeMask: Маска, используемая при записи в буфер трафарета (параметр glStencilMask).
    • По умолчанию все 1.

Оверлеи

Оверлей применяет шейдер ко всему экрану или вьюпорту (в отличие от отдельных спрайтов). Эффекты наркотиков, слепота и искривление пространства сингулярностью — примеры оверлеев (написанных на C#), которые загружают прототипы шейдеров (определённые в YAML), которые могут загружать пользовательские шейдерные эффекты (написанные на SWSL).

Оверлеи наследуют Overlay, например:

    public sealed class BlindOverlay : Overlay
    {
        [Dependency] private readonly IPrototypeManager _prototypeManager = default!;

        // Установите true, чтобы получить ScreenTexture. Иначе оно null.
        public override bool RequestScreenTexture => true;
        
        // Это нужно установить в соответствующий слой оверлея.
        public override OverlaySpace Space => OverlaySpace.WorldSpace;
        
        // Сохраняем ссылки на шейдеры.
        private readonly ShaderInstance _greyscaleShader;
        private readonly ShaderInstance _circleMaskShader;

        public BlindOverlay()
        {
            IoCManager.InjectDependencies(this);
            // Загружаем шейдеры из прототипов
            _greyscaleShader = _prototypeManager.Index<ShaderPrototype>("GreyscaleFullscreen").InstanceUnique();
            _circleMaskShader = _prototypeManager.Index<ShaderPrototype>("CircleMask").InstanceUnique();
        }
        
        protected override bool BeforeDraw(in OverlayDrawArgs args)
        {
            // Если это вернёт true, этот шейдер будет отрисован. Если этот
            // метод не переопределён, по умолчанию он возвращает true,
            // то есть шейдер всегда активен для всех всё время.
        }
        
        protected override void Draw(in OverlayDrawArgs args)
        {
            // Если вашему шейдеру нужны входные данные (пиксели, сейчас находящиеся на экране),
            // вы должны проверить, что оно не null, и передать его в ваш шейдер.
            if (ScreenTexture == null)
                return;
                
            _greyscaleShader?.SetParameter("SCREEN_TEXTURE", ScreenTexture);
            
            var handle = args.WorldHandle;
            var viewport = args.WorldBounds;
            // рисуем шейдер оттенков серого
            handle.UseShader(_greyscaleShader);
            handle.DrawRect(viewport, Color.White);
            // рисуем шейдер круговой маски
            handle.UseShader(_circleMaskShader);
            handle.DrawRect(viewport, Color.White);
            // прекращаем использовать этот шейдер
            handle.UseShader(null);
        }
    }

Шейдеру нужна область для отрисовки. Здесь это белый прямоугольник, равный WorldBounds. Учтите, что WorldAABB не скорректирован с учётом поворота и, вероятно, сломает многие шейдеры.

Наконец, чтобы оверлеи действительно отрисовывались, их нужно добавить в менеджер оверлеев:

[Dependency] private readonly IOverlayManager _overlayMan = default!;
_overlayMan.AddOverlay(your_overlay_here);
_overlayMan.RemoveOverlay(your_overlay_here);

Если этот оверлей должен быть всегда активным, добавьте его в PostInit() в Content.Client/Entry/EntryPoint.cs.

Тестирование и отладка

Warning

Тестируйте свои шейдеры как с режимом совместимости, так и без него. Включить режим совместимости можно, запустив клиент с аргументом --cvar display.compat=true.

Info

Вы можете использовать команду /rldshader, чтобы перезагрузить шейдеры .swsl без перезапуска игры. Это значит, что вы часто можете использовать цветовые выводы для интерактивной отладки шейдеров.

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

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

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

glslang.

Внешние инструменты

  • renderdoc - отличный инструмент для отладки рендеринга и шейдеров.

Subpages