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.

Використання SubViewport як текстури

Вступ

Цей підручник познайомить вас із використанням SubViewport як текстури, яку можна застосовувати до 3D-об’єктів. Щоб це зробити, він проведе вас через процес створення процедурної планети, як показано нижче:

../../_images/planet_example.png

Примітка

У цьому підручнику не описано, як закодувати динамічну атмосферу, подібну до тієї, що є на цій планеті.

У цьому підручнику передбачається, що ви знайомі з тим, як налаштувати основну сцену, зокрема: Camera3D, light source, MeshInstance3D з a Primitive Mesh, і застосовуючи StandardMaterial3D до сітки. Основна увага буде зосереджена на використанні SubViewport для динамічного створення текстур, які можна застосувати до сітки.

У цьому посібнику ми розглянемо такі теми:

  • Як використовувати SubViewport як текстуру візуалізації

  • Відображення текстури на сферу за допомогою рівнопрямокутного відображення

  • Техніка фрагментного шейдера для процедурних планет

  • Встановлення карти шорсткості з Viewport Texture

Налаштування сцени

Створіть нову сцену та додайте наступні вузли, як показано нижче.

../../_images/viewport_texture_node_tree.webp

Перейдіть до MeshInstance3D і зробіть сітку SphereMesh

Налаштування SubViewport

Натисніть на вузол SubViewport і встановіть для нього розмір (1024, 512). SubViewport насправді може мати будь-який розмір, якщо ширина вдвічі перевищує висоту. Ширина повинна бути вдвічі більшою за висоту, щоб зображення було точно відображено на сфері, оскільки ми будемо використовувати рівнопрямокутну проекцію, але про це пізніше.

Далі вимкніть 3D. Ми будемо використовувати ColorRect для візуалізації поверхні, тому нам також не потрібен 3D.

../../_images/planet_new_viewport.webp

Виберіть ColorRect і в інспекторі встановіть прив’язки на Full Rect. Це гарантує, що ColorRect займає весь SubViewport.

../../_images/planet_new_colorrect.webp

Далі ми додаємо Shader Material до ColorRect (ColorRect > CanvasItem > Material > Material > New ShaderMaterial).

Примітка

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

Натисніть кнопку спадного меню для матеріалу шейдера та натисніть / Редагувати. Звідси перейдіть до Shader > New Shader. дайте йому назву та натисніть «Створити». клацніть шейдер в інспекторі, щоб відкрити редактор шейдерів. Видаліть стандартний код і додайте наступне:

shader_type canvas_item;

void fragment() {
    COLOR = vec4(UV.x, UV.y, 0.5, 1.0);
}

збережіть код шейдера, ви побачите в інспекторі, що наведений вище код відображає градієнт, як наведений нижче.

../../_images/planet_gradient.png

Тепер у нас є основи SubViewport, який ми рендеримо, і у нас є унікальне зображення, яке ми можемо застосувати до сфери.

Нанесення текстури

Тепер перейдіть до MeshInstance3D і додайте до нього StandardMaterial3D. Немає потреби в спеціальному Shader Material (хоча це було б хорошою ідеєю для більш просунутих ефектів, як-от атмосфера у прикладі вище).

MeshInstance3D > GeometryInstance > Geometry > material Override > New StandardMaterial3D

Потім натисніть спадне меню для StandardMaterial3D і натисніть «Редагувати»

Перейдіть до розділу «Ресурс» і встановіть прапорець Локально для сцени. Потім перейдіть до розділу «Альбедо» та клацніть біля властивості «Текстура», щоб додати текстуру Альбедо. Тут ми застосуємо створену текстуру. Виберіть «Нова текстура вікна перегляду»

../../_images/planet_new_viewport_texture.webp

Клацніть Viewport Texture, яку ви щойно створили в інспекторі, а потім натисніть «Призначити». Потім у спливаючому меню виберіть вікно перегляду, яке ми відтворили раніше.

../../_images/planet_pick_viewport_texture.webp

Тепер ваша сфера має бути забарвлена кольорами, які ми відобразили у вікні перегляду.

../../_images/planet_seam.webp

Помітили потворний шов, який утворюється там, де текстура обертається навколо? Це тому, що ми вибираємо колір на основі УФ-координат, а УФ-координати не обертаються навколо текстури. Це класична проблема в двовимірній картографічній проекції. Розробники ігор часто мають двовимірну карту, яку вони хочуть спроектувати на сферу, але коли вона обертається навколо, вона має великі шви. Існує елегантне рішення цієї проблеми, яке ми проілюструємо в наступному розділі.

Створення текстури планети

Тож тепер, коли ми візуалізуємо наш SubViewport, він чарівним чином з’являється на сфері. Але є потворний шов, створений нашими координатами текстури. Отже, як ми отримуємо діапазон координат, який добре обертається навколо сфери? Одним із рішень є використання функції, яка повторюється в області нашої текстури. sin і cos є двома такими функціями. Давайте застосуємо їх до текстури і подивимося, що вийде. Замініть існуючий код кольору в шейдері на такий:

