learnopengl. Урок 1.6 — Текстури

У минулому уроці ми навчилися розфарбовувати наші об'єкти в різні кольори. Але для того, щоб домогтися якогось реалізму нам потрібно дуже багато квітів. Минулого разу, ми розфарбовували вершини трикутника, якщо ми підемо тим шляхом, то нам знадобиться занадто велика кількість вершин для виведення картинки. Зацікавилися, прошу під кат.

Меню
  1. Починаємо
    1. OpenGL

    2. Створення вікна
    3. Hello Window
    4. Hello Triangle
    5. Shaders
    6. Текстури
Програмісти і художники воліють використовувати текстури. Текстура — це 2D зображення (1D і 3D текстура також існують), що використовується для додавання деталей об'єкту; вважайте, що текстура — це шматок паперу з зображенням цегли (наприклад), який наклеєний на ваш будинок і здається, що ваш будинок зроблений з цегли.
Крім картинок, текстури можуть зберігати великі набори даних, що надсилаються в шейдери, але ми залишимо це питання для іншого уроку.
Нижче ви можете бачити текстуру цегляної стіни приліплену на трикутник з минулого уроку.
Цегляна стіна на трикутнику
Для того, щоб прив'язати текстуру до трикутника ми повинні повідомити кожній вершині трикутника, якій частині текстури належить ця вершина. Кожна вершина, відповідно повинна мати текстурні координати, асоційовані з частиною текстури.
Текстурні координати знаходяться в проміжку між 0 і 1 по x і осі y (ми ж використовуємо 2D текстури). Отримання кольору текстури з допомогою текстурних координат називається відбором (sampling). Текстурні координати починаються в (0, 0) у нижньому лівому куті текстури і закінчуються на (1, 1) у верхньому правому кутку. Зображення нижче демонструє як ми накладали текстурні координати на трикутник:
Накладення текстурних координат на трикутник
Ми вказали 3 текстурні координати для трикутника. Ми хочемо, щоб нижній лівий кут трикутника співвідносився з нижнім лівим кутом текстури, тому ми передаємо (0, 0) нижній лівій вершині трикутника. Відповідно в нижню праву вершину передаємо (1, 0). Верхня вершина трикутника повинна співвідноситися з центральною частиною верхньої стороною текстури, тому ми передаємо у верхню вершину (0.5, 1.0) в якості текстурні координати.
В результаті текстурні координати для трикутника повинні виглядати якось так:
GLfloat texCoords[] = {
0.0 f, 0.0 f, // Нижній лівий кут 
1.0 f, 0.0 f, // Нижній правий кут
0.5 f, 1.0 f // Верхня центральна сторона
};

Семплінг текстури може бути виконаний різними методами. Наша робота — повідомити OpenGL як він повинен проводити семплінг.
Texture Wrapping
Текстурні координати, найчастіше, знаходяться в проміжку між (0,0) і (1,1), але що станеться, якщо текстурні координати вийдуть за цей проміжок? Поведінка OpenGL за замовчуванням — повторювати зображення (фактично, просто ігнорується ціла частина числа з плаваючою точкою), але також є й інші опції:
  • GL_REPEAT: поведінка для текстур. Повсторяет текстуру.
  • GL_MIRRORED_REPEAT: Схоже на _GLREPEAT за винятком того, що в цьому режимі зображення відображається.
  • GL_CLAMP_TP_EDGE: Прив'язує координати між 0 і 1. В результаті виходять за межі координати будуть прив'язані до кордону текстури.
  • GL_CLAMP_TO_BORDER: Координати, що виходять за межі діапазону будуть давати встановлений користувачем колір кордону.
Кожна їх цих опцій по різному відображається при використанні текстурних координат, що виходять за межі проміжку. Зображення нижче відмінно демонструє відмінності:
Демонстрація відмінностей опцій
Кожну з вищепредставленних опцій можна встановити на осі (s, t (r якщо ви використовуєте 3D текстури), еквівалентні x, y і z за допомогою функцій **glTextParameter***:
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);

