Comments 40
Огромное спасибо за такую познавательную статью!
Для тех у кого «шейдеры = магия», это как раз то, что позволяет дать начальное понимание этой кухни.
Shadertoy — вот это настоящая магия. И целые игры с продвинутой графикой, которые там на шейдерах написаны. Ну ОК, не игры а демосцены (user input нет), но там уже полшага остаётся.
Супер! А как? Это ещё одна фича Shadertoy — проброс кликов мыши в шейдер? Не видел такого в описании.
Проброс кликов мыши и позиции там был с ооочень давных времен, как юниформ переменная:
uniform vec4      iMouse;                // mouse pixel coords. xy: current (if MLB down), zw: click
Инпут с клавиатуры тоже появился вскоре как текстура. Но основная проблема была в том, что негде было промежуточный стейт хранить. И только с появлением рендера в текстуру стало возможным писать вот такие игры.
Спасибо! Очень интересно! Давно интересовала данная тема, но не мог найти простого описания. Надеюсь что вы продолжите писать на эту тему для начинающих.
Очень интересно.
Было бы здорово в будущем увидеть статью о тех подходах, которые используют в реальных шейдерах для игр. Например, хотя все примеры из статьи мне показались тривиальными, я всё равно не могу понять, как таким методом попиксельной обработки можно из верхней половины картинки из майнкрафта получить нижнюю.

Например, вода. Как я понимаю, там каким-то образом должен учитываться наклон поверхности относительно линии взгляда и окружающие объекты, причём с учётом их положения в 3D-пространстве. Я даже предположить не могу, как всё это получается только лишь попиксельными манипуляциями с плоской картинкой.
Или освещённость. Там явно как-то учитывается, какой стороной поверхности повёрнуты к солнцу. Как этого добиться с помощью принципов, описанных в статье?
Иногда сцена рендерится несколькими камерами. Вид из одной камеры является текстурой для смешивания с текстурами предметов, либо становится картой глубин, картой теней, еще можно смешивать текстуры ориентируясь на угол между нормалями плоскостей текстуры и экрана, и так далее, (что знал — всё написал)
Хорошая ремарка про картинку с Майнкрафтом. Опять получилась статья «Как нарисовать сову.»

Движок игры создаёт дополнительные однобитные (обычно) маски — например, картинку, в которой каждый пиксель тем темнее, чем под более острым углом повёрнута к камере поверхность, с которой он взят. Или картинку, в которой каждый пиксель тем темнее, чем дальше от камеры поверхность под ним. И всё это тоже скармливается шейдеру. Вместе с картами теней и прочей мурой. Не всякий шейдер, кстати, как-то влияет на выходную картинку — некоторые выполняют чисто технические задачи.

Нет там битовых масок. GPU вообще битовые операции раньше не умел, и с интами там все плохо. В идеале GPU работает с флоатами.
Например диффузная компонента обычного Фонга — это косинус угла между инвертированной нормалью и направлением света.
Скалярное произведение двух векторов это результат умножения длин векторов на косинус между ними: |a||b|cos(ab)
Если длины векторов равны единицы, то скалярное произведение вернет косинус. Именно то, что нам нужно для освещения.
Для specular компоненты фонга — вектор луча сначала отражают вдоль нормали, а потом смотрят насколько он попадает в глаз. Через то же самое скалярное произведение.
О каких однобитовых масках вы говорите? Для классического фонга например там одно скалярное произведение на дифузный цвет, и два на спекуляр.
Углы там никто не считает, а скалярное произведение от нормализованных векторов возвращает косинус угла, вот его и используют.

А теперь то же самое, но по русски и чуть подробнее. Я не игродел, а описал решение как оно было сделано в одной программе для проектирования шкафов (из ДСП). Там уровень освещенности поверхности рассчитывался, реально, в шейдере.
Млиииин! ОдноБАЙТные маски у меня там были.

Вот выше описал:
https://habrahabr.ru/company/infopulse/blog/324476/#comment_10129732
А вот в псевдокоде вычисление коэффициента диффузного освещения (diffK)
vec3 N = normalize(pixelNormal); // pixelNormal - нормаль к поверхности в освещаемом пикселе
vec3 lightN = normalize(lightDir); // lightDir - вектор от источника света на освещаемый пиксель
float diffK = dot(-N, lightN); //косинус угла между этими векторами, наш коэффициент диффузного освещения
diffK = clamp(diffK, 0, 1); //но нас интересуют только положительные косинусы, обрезаем отрицательную часть

Интересно, почему переменные называются юниформные? И откуда они берутся? Если их передает ShaderToy, то, получается, в голом JS без обвязки написанные шейдеры перестанут работать? Если нет, то где полный список?

Униформные, т.к. принадлежат плоскости (полигону). Берутся из js обвязки, и да, надо будет пробросить их руками вне shader toy

Спасибо, стало понятнее. А вершинные шейдеры тоже получают униформные переменные или как?

Да, туда заливаются координаты вершин с индексами и куча вспомогательных переменных, типа viewport`а камеры. Uniform слово вообще можно опустить, т.к. других у шейдеров нет
Премного благодарен. Вы приоткрыли занавесу тайны =)
Крайне надеюсь на продолжение.
У оригинального автора есть ч. 2 и ч. 3
https://gamedevelopment.tutsplus.com/tutorials/a-beginners-guide-to-coding-graphics-shaders-part-2--cms-24111

https://gamedevelopment.tutsplus.com/tutorials/a-beginners-guide-to-coding-graphics-shaders-part-3--cms-24351
Не заметил, что статья — перевод, пока Вы явно на это не указали. И не поверил бы, если не соответствующая плашка. Текст читается хорошо, и даже комментарии в коде на русском. Перечитал еще раз отдельные абзацы — очень хороший перевод, побольше бы таких на хабре.

Так что да, все равно надеюсь на продолжение =)
Перевод действительно хороший, но пара английских шаблонов всё-таки в него просочилась. Я на середине заподозрил, что это перевод, и оказался прав :) Но — да, однозначно отличный материал и хороший перевод. Уже не в первый раз замечаю качественный материал от Инфопульс.
Спасибо огромное! Статья — что надо! Есть необходимость научиться выводить через шейдер 10 битные yuv планы видеокадра и собирать из них целый кадр. С 8ми битными разобрался без проблем. А вот 10-ти битные пока — магия.
Посмотрите в сторону формата текстуры DXGI_FORMAT_R10G10B10A2_UNORM и DXGI_FORMAT_R10G10B10A2_UINT если у вас DX. Для OGL можно нагуглить аналоги констант для этих форматов.
А есть низкоуровневый доступ? Например, писать на ассемблере или отлаживать пошагово в отладчике.
Я бы на таких штуках тригонометрию в школе преподавал. Наглядно же видно как арктангенс работает и к чему тут число Пи. А то «пишем формулу: синуск квадрат альфа плюс косинус квадрат альфа...» — скукота.
прикольно.
даже руки зачесались. слегка перепилил, вроде стало ещё круче. ))
https://www.shadertoy.com/view/lssyDl
Спасибо большое! Читать интересно, примеры вроде не очень сложные, но решать прямо приятно :)
Единственным предназначением шейдера является вернуть четыре числа: r, g, b и a.

Люблю когда простые вещи объясняют просто. Отличная статья, спасибо за перевод!
Only those users with full accounts are able to leave comments. Log in, please.