COLOR.xyz = vec3(sin(UV.x * 3.14159 * 4.0) * cos(UV.y * 3.14159 * 4.0) * 0.5 + 0.5);
../../_images/planet_sincos.webp

Не дуже погано. Якщо озирнутися навколо, то можна побачити, що шов тепер зник, але на його місці ми маємо защемлення на полюсах. Це щипання пов’язане з тим, як Godot відображає текстури на сферах у своєму StandardMaterial3D. Він використовує техніку проекції, яка називається рівнопрямокутною проекцією, яка переводить сферичну карту на двовимірну площину.

Примітка

Якщо вас цікавить трохи додаткової інформації про техніку, ми будемо конвертувати сферичні координати в декартові координати. Сферичні координати відображають довготу та широту сфери, тоді як декартові координати, для всіх намірів і цілей, є вектором від центру сфери до точки.

Для кожного пікселя ми розрахуємо його 3D-положення на сфері. З цього ми будемо використовувати тривимірний шум для визначення значення кольору. Розраховуючи шум у 3D, ми вирішуємо проблему защемлення на полюсах. Щоб зрозуміти чому, уявіть шум, який обчислюється по поверхні сфери, а не по 2D площині. Коли ви обчислюєте поверхню сфери, ви ніколи не торкаєтеся краю, а отже, ви ніколи не створюєте шва чи точки защемлення на стовпі. Наступний код перетворює UV в декартові координати.

float theta = UV.y * 3.14159;
float phi = UV.x * 3.14159 * 2.0;
vec3 unit = vec3(0.0, 0.0, 0.0);

unit.x = sin(phi) * sin(theta);
unit.y = cos(theta) * -1.0;
unit.z = cos(phi) * sin(theta);
unit = normalize(unit);

І якщо ми використовуємо одиницю як вихідне значення COLOR, ми отримаємо:

../../_images/planet_normals.webp

Тепер, коли ми можемо обчислити 3D-положення поверхні сфери, ми можемо використовувати 3D-шум, щоб створити планету. Ми будемо використовувати цю функцію шуму безпосередньо з Shadertoy:

vec3 hash(vec3 p) {
    p = vec3(dot(p, vec3(127.1, 311.7, 74.7)),
             dot(p, vec3(269.5, 183.3, 246.1)),
             dot(p, vec3(113.5, 271.9, 124.6)));

    return -1.0 + 2.0 * fract(sin(p) * 43758.5453123);
}

float noise(vec3 p) {
  vec3 i = floor(p);
  vec3 f = fract(p);
  vec3 u = f * f * (3.0 - 2.0 * f);

  return mix(mix(mix(dot(hash(i + vec3(0.0, 0.0, 0.0)), f - vec3(0.0, 0.0, 0.0)),
                     dot(hash(i + vec3(1.0, 0.0, 0.0)), f - vec3(1.0, 0.0, 0.0)), u.x),
                 mix(dot(hash(i + vec3(0.0, 1.0, 0.0)), f - vec3(0.0, 1.0, 0.0)),
                     dot(hash(i + vec3(1.0, 1.0, 0.0)), f - vec3(1.0, 1.0, 0.0)), u.x), u.y),
             mix(mix(dot(hash(i + vec3(0.0, 0.0, 1.0)), f - vec3(0.0, 0.0, 1.0)),
                     dot(hash(i + vec3(1.0, 0.0, 1.0)), f - vec3(1.0, 0.0, 1.0)), u.x),
                 mix(dot(hash(i + vec3(0.0, 1.0, 1.0)), f - vec3(0.0, 1.0, 1.0)),
                     dot(hash(i + vec3(1.0, 1.0, 1.0)), f - vec3(1.0, 1.0, 1.0)), u.x), u.y), u.z );
}

Примітка

Вся заслуга автора, Ініго Кілеза. Публікується за ліцензією MIT.

Тепер, щоб використовувати noise, додайте наступне до функції fragment:

float n = noise(unit * 5.0);
COLOR.xyz = vec3(n * 0.5 + 0.5);
../../_images/planet_noise.webp

Примітка

Щоб підкреслити текстуру, ми встановлюємо незатінений матеріал.

Тепер ви бачите, що шум справді плавно обертається навколо сфери. Хоча це зовсім не схоже на планету, яку вам обіцяли. Отже, давайте перейдемо до чогось більш барвистого.

Розфарбовуємо планету

Тепер розфарбуємо планету. Хоча є багато способів зробити це, поки що ми будемо дотримуватися градієнта між водою та землею.

Щоб зробити градієнт у GLSL, ми використовуємо функцію mix. mix приймає два значення для інтерполяції між ними та третій аргумент для вибору кількості інтерполяції між ними; по суті, він змішує два значення разом. В інших API ця функція часто називається lerp. Проте lerp зазвичай зарезервований для змішування двох плаваючих елементів разом; mix може приймати будь-які значення, незалежно від того, чи це плаваючі чи векторні типи.