Перший аргумент визначає мета, до якої прив'язана наша текстура, ми працюємо з 2D текстурою, тому наше значення GL_TEXTURE_2D. Друге значення потрібно для того, щоб повідомити OpenGL який конкретно параметр ми хочемо встановити. Ми хочемо налаштувати опцію WRAP і вказати її значення для осей S і T. В останньому аргументі передається вибраний метод wrapping. В даному випадку ми використовуємо GL_MIRRORED_REPEAT.
Якщо б ми вибрали GL_CLAMP_TO_BORDER, то нам би довелося вказати колір кордонів. Робиться це fv альтернативою glTextParameter з передачею до неї GL_TEXTURE_BORDER_COLOR в якості опції і з масиву чисел з плаваючою точкою в якості колірного значення.
float borderColor[] = { 1.0 f, 1.0 f, 0.0 f, 1.0 f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); 

Фільтрування текстур
Текстурні координати не залежать від роздільної здатності, але при цьому можуть приймати будь-які значення з плаваючою точкою, тому OpenGL потрібно зрозуміти який піксель текстури (також званого текселем) йому потрібно накласти. Ця проблема стає найбільш гострою якщо потрібно накласти текстуру низького дозволу на великий об'єкт. Можливо ви вже здогадалися, що в OpenGL є опція для фільтрування текстур. Є кілька доступних опцій, але ми розглянемо тільки найбільш важливі: GL_NEAREST і GL_LINEAR.
GL_NEAREST (також званий фільтр найближчого сусіда) — стандартний метод фільтрування в OpenGL. Поки він встановлений OpenGL буде вибирати піксель, який знаходиться ближче всього до текстурної координаті. Нижче ви можете бачити 4 пікселя і хрест, який засвідчує текстурну координату. Оскільки центр верхнього лівого тексель ближче всього до текстурної координаті, то він і обирається в якості кольору семпла.
Фільтр найближчого сусіда
GL_LINEAR (також званий (бі)лінійної фільтрацією). Приймає интерполированное значення від найближчих до текстурної координаті текселей. Чим ближче тексель до текстурної координаті, тим більше множник кольори цього текселя.
Нижче ви можете бачити приклад змішування кольорів сусідніх пікселів:
Білінійна фільтрація
Але який же все таки візуальний ефект від вибраного ефекту фільтрування? Давайте подивимося, як ці методи відпрацюють з текстурою в маленькому дозволі на великому об'єкті (текстура була збільшена для того, щоб було видно окремі тексели):
Робота фільрації

Mipmaps

Уявіть, що у вас є велика кімната з тисячами об'єктів, до кожного з яких прив'язана текстура. Частина об'єктів ближче до спостерігача, частина об'єктів далі від спостерігача і кожному об'єкту прив'язана текстура з високою роздільною здатністю. Коли об'єкт знаходиться далеко від спостерігача, потрібно обробити лише кілька фрагментів. У OpenGL є складнощі з отриманням правильного кольору для фрагмента з текстури високого дозволу, коли доводиться враховувати велику кількість пікселів текстури. Така поведінка буде генерувати артефакти на маленьких об'єктах, не кажучи вже про надмірну витрату пам'яті пов'язаної з використанням текстур високого дозволу на маленьких об'єктах.
Для вирішення цієї проблеми OpenGL використовує технологію, звану мипмапами (mipmaps), яка передбачає набір зображень-текстур де кожна наступна текстура вдвічі менше минулого. Ідея, яка лежить в основі мипмапов досить проста: після певної відстані від спостерігача, OpenGL буде використовувати іншу мипмап текстуру, яка буде краще виглядати на поточному відстані. Чим далі від спостерігача знаходиться об'єкт, тим менше буде використовуватися текстура, оскільки користувачеві складніше буде помітити різницю між дозволами текстур.Також мипмапы мають приємне властивість збільшувати продуктивність, що ніколи не буває зайвим. Давайте подивимося на приклад мипмапы ближче:
Приклад мипмапа
Створення набору мипмап текстур для кожного зображення досить клопітно, але на щастя OpenGL вміє генерувати їх за допомогою виклику glGenerateMipmaps після створення текстури. Скоро ми побачимо приклад.
Під час перемикання між рівнями мипмапов в процесі відтворення OpenGL може відображати деякі артефакти, начебто гострих країв між двома рівнями. Також можливе використання фільтрації на текстурах, можна також використання фільтрації на різних рівнях мипмапов з допомогою NEAREST і LINEAR фільтрації для перемикання між рівнями. Для вказівки способу фільтрації між рівнями мипмапами ми можемо замінити стандартні методи одній з наступних чотирьох параметрів:
  • _GL_NEARESET_MIPMAPNEAREST: Вибирає найближчий мипмап, співвідноситься з розміром пікселя і також використовується інтерполяція найближчого сусіда для семплінгу текстур.
  • _GL_LINEAR_MIPMAPNEAREST: Вибирає найближчий мипмап і сэмплирует його методом лінійної інтерполяції.
  • _GL_NEAREST_MIPMAPLINEAR: Лінійна інтерполяція між двома найближчими мипмапами і семплірування текстур за допомогою лінійної інтерполяції.
