Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
Шейдеры чтения экрана
Введение
Часто требуется создать шейдер, считывающий данные с того же экрана, на который он выводит данные. 3D API, такие как OpenGL или DirectX, значительно затрудняют это из-за внутренних аппаратных ограничений. Графические процессоры работают исключительно параллельно, поэтому чтение и запись вызывают всевозможные проблемы с кэшем и когерентностью. В результате даже самое современное оборудование не поддерживает эту функцию должным образом.
Обходной путь — скопировать экран или его часть во вторичный буфер, а затем считывать оттуда данные во время рисования. Godot предоставляет несколько инструментов, упрощающих этот процесс.
Текстура экрана
Godot Язык шейдеров имеет специальную текстуру для доступа к уже отрисованному содержимому экрана. Она используется с указанием подсказки при объявлении юниформы sampler2D: hint_screen_texture. Специальный встроенный вариативный SCREEN_UV может использоваться для получения UV-координат относительно экрана для текущего фрагмента. В результате этот фрагментный шейдер canvas_item создаёт невидимый объект, поскольку отображает только то, что находится за ним:
shader_type canvas_item;
uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest;
void fragment() {
COLOR = textureLod(screen_texture, SCREEN_UV, 0.0);
}
Здесь используется textureLod, поскольку мы хотим читать только из нижней MIP-карты. Если вы хотите читать из размытой версии текстуры, можно увеличить третий аргумент до textureLod и изменить подсказку filter_nearest на filter_nearest_mipmap (или любой другой фильтр с поддержкой MIP-карт). При использовании фильтра с MIP-картами Godot автоматически рассчитает размытую текстуру.
Предупреждение
Если режим фильтра не изменен на режим фильтра, содержащий mipmap в своем названии, textureLod с параметром LOD больше 0.0 будет иметь тот же вид, что и с параметром LOD 0.0.
Пример текстуры экрана
Текстуру экрана можно использовать для множества целей. Существует специальная демоверсия Screen Space Shaders, которую вы можете скачать, чтобы посмотреть и изучить. Один из примеров — простой шейдер для настройки яркости, контрастности и насыщенности:
shader_type canvas_item;
uniform sampler2D screen_texture : hint_screen_texture, repeat_disable, filter_nearest;
uniform float brightness = 1.0;
uniform float contrast = 1.0;
uniform float saturation = 1.0;
void fragment() {
vec3 c = textureLod(screen_texture, SCREEN_UV, 0.0).rgb;
c.rgb = mix(vec3(0.0), c.rgb, brightness);
c.rgb = mix(vec3(0.5), c.rgb, contrast);
c.rgb = mix(vec3(dot(vec3(1.0), c.rgb) * 0.33333), c.rgb, saturation);
COLOR.rgb = c;
}
За кулисами сцен
Хоть это и кажется магией, это не так. В 2D, когда hint_screen_texture впервые обнаруживается в узле, готовящемся к отрисовке, Godot делает полноэкранное копирование во вторичный буфер. Последующие узлы, использующие его в шейдерах, не будут копировать экран, поскольку это становится неэффективным. В 3D экран копируется после прохода непрозрачной геометрии, но до прохода прозрачной геометрии, поэтому прозрачные объекты не будут захвачены в текстуре экрана.
В результате в 2D, если шейдеры, использующие hint_screen_texture, перекрываются, второй не будет использовать результат первого, что приведет к неожиданным визуальным эффектам:
На изображении выше вторая сфера (справа вверху) использует тот же источник для текстуры экрана, что и первая ниже, поэтому первая "исчезает" или не видна.
В 2D это можно исправить с помощью узла BackBufferCopy, который можно создать между обеими сферами. BackBufferCopy может работать, указывая как область экрана, так и весь экран:
При правильном копировании обратного буфера две сферы смешиваются правильно:
Предупреждение
В 3D материалы, использующие hint_screen_texture, сами по себе считаются прозрачными и не будут отображаться в результирующей текстуре экрана других материалов. Если вы планируете создать сцену, использующую материал с hint_screen_texture, вам потребуется узел BackBufferCopy.
В 3D-графике решение этой проблемы менее гибкое, поскольку экранная текстура захватывается только один раз. Будьте осторожны при использовании экранной текстуры в 3D, так как она не захватывает прозрачные объекты и может захватывать некоторые непрозрачные объекты, находящиеся перед объектом, использующим экранную текстуру.
Вы можете воспроизвести логику заднего буфера в 3D, создав Viewport с камерой в том же положении, что и ваш объект, а затем использовать текстуру Viewport's вместо текстуры экрана.
Логика обратного буфера
Итак, чтобы было понятнее, вот как работает логика копирования обратного буфера в 2D в Godot:
Если узел использует
hint_screen_texture, весь экран копируется в дальний буфер перед отрисовкой этого узла. Это происходит только в первый раз; последующие узлы не вызывают этого.Если узел BackBufferCopy был обработан до ситуации, описанной в пункте выше (даже если
hint_screen_textureне использовался), описанное в пункте выше поведение не происходит. Другими словами, автоматическое копирование всего экрана происходит только в том случае, еслиhint_screen_textureиспользуется в узле впервые и ранее в дереве не было найдено ни одного узла BackBufferCopy (не отключённого).BackBufferCopy может копировать как весь экран, так и его область. Если BackBufferCopy копирует только область (а не весь экран), и ваш шейдер использует пиксели, не входящие в копируемую область, результат чтения будет неопределённым (скорее всего, это мусор из предыдущих кадров). Другими словами, BackBufferCopy можно использовать для обратного копирования области экрана, а затем для чтения текстуры экрана из другой области. Избегайте такого поведения!
Текстура глубины
Для 3D-шейдеров также возможен доступ к буферу глубины экрана. Для этого используется подсказка hint_metre_texture. Эта текстура нелинейна и должна быть преобразована с помощью матрицы обратной проекции.
Следующий код извлекает 3D-позицию под отрисовываемым пикселем:
uniform sampler2D depth_texture : hint_depth_texture, repeat_disable, filter_nearest;
void fragment() {
float depth = textureLod(depth_texture, SCREEN_UV, 0.0).r;
vec4 upos = INV_PROJECTION_MATRIX * vec4(SCREEN_UV * 2.0 - 1.0, depth, 1.0);
vec3 pixel_position = upos.xyz / upos.w;
}
Текстура нормальной шероховатости
Примечание
Текстура с нормальной шероховатостью поддерживается только в методе рендеринга Forward+, но не в Mobile или Compatibility.
Аналогично, текстура нормалей и шероховатости может использоваться для считывания нормалей и шероховатости объектов, визуализируемых в предварительном проходе глубины. Нормаль хранится в каналах .xyz (соответствующих диапазону 0-1), а шероховатость — в канале .w.
uniform sampler2D normal_roughness_texture : hint_normal_roughness_texture, repeat_disable, filter_nearest;
void fragment() {
float screen_roughness = texture(normal_roughness_texture, SCREEN_UV).w;
vec3 screen_normal = texture(normal_roughness_texture, SCREEN_UV).xyz;
screen_normal = screen_normal * 2.0 - 1.0;
Переосмысление текстур экрана
Подсказки текстуры экрана (hint_screen_texture, hint_metre_texture и hint_normal_roughness_texture) можно использовать с несколькими униформами. Например, может потребоваться несколько раз прочитать текстуру с разными флагами повторения или фильтра.
В следующем примере показан шейдер, который считывает нормаль экранного пространства с помощью линейной фильтрации, но считывает шероховатость экранного пространства с помощью фильтрации ближайшего соседа.
uniform sampler2D normal_roughness_texture : hint_normal_roughness_texture, repeat_disable, filter_nearest;
uniform sampler2D normal_roughness_texture2 : hint_normal_roughness_texture, repeat_enable, filter_linear;
void fragment() {
float screen_roughness = texture(normal_roughness_texture, SCREEN_UV).w;
vec3 screen_normal = texture(normal_roughness_texture2, SCREEN_UV).xyz;
screen_normal = screen_normal * 2.0 - 1.0;