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...
Розширена постобробка
Вступ
Цей підручник описує розширений метод постобробки в Godot. Зокрема, буде пояснено, як написати шейдер постобробки, який використовує буфер глибини. Ви вже повинні бути знайомі з постобробкою загалом і, зокрема, з методами, викладеними в custom post-processing tutorial.
Повноекранний квадроцикл
Один із способів створення користувацьких ефектів постобробки — використання вікна перегляду. Однак є два основні недоліки використання Viewport:
Немає доступу до буфера глибини
Ефект шейдера постобробки не видно в редакторі
Щоб обійти обмеження на використання буфера глибини, використовуйте MeshInstance3D з примітивом QuadMesh. Це дозволяє нам використовувати шейдер і отримати доступ до текстури глибини сцени. Потім скористайтеся вершинним шейдером, щоб квадроцикл постійно покривав екран, щоб ефект постобробки застосовувався постійно, включно з редактором.
Спочатку створіть новий MeshInstance3D і встановіть його сітку на QuadMesh. Це створює чотирикутник із центром у позиції (0, 0, 0) із шириною та висотою 1. Встановіть ширину та висоту на 2 і ввімкніть Flip Faces. Прямо зараз квадроцикл займає позицію у світовому просторі на початку координат. Однак ми хочемо, щоб він рухався разом з камерою, щоб він завжди покривав весь екран. Щоб зробити це, ми обійдемо перетворення координат, які переміщують позиції вершин через простори різницевих координат і оброблятимемо вершини так, ніби вони вже знаходяться в просторі відсікання.
Вершиний шейдер очікує виводу координат у просторі відсікання, які є координатами в діапазоні від -1 у лівій і нижній частині екрана до 1 у верхній і правій частині екрана. Ось чому QuadMesh має мати висоту та ширину 2. Godot виконує перетворення від моделі до простору огляду до простору кліпу за кадром, тому нам потрібно звести нанівець ефект перетворень Godot. Ми робимо це, встановлюючи вбудований POSITION у бажане положення. POSITION обходить вбудовані перетворення та встановлює позицію вершини в просторі кліпу безпосередньо.
shader_type spatial;
// Prevent the quad from being affected by lighting and fog. This also improves performance.
render_mode unshaded, fog_disabled;
void vertex() {
POSITION = vec4(VERTEX.xy, 1.0, 1.0);
}
Примітка
У версіях Godot, раніших за 4.3, цей код рекомендував використовувати POSITION = vec4(VERTEX, 1.0);, який неявно припускав, що простір кліпу біля площини був 0.0. Тепер цей код неправильний і не працюватиме у версіях 4.3+, оскільки тепер ми використовуємо буфер глибини "reversed-z", де ближня площина знаходиться на 1.0.
Навіть із цим вершинним шейдером квадроцикл продовжує зникати. Це пов’язано з вибракуванням усеченої точки, яке виконується на ЦП. Frustum cutling використовує матрицю камери та AABB сіток, щоб визначити, чи буде сітка видимою перед передачею її до графічного процесора. ЦП не знає, що ми робимо з вершинами, тому він припускає, що вказані координати стосуються світових позицій, а не позицій у просторі відсіку, що призводить до того, що Godot відбирає квадроцикл, коли ми повертаємося від центру сцени. Щоб уберегти квадроцикл від вибракування, є кілька варіантів:
Додайте QuadMesh до камери як дочірнього елемента, щоб камера завжди була спрямована на нього
Встановіть властивість Geometry
extra_cull_marginякомога більше в QuadMesh
Другий варіант гарантує, що квадроцикл буде видно в редакторі, тоді як перший варіант гарантує, що він все одно буде видимим, навіть якщо камера переміститься за межі відбракування. Ви також можете використовувати обидва варіанти.
Глибина текстури
Щоб прочитати текстуру глибини, нам спочатку потрібно створити однорідну текстуру, налаштовану на буфер глибини за допомогою hint_depth_texture.
uniform sampler2D depth_texture : hint_depth_texture;
Після визначення текстуру глибини можна прочитати за допомогою функції texture().
float depth = texture(depth_texture, SCREEN_UV).x;
Примітка
Подібно до доступу до текстури екрана, доступ до текстури глибини можливий лише під час читання з поточного вікна перегляду. До текстури глибини неможливо отримати доступ з іншого вікна перегляду, яке ви відтворили.
Значення, які повертає depth_texture, знаходяться між 1.0 і 0.0 (відповідає ближній і дальній площинам, відповідно, через використання буфера глибини "reverse-z") і є нелінійними. При відображенні глибини безпосередньо з depth_texture все виглядатиме майже чорним, якщо воно не буде дуже близько через цю нелінійність. Щоб узгодити значення глибини зі світовими або модельними координатами, нам потрібно лінеаризувати значення. Коли ми застосовуємо матрицю проекції до позиції вершини, значення z стає нелінійним, тому, щоб лінеаризувати його, ми множимо його на обернену матрицю проекції, яка в Godot доступна за допомогою змінної INV_PROJECTION_MATRIX.
По-перше, візьміть координати екранного простору та перетворіть їх у нормалізовані координати пристрою (NDC). NDC виконується від -1.0 до 1.0 у напрямках x і y та від 0.0 до 1.0 у напрямку z під час використання бекенд Vulkan. Реконструюйте NDC, використовуючи SCREEN_UV для осей x і y, а також значення глибини z.
void fragment() {
float depth = texture(depth_texture, SCREEN_UV).x;
vec3 ndc = vec3(SCREEN_UV * 2.0 - 1.0, depth);
}
Примітка
Цей підручник передбачає використання засобів візуалізації Forward+ або Mobile, які використовують Vulkan NDC із Z-діапазоном [0.0, 1.0]. На відміну від цього, рендерер сумісності використовує OpenGL NDC із Z-діапазоном [-1.0, 1.0]. Для рендерера сумісності замініть обчислення NDC цим:
vec3 ndc = vec3(SCREEN_UV, depth) * 2.0 - 1.0;
Ви також можете використовувати вбудовані визначення CURRENT_RENDERER і RENDERER_COMPATIBILITY для шейдера, який працюватиме в усіх засобах візуалізації:
#if CURRENT_RENDERER == RENDERER_COMPATIBILITY
vec3 ndc = vec3(SCREEN_UV, depth) * 2.0 - 1.0;
#else
vec3 ndc = vec3(SCREEN_UV * 2.0 - 1.0, depth);
#endif
Перетворіть NDC на оглядовий простір, помноживши NDC на INV_PROJECTION_MATRIX. Пам’ятайте, що простір перегляду визначає положення відносно камери, тому значення z дасть нам відстань до точки.
void fragment() {
...
vec4 view = INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
view.xyz /= view.w;
float linear_depth = -view.z;
}
Оскільки камера спрямована в негативному напрямку z, позиція матиме від’ємне значення z. Щоб отримати придатне значення глибини, ми маємо заперечити view.z.
Світову позицію можна побудувати з буфера глибини за допомогою наступного коду, використовуючи INV_VIEW_MATRIX для перетворення позиції з простору перегляду у світовий простір.
void fragment() {
...
vec4 world = INV_VIEW_MATRIX * INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
vec3 world_position = world.xyz / world.w;
}
Приклад шейдера
Як тільки ми додамо рядок для виведення в ALBEDO, ми отримаємо повний шейдер, який виглядає приблизно так. Цей шейдер дозволяє візуалізувати лінійну глибину або координати світового простору, залежно від того, який рядок закоментовано.
shader_type spatial;
// Prevent the quad from being affected by lighting and fog. This also improves performance.
render_mode unshaded, fog_disabled;
uniform sampler2D depth_texture : hint_depth_texture;
void vertex() {
POSITION = vec4(VERTEX.xy, 1.0, 1.0);
}
void fragment() {
float depth = texture(depth_texture, SCREEN_UV).x;
vec3 ndc = vec3(SCREEN_UV * 2.0 - 1.0, depth);
vec4 view = INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
view.xyz /= view.w;
float linear_depth = -view.z;
vec4 world = INV_VIEW_MATRIX * INV_PROJECTION_MATRIX * vec4(ndc, 1.0);
vec3 world_position = world.xyz / world.w;
// Visualize linear depth
ALBEDO.rgb = vec3(fract(linear_depth));
// Visualize world coordinates
//ALBEDO.rgb = fract(world_position).xyz;
}
Оптимізація
Ви можете отримати вигоду від використання одного великого трикутника, а не використання повноекранного чотирикутника. Причину цього пояснюється тут. Однак переваги досить невеликі й корисні лише під час запуску особливо складних фрагментних шейдерів.
Встановіть Mesh у MeshInstance3D на ArrayMesh. ArrayMesh — це інструмент, який дозволяє легко побудувати сітку з масивів для вершин, нормалей, кольорів тощо.
Тепер додайте скрипт до MeshInstance3D і використовуйте такий код:
extends MeshInstance3D
func _ready():
# Create a single triangle out of vertices:
var verts = PackedVector3Array()
verts.append(Vector3(-1.0, -1.0, 0.0))
verts.append(Vector3(3.0, -1.0, 0.0))
verts.append(Vector3(-1.0, 3.0, 0.0))
# Create an array of arrays.
# This could contain normals, colors, UVs, etc.
var mesh_array = []
mesh_array.resize(Mesh.ARRAY_MAX) #required size for ArrayMesh Array
mesh_array[Mesh.ARRAY_VERTEX] = verts #position of vertex array in ArrayMesh Array
# Create mesh from mesh_array:
mesh.add_surface_from_arrays(Mesh.PRIMITIVE_TRIANGLES, mesh_array)
Примітка
Трикутник задається в нормалізованих координатах пристрою. Нагадаємо, NDC працює від -1.0 до 1.0 в обох напрямках x і y. Це робить екран 2 одиниці шириною та 2 одиниці висотою. Щоб покрити весь екран одним трикутником, використовуйте трикутник шириною 4 одиниць і 4 одиниць заввишки, подвоївши його висоту та ширину.
Призначте той самий вершинний шейдер зверху, і все повинно виглядати точно так само.
Єдиним недоліком використання ArrayMesh порівняно з QuadMesh є те, що ArrayMesh не видно в редакторі, оскільки трикутник не будується, доки не буде запущено сцену. Щоб уникнути цього, побудуйте одну трикутну сітку в програмі моделювання та використовуйте її в MeshInstance3D.