Також як і з фільтрацією текстур ми можемо встановити метод фільтрації за допомогою функції glTexParameteri
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

Часта помилка — це установка методу фільтрації мипмапов як збільшує фільтра. Це не дасть ніякого ефекту, оскільки мипмапы в основному використовують при зменшенні текстури. Збільшення текстури не використовує мипмапы, тому при передачі йому опції фільтрації мипмапа згенерує помилку GL_INVALID_ENUM.
Завантаження і створення текстур
Перед тим як почати використовувати наші текстури нам потрібно їх завантажити в наш додаток. Текстурні зображення можуть зберігатися в безмежній кількості форматів, у кожному з яких своя структура і впорядкованість даних, так як ми передамо нашу зображення в додаток? Одним з рішень є використання зручного нам формату, наприклад .PNG і написати власну систему завантаження зображень у великий масив байт. Хоч написання власного завантажувача зображень не являє собою непідйомну роботу, все-таки це досить важко, тим більше якщо ви захочете використовувати багато форматів файлів.
Іншим рішенням є використання готової бібліотеки для завантаження зображень, яка б підтримувала безліч різних популярних форматів і робила багато важкої роботи за нас. Приміром SOIL.
SOIL
SOIL розшифровується як Simple OpenGL Image Library, підтримує більшість популярних форматів зображень, легка у використанні і може бути завантажена звідси. Також як і більшість інших бібліотек вам доведеться згенерувати файл .lib самостійно. Ви можете використовувати один з їхніх проектів, розташованих в папці /projects (не хвилюйтеся, якщо версія їх проектів буде нижче версії вашої VS. Просто сконвертируйте їх у нову версію, це повинно працювати на більшості випадків) для створення на його основі власного. Також додайте вміст папки src у свою папку include. Також не забудьте додати SOIL.lib в налаштування свого зв'язування і додати #include <SOIL.h> на початку вашого коду.
Для поточної текстурної секції ми будемо використовувати зображення дерев'яного контейнера. Для завантаження зображення через SOIL ми використовуємо функцію SOIL_load_image:
int width, height;
unsigned char* image = SOIL_load_image("container.jpg", &width, &height, 0, SOIL_LOAD_RGB);

Перший аргумент функції — це розташування файлу зображення. Другий і третій аргументи — це покажчики на int в які будуть поміщені розміри зображення: ширина і висота. Вони нам знадобляться для генерації текстури. Четвертий аргумент — це кількість каналів зображення, але ми залишимо там просто 0. Останній аргумент повідомляє SOIL як йому завантажувати зображення: нам потрібна тільки RGB інформація зображення. Результат буде зберігатися у великому масиві байт.
Генерація текстури
Також як і на будь-який інший об'єкт в OpenGL, текстури посилаються ідентифікатори. Давайте створимо один:
GLuint texture;
glGenTexture(1, &texture);

Функція glGenTexture приймає в якості першого аргументу кількість текстур для генерації, а в якості другого аргументу — масив GLuint в якому будуть зберігатися ідентифікатори цих текстур (в нашому випадку це один GLuint). Як будь-який інший об'єкт, ми прив'яжемо його для того, щоб функції, які використовують текстури, знали яку текстуру використовувати.
glBindTexture(GL_TEXTURE_2D, texture);

Після прив'язки текстури ми можемо почати генерувати дані текстури використовуючи попередньо завантажене зображення. Текстури генеруються за допомогою glTexImage2D:
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
glGenerateMipmap(GL_TEXTURE_2D);

