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, оскільки ми хочемо читати лише з нижньої mipmap. Якщо замість цього ви хочете читати з розмитої версії текстури, ви можете збільшити третій аргумент до textureLod і змінити підказку filter_nearest на filter_nearest_mipmap (або будь-який інший фільтр з увімкненими mipmaps). Якщо використовується фільтр з mip-картами, Godot автоматично обчислить розмиту текстуру для вас.
Попередження
Якщо режим фільтра не змінено на режим фільтра, який містить mipmap у своїй назві, textureLod з параметром LOD, більшим за 0.0, матиме такий самий вигляд, як і 0.0 ` Параметр LOD.
Приклад текстури екрана
Текстуру екрана можна використовувати для багатьох речей. Існує спеціальна демонстрація для 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 замість текстура екрану.
Логіка зворотного буфера
Отже, щоб було зрозуміліше, ось як працює логіка копіювання зворотного буфера в 2D у Godot:
Якщо вузол використовує
hint_screen_texture, весь екран копіюється до заднього буфера перед малюванням цього вузла. Це відбувається тільки вперше; наступні вузли не запускають це.Якщо вузол BackBufferCopy було оброблено до ситуації, описаної вище (навіть якщо
hint_screen_textureне використовувався), поведінка, описана в пункті вище, не відбувається. Іншими словами, автоматичне копіювання всього екрана відбувається, лише якщоhint_screen_textureвикористовується у вузлі вперше, і жоден вузол BackBufferCopy (не вимкнений) не був знайдений раніше в порядку дерева.BackBufferCopy може копіювати весь екран або його область. Якщо встановлено лише область (а не весь екран), а ваш шейдер використовує пікселі не в скопійованій області, результат цього читання буде невизначеним (швидше за все, сміття з попередніх кадрів). Іншими словами, можна використовувати BackBufferCopy, щоб скопіювати назад область екрана, а потім зчитати текстуру екрана з іншої області. Уникайте такої поведінки!
Глибина текстури
Для 3D-шейдерів також можна отримати доступ до буфера глибини екрана. Для цього використовується підказка hint_depth_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+, але не в мобільному або сумісності.
Подібним чином текстуру нормальної шорсткості можна використовувати для читання нормалей і шорсткості об’єктів, відтворених у попередньому проході глибини. Нормаль зберігається в каналах .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_depth_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;