COLOR.xyz = mix(vec3(0.05, 0.3, 0.5), vec3(0.9, 0.4, 0.1), n * 0.5 + 0.5);

Перший колір - блакитний для океану. Другий колір — червонуватий колір (оскільки всім чужим планетам потрібна червона місцевість). І, нарешті, вони змішуються за допомогою n * 0,5 + 0,5. n плавно змінюється між -1 та 1. Тому ми відображаємо його в діапазоні 0-1, який очікує mix. Тепер ви бачите, що кольори змінюються між синім і червоним.

../../_images/planet_noise_color.webp

Це трохи розмитіше, ніж ми хочемо. Планети зазвичай мають відносно чітке відокремлення між сушею та морем. Щоб зробити це, ми змінимо останній член на smoothstep(-0.1, 0.0, n). Таким чином, весь рядок виглядає так:

COLOR.xyz = mix(vec3(0.05, 0.3, 0.5), vec3(0.9, 0.4, 0.1), smoothstep(-0.1, 0.0, n));

Функція smoothstep повертає 0, якщо третій аргумент менший за перший, та 1, якщо третій аргумент більший за другий, і плавно переходить між 0 та 1, якщо третє число знаходиться між першим та другим. Отже, у цьому рядку smoothstep повертає 0, коли n менше за -0.1, і повертає 1, коли n більше за 0.

../../_images/planet_noise_smooth.webp

Ще одна річ, щоб зробити це трохи більше планети. Земля не повинна бути такою плямистою; давайте зробимо краї трохи шорсткіше. Трюк, який часто використовується в шейдерах, щоб зробити нерівну місцевість із шумом, полягає в накладенні рівнів шуму один на інший на різних частотах. Ми використовуємо один шар, щоб створити загальну пухлисту структуру континентів. Потім інший шар трохи розбиває краї, а потім ще один і так далі. Що ми зробимо, це обчислимо n за допомогою чотирьох рядків шейдерного коду замість одного. n стає:

float n = noise(unit * 5.0) * 0.5;
n += noise(unit * 10.0) * 0.25;
n += noise(unit * 20.0) * 0.125;
n += noise(unit * 40.0) * 0.0625;

А зараз планета виглядає так:

../../_images/planet_noise_fbm.webp

Створення океану

Остання річ, щоб зробити це більше схожим на планету. Океан і земля по-різному відбивають світло. Тому ми хочемо, щоб океан сяяв трохи більше, ніж суша. Ми можемо зробити це, передавши четверте значення в альфа канал нашого результату COLOR і використовуючи його як карту шорсткості.

COLOR.a = 0.3 + 0.7 * smoothstep(-0.1, 0.0, n);

Цей рядок повертає 0,3 для води та 1,0 для землі. Це означає, що земля буде досить нерівною, тоді як вода буде досить гладкою.

А потім у матеріалі в розділі «Металевий» переконайтеся, що для Металевий встановлено 0, а Дзеркальне1. Причина цього полягає в тому, що вода дуже добре відбиває світло, але не є металевою. Ці значення фізично не точні, але їх достатньо для цієї демонстрації.

Далі в розділі «Шорсткість» встановіть текстуру шорсткості на Viewport Texture, яка вказує на текстуру нашої планети SubViewport. Нарешті, встановіть Текстурний канал на Альфа. Це вказує рендереру використовувати альфа канал нашого результату COLOR як значення Roughness.

../../_images/planet_ocean.webp

Ви помітите, що зміни мало, за винятком того, що планета більше не відображає небо. Це відбувається тому, що за замовчуванням, коли щось відображається зі значенням альфа, воно малюється як прозорий об’єкт на тлі. А оскільки фон за замовчуванням SubViewport непрозорий, альфа канал Viewport Texture має значення 1, що призводить до текстури планети. намальовані дещо тьмянішими кольорами та скрізь зі значенням Шорсткості 1. Щоб виправити це, ми переходимо до SubViewport і вмикаємо властивість «Transparent Bg». Оскільки ми зараз рендеримо один прозорий об’єкт поверх іншого, ми хочемо ввімкнути blend_premul_alpha:

render_mode blend_premul_alpha;

Це попередньо множить кольори на значення альфа, а потім правильно їх поєднує. Як правило, під час змішування одного прозорого кольору поверх іншого, навіть якщо фон має альфа 0 (як це відбувається в цьому випадку), у вас виникають дивні проблеми з виливом кольору. Налаштування blend_premul_alpha виправляє це.

Тепер планета має виглядати так, ніби вона відбиває світло від океану, а не від землі. переміщайтеся навколо OmniLight3D у сцені, щоб ви могли побачити ефект відблисків на океані.

../../_images/planet_ocean_reflect.webp

І ось воно. Процедурна планета, створена за допомогою SubViewport.