У цій функції досить багато аргументів, тому давайте по порядку:
  • Перший аргумент описує текстурну мета. Встановивши значення GL_TEXTURE_2D ми повідомили функції, що наша текстура прив'язана до цієї мети (щоб інші цілі GL_TEXTURE_1D і GL_TEXTURE_3D не будуть задіяні).
  • Другий аргумент описує рівень мипмапа для якого ми хочемо створити текстуру, якщо раптом ми хочемо самостійно згенерувати мипмапы. Оскільки ми залишимо генерацію мипмапов на OpenGL ми передамо 0.
  • Третій аргумент повідомляє OpenGL в якому форматі ми хочемо зберігати текстуру. Оскільки наше зображення має тільки RGB значення і текстури ми також будемо зберігати тільки RGB значення.
  • Четвертий і п'ятий аргументи задають ширину і висоту результуючої текстури. Ми отримали ці значення раніше під час завантаження зображення.
  • Шостий аргумент завжди повинен бути 0. Аргумент застарів).
  • Сьомий і восьмий аргументи описують формат і тип даних вихідного зображення. Ми завантажували RGB значення і зберігали їх в байтах (char) так що ми передаємо ці значення.
  • Останній аргумент — це самі дані зображення.
Після виклику glTexImage2D поточна прив'язана текстура буде мати прив'язане до неї зображення. Правда текстура буде мати тільки базове зображення і якщо ми захочемо використовувати мипмапы, то нам доведеться таким же чином задавати зображення просто инкрементируя значення рівня мипмапов. Ну або ми можемо просто викликати glGenerateMipmap після генерації текстури. Ця функція автоматично згенерує всі необхідні мипмапы для поточної прив'язаною текстури.
Після закінчення генерації текстури і мипмапов хорошою практикою є звільнення ділянки пам'яті, виділеної під завантажене зображення, і відв'язування об'єкта текстури.
SOIL_free_image_data(image);
glBindTexture(GL_TEXTURE_2D, 0);

Весь процес генерації текстури виглядає приблизно так:
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
// Встановлюємо налаштування фільтрації і перетворень (на поточному текстурі)
...
// Завантажуємо і генеруємо текстуру
int width, height;
unsigned char* image = SOIL_load_image("container.jpg", &width, &height, 0, SOIL_LOAD_RGB); 
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
glGenerateMipmap(GL_TEXTURE_2D);
SOIL_free_image_data(image);
glBindTexture(GL_TEXTURE_2D, 0); 

Застосування текстур
Для наступних глав ми будемо використовувати чотирикутник отрисованный з допомогою *glDrawElements з останньої частини уроку про Hello Triangle. Нам треба повідомити OpenGL як сэмплировать текстуру, тому ми оновимо вершинні дані, додавши в них текстурні координати:
GLfloat vertices[] = {
// Позиції // Кольору // Текстурні координати
0.5 f, 0.5 f, 0.0 f, 1.0 f, 0.0 f, 0.0 f, 1.0 f, 1.0 f, // Верхній правий
0.5 f, -0.5 f, 0.0 f, 0.0 f, 1.0 f, 0.0 f, 1.0 f, 0.0 f, // Нижній правий
-0.5 f, -0.5 f, 0.0 f, 0.0 f, 0.0 f, 1.0 f, 0.0 f, 0.0 f, // Нижній лівий
-0.5 f, 0.5 f, 0.0 f, 1.0 f, 1.0 f, 0.0 f, 0.0 f, 1.0 f // Верхній лівий
};

Після додавання додаткових атрибутів нам знову доведеться сповістити OpenGL про нашому новому форматі:
Новий формат верхових даних.
glVertexAttribPointer(2, 2, GL_FLOAT,GL_FALSE, 8 * sizeof(GLfloat), (GLvoid*)(6 * sizeof(GLfloat)));
glEnableVertexAttribArray(2); 

Зауважте, що ми скорегували значення кроку минулих двох атрибутів під 8 * sizeof(GLfloat).
Потім нам потрібно змінити вершинний шейдер для того, щоб він приймав текстурні координати в якості атрибута а потім передавав їх фрагментному шейдеру:
#version 330 core
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color;
layout (location = 2) in vec2 texCoord;

