Экранные шейдеры: чтение кадра и цепочка эффектов
Обесцветить показанное изображение и правильно передать результат следующему проходу.
Запустить пример
Скачать проект. Нужны Reckfell 1.0.0 с C# SDK и Forward+/Vulkan. Откройте project.reckfell, соберите C# и запустите Main.rscene. Для исходной комнаты выберите Preview=Original, для обесцвечивания — Grayscale, для диагностической цепочки — Chained. Во время выполнения вызывайте SetPreview(mode) после _Ready.
Комната рендерится в отдельном SubViewport и показывается через TextureRect. Затем canvas_item shader читает уже показанное изображение. Создание источника и полный C#-контроллер — в руководстве по живой текстуре.
Исходное изображение:
Та же текстура, обесцвеченная экранным shader:
Читать координаты экрана
Файл ScreenTone.reckfellshader:
shader_type canvas_item;
render_mode unshaded;
uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_linear;
uniform float desaturate : hint_range(0.0, 1.0) = 0.0;
void fragment() {
vec4 source = textureLod(screen_texture, SCREEN_UV, 0.0);
float gray = dot(source.rgb, vec3(0.2126, 0.7152, 0.0722));
COLOR = vec4(mix(source.rgb, vec3(gray), desaturate), source.a);
}hint_screen_texture привязывает sampler к копии экрана текущего viewport. SCREEN_UV задаёт координаты этого экрана; UV относится к геометрии самого ColorRect. При перемещении или уменьшении прямоугольника SCREEN_UV сохраняет совпадение с изображением позади него.
repeat_disable отключает повтор текстуры за её краями, filter_linear задаёт линейную выборку. textureLod(..., 0.0) здесь читает базовый уровень. Пример не использует mipmap blur; увеличение LOD само по себе без подходящего фильтра не является законченным решением размытия.
При desaturate=0 shader возвращает исходный цвет. При 1 все RGB-каналы равны взвешенному серому значению. Alpha источника сохраняется. ColorRect должен быть нарисован после TextureRect и покрывать область эффекта; белый ColorRect без назначенного ShaderMaterial просто закроет изображение.
Соединить два прохода
Второй shader ScreenInvert.reckfellshader:
shader_type canvas_item;
render_mode unshaded;
uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_linear;
void fragment() {
vec4 source = textureLod(screen_texture, SCREEN_UV, 0.0);
COLOR = vec4(vec3(1.0) - source.rgb, source.a);
}При первом 2D-элементе с hint_screen_texture движок получает копию экрана, если её ещё не подготовил BackBufferCopy. Следующий screen-reading shader обычно использует эту же копию. Он не получает результат предыдущего эффекта автоматически.
Порядок в примере при одинаковом z_index:
- Display показывает живую текстуру комнаты.
- TonePass обесцвечивает её.
- Copy с CopyMode=Viewport копирует уже обесцвеченный кадр.
- InvertPass читает обновлённую копию и инвертирует её.
Если отключить Copy, инверсия читает исходную цветную комнату, а не серый результат TonePass. Это проверено по пикселям: второй проход снова содержит цвет. В режиме Chained копирование включено, поэтому результат остаётся серым.
Диагностическая инверсия после обесцвечивания; не цветокоррекция готовой игры:
Копировать только нужную область
CopyMode=Rect использует Copy.Rect в локальных координатах BackBufferCopy. В проверенном случае Copy находится в начале координат, Rect=(0, 0, 1280, 1440), а второй ColorRect рисует только левую половину кадра. Внутри неё он видит результат TonePass; справа остаётся первый проход.
Не выбирайте texel вне реально скопированной области: там нет гарантии актуального результата. Если shader смещает координаты выборки, область копирования должна учитывать это смещение. Для начала используйте Viewport и сужайте область после получения правильного результата.
Диагностика
| Симптом | Проверка |
|---|---|
| эффект не виден | canvas_item, назначенный ShaderMaterial, размер и порядок ColorRect |
| части изображения повторяются или съезжают | SCREEN_UV вместо локальных UV, repeat_disable |
| второй эффект теряет первый | BackBufferCopy между проходами и CopyMode не Disabled |
| после добавления UI цепочка изменилась | фактический порядок рисования, z_index и CanvasLayer |
| по краям эффекта старые пиксели | Rect слишком мал для области выборки |
| 3D shader ведёт себя иначе | этот пример выполняет 2D-обработку уже показанной текстуры, а не spatial screen-reading |
Проверено в текущем Forward+/Vulkan: живая сцена, нейтральный проход, обесцвечивание, SCREEN_UV на половине экрана, повторное использование копии, полный и прямоугольный BackBufferCopy. Глубина/нормали 3D, mipmap blur, Compatibility и производительность этой цепочки не проверены. Для spatial-эффектов нужен отдельный разбор 3D-постобработки.