out vec3 ourColor;
out vec2 TexCoord;

void main()
{
gl_Position = vec4(position, 1.0 f);
ourColor = color;
TexCoord = texCoord;
}

Фрагментний шейдер також повинен ухвалювати TexCoord в якості вхідної змінної.
Фрагментний шейдер також повинен мати доступ до текстурного об'єкту, але як ми передамо його у фрагментний шейдер? GLSL має вбудований тип даних для текстурних об'єктів, званий sampler у якого в якості закінчення тип текстури, тобто sampler1D, sampler3D і, в нашому випадку, sampler2D. Ми можемо додати текстуру фрагментному шейдеру просто оголосивши uniform smpler2D до якого пізніше ми передамо текстуру.
#version 330 core
in vec3 ourColor;
in vec2 TexCoord;

out vec4 color;

uniform sampler2D ourTexture;

void main()
{
color = texture(ourTexture, TexCoord);
}

Для семплювання кольору текстури ми використовуємо вбудовану в GLSL функцію texture яка в якості першого аргументу приймає текстурний sampler, а в якості другого аргументу текстурні координати. Функція texture потім сэмплирует значення кольору, використовуючи текстурні параметри, які ми поставили раніше. Результатом роботи цього фрагментного шейдерів (фільтрований) колір текстури (интерполированноый) текстурної координаті.
Залишилося лише прив'язати текстуру перед викликом glDrawElements і вона автоматично буде передана сэмплеру фрагментного шейдера:
glBindTexture(GL_TEXTURE_2D, texture);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);

Якщо ви все зробили правильно то отримаєте наступне зображення:
Результат з одного текстурою
Якщо ваш чотирикутник повністю чорний або білий значить ви помилились. Перевірте шейдерні логи і порівняйте ваш код вихідним.
Для отримання більш кольорового ефекту ми можемо змішати результуючий колір текстури з вершинним кольором. Для змішування ми просто помножимо кольору під фрагментном шейдере.
Color = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0 f);

У вас повинно вийти щось таке?
Результат зі змішуванням
Текстурний блок
Можливо ви ставите питанням: «Чому sampler2D мінлива є uniform, якщо ми їй так і не присвоїли ніяке значення за допомогою glUniform?». За допомогою glUniform1i ми можемо присвоїти значення метоположения текстурного сэмплеру для можливості використання декількох текстур в одному фрагментном шейдере. Розташування текстури частіше називається текстурним блоком. Текстурний блок за замовчуванням — 0, що означає поточний активний текстурний блок для того, щоб нам не потрібно було вказувати місцезнаходження в минулому секції.
Основна мета текстурних блоків це забезпечення можливості використання більш ніж 1 текстури в нашому шейдере. Передаючи текстурні блоки сэмплеру ми можемо прив'язувати кілька текстур за один раз до тих пір, поки ми активуємо співвідносяться текстурні блоки. Також як і glBindTexture ми можемо активувати текстури за допомогою glActivateTexture передаючи туди текстурний блок, який ми хочемо використовувати:
glActiveTexture(GL_TEXTURE0); // Активуємо текстурний блок перед прив'язкою текстури
glBindTexture(GL_TEXTURE_2D, texture);

Після активації текстурного блоку, подальший виклик glBindTexture прив'яже цю текстуру активного текстурного блоку. Блок _GLTEXTURE0 завжди активований за замовчуванням, так що нам не потрібно активувати текстурні блоки у минулому прикладі.
OpenGL підтримує як мінімум 16 текстурних блоків, які ви можете отримати через GL_TEXTURE0GL_TEXTURE15. Вони оголошені по-порядку, тому ви також можете отримати їх наступним чином: GL_TEXTURE8= GL_TEXTURE0 + 8. Це зручно, якщо вам доводиться итерировать через текстурні блоки.
У будь-якому випадку нам все ще потрібно змінити фрагментний шейдер для прийняття іншого семплера:
#version 330 core
...

uniform sampler2D ourTexture1;
uniform sampler2D ourTexture2;

void main()
{
color = mix(texture(ourTexture1, TexCoord), texture(ourTexture2, TexCoord), 0.2);
}

Фінальний результат — це комбінація двох текстур. У GLSL вбудована функція mix яка приймає два значення на вхід і інтерполює їх на основі третього значення. Якщо третє значення 0.0 ця функція поверне перший аргумент, якщо 1.0 то другий. Значення 0.2 поверне 80% першого вхідного кольору і 20% другого вхідного кольору.
Тепер нам треба завантажити і створити іншу текстуру; ви вже знайомі з такими кроками. Переконайтеся, що ви створили ще один об'єкт текстури, завантажили зображення і згенерували фінальну текстуру за допомогою glTexImage2D. Для другої текстури ми використовуємо зображення особи під час вивчення цих уроків.
Для того, щоб використовувати другу текстуру (першу) нам треба буде трохи змінити процедуру відтворення, прив'язкою обох текстур до відповідних текстурних блоків і зазначенням, до якого сэмплеру відноситься який текстурний блок:
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture1"), 0);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);
glUniform1i(glGetUniformLocation(ourShader.Program, "ourTexture2"), 1);

glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glBindVertexArray(0); 

Зауважте, що використовували glUniform1i для того, щоб встановити позицію текстурного блоку в uniform sampler. Встановлюючи їх через glUniform1i ми будемо впевнені, що uniform sampler співвідноситься з правильним текстурним блоком. В результаті ви повинні будете отримати наступний результат:
Результат зі змішуванням
напевно ви помітили, що текстура перевернута догори ногами! Це відбулося, оскільки OpenGL представляє координату 0.0 по осі Y знизу зображення, але зображення часто мають координату 0.0 зверху по осі Y. Деякі бібліотеки для завантаження зображень, типу Devil мають настройки для інвертування Y осі під час завантаження. SOIL такий налаштування позбавлений. У SOIL є функція SOIL_load_OGL_texture, яка завантажує текстуру і генерує текстуру з прапором __SOIL_FLAG_INVERT_Y__, який вирішує нашу проблему. Тим не менше ця функція використовує виклики, недоступні в сучасній версії OpenGL, тому нам доведеться зупинитися на використанні SOIL_load_image і самостійної завантаженням текстури.
Для виправлення цієї невеликої недоробки у нас є 2 шляхи:
  1. Ми можемо змінити текстурні координати в вершинних даних і перевернути вісь Y (відняти Y координату з 1)
  2. Ми можемо змінити вершинний шейдер для перевертання Y координати, замінивши формулу завдання TexCoord TexCoord = vec2(texCoord.x, 1.0 f — texCoord.y);..
Наведені рішення — це маленькі хакі, які дозволяють перевернути зображення. Ці способи працюють в більшості випадків, але результат завжди буде залежати від формату та типу обраної текстури, так що найкраще вирішення проблеми — вирішувати її на етапі завантаження зображення, приводячи її у формат, зрозумілий OpenGL.
Як тільки ви зміните вершинні дані або перевернете вісь Y у вершинном шейдере ви отримаєте наступний результат:
Перевернутий результат зі змішуванням
Якщо ви побачили щасливий контейнер, то ви все зробили правильно. Ви можете порівняти свій код вихідним, а також вершинный і фрагментный шейдери.
Вправи
Для кращого засвоєння матеріалу перш ніж приступати до наступного уроку погляньте на наступні вправи.
  1. Добийтеся того, щоб тільки вершинний шейдер був перевернутий, з допомогою зміни фрагментного шейдера. Решение
  2. Поекспериментуйте з іншими методами натягування текстур, змінюючи текстурні координати в межах від 0.0 f 2.0 f замість 0.0 f 1.0 f. Перевірте, чи зможете ви відобразити 4 усміхнених пики на одному контейнері. Рішення, Результат
  3. Спробуйте відобразити тільки центральні пікселі текстури на чотирикутнику так, щоб поодинокі пікселі були видні при зміні текстурних координат. Спробуйте встановити режим фільтрації __GL_NEAREST__ для того, щоб було видно пікселі більш чітко. Решение.
  4. Використовуйте uniform змінну як 3 параметри функції mix для зміни коефіцієнта змішування двох текстур на льоту. Використовуйте кнопки вгору і вниз для регулювання змішування. Рішення, Фрагментний шейдер
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.