Шейдер глаза v.1
Сразу скажу, что мы делаем красивый глаз для мультика, а не фоторил для кино, так что если вы ищете последнее – то просто посмотрите картинки и закрывайте статью.
Наверно каждый из вас уже видал шейдер глаза выложенный на highend3D, не буду врать – я тоже сразу его скачал, как потребовалось сделать процедурные глаза в нашем мультике. Глаз не подходил на 100% и я было решил его чуть поправить, но открыв понял, что разобраться в таком коде как там сложнее, чем написать с нуля то, что необходимо конкретно мне. На этой радостной ноте я и хочу перейти собственно к создания глаза.
Постановка задачи – 90% успеха всего дела, так что надо разобраться по пунктам, что нам надо получить. Не буду утруждать вас познаниями в биологии о том, из чего состоит глаз и как он работает. Так же не буду приводить излюбленную картинку глаза в разрезе, чтобы не травмировать вашу утонченную психику.
Разделим задачу на несколько этапов, чтобы не мешать все сразу в кучу
A. текстура глаза
B. дисплейс/бамп радужки
C. бонусны и фичи
А. Текстура глаза
Текстура глаза состоит из 3х окружностей вставленных друг в друга:
1. зрачок – черная окружность (zRadius)
2. радужка – разводы и «каустики» (rRadius)
3. обводка – ободок темнее радужки в один тон с ней (oRadius)
все остальное белок

Попробуем его нарисовать. Начать я думаю надо с того, как рисовать окружность:
a) Задаем центр (posX, posY) и радиус внешними переменными
b) Вычисляем расстояние от текущей точки поверхности до центра окружности и если длина менее радиуса, то заливаем внутренним цветом, если нет – то внешним.
rrr = sqrt(pow2) + powt-posY),2;
Задав 3 радиуса (зрачок, радужка, обводка) мы можем нарисовать самый простой вариант глаза сразу, но хочется красивый ведь, так что движемся далее.
Насобирав кучу картинок всевозможных реальных и мультяшных глаз, я пришел к выводу, что почти всегда рисунок на радужке можно свести к трем компонентам:
a) Двойной градиент от зрачка к обводке. В условной середине светлее, к зрачку и обводке затемнение
b) Радиальные полосы
c) Шум
Градиент делается просто, достаточно не просто заливать по радиусу, а смещать цвет еще, так что идем далее.
Радиальные полосы тоже довольно легко получить, достаточно получить шум зависящий от угла поворота вектора от центра глаза к текущей.
d = distance(point(s, t, 0), point(posX, posY, 0));
if (d != 0) ang = asin((posY-t)/d);
else ang = 0;
if (posX-s < 0) ang = 180 – ang;nnn = noise(ang*5+seed);
Шум получается еще проще, достаточно просто взять noise от s и t поверхности.
no = noise(s*size, t*size);
Шум получается довольно гладким, так что я обычно умножаю его самого на себя с увеличенным в 4–8 раз size параметром. Плюс можно чуть ослабить влияние второго шума через clamp:
no = noise(s*size, t*size) * clamp(noise(s*size*4, t*size*4),.5, 1);
Перемножив все три компонента и добавив какой то цвет мы уже сразу получаем довольно приличный глаз. Хотите сложнее что-то – просто добавьте еще шума и т.п., но я остановился уже на этом.
B. Дисплейс/бамп радужки
Особо изобретать тут нечего, просто надо сделать вмятину по окружности. Код представляет собой немного модифицированный simple-displace:
float disp = (rrr<rRadius) ? 1-pow(rrr/rRadius,2) : 0;
point PP = transform(«shader», P);
Nf = normalize(transform(«shader», N));
PP += -Kb * disp * Nf;
PP = transform(«shader», “current”, PP);if(Use Shading Normals? != 0)
{
normal deltaN = normalize(N) – normalize(Ng);
N = normalize(calculatenormal(PP)) + deltaN;}
else N = calculatenormal(PP);if(Do Displacement? != 0)
P = PP;
Вся хитрость в первой строчке, где собственно мы и получаем величину дисплейса для каждого пиксела внутри нашей радужки
С) бонусны и фичи, тоесть то, что оживит глаз и сделает из него нечто больше, чем шарик
Отсмотрев кучу пиксаровских мультиков на «глаза» я заметил, что у них почти всегда используются одинаковые глаза, и я так полагаю, что один шейдер давно написанный для этого у них. Разложив все полученный знания с мультиков была выведена «формула пиксаровского глаза», а именно поняли, что они делают…
Для начала было сделано освещение радужки глаза не соответствующее освещению персонажа и даже белка на этом же глазе. Дополнительное освещение было реализовано руками от локатора висящего перед глазами. Локатор нужен для визуального контроля положения фэйкового освещения. Спекуляры с белка убрали и положили всего один с локатора.
Вот тут придется немного подробнее остановиться, так как именно в этой части возникло больше всего трудностей при написании шейдера.
Для начала мы заводим в шейдере три переменные float и пишем в них координаты нашего локатора:
float lx; – [mattr “locator1.translateX” $f]
float ly; – [mattr “locator1.translateY” $f]
float lz; – [mattr “locator1.translateZ” $f]
Дальше начинаются танцы с бубном. Для начала переводим три float координаты в один point элемент:
inP = point(lx,ly,lz);
Далее переводим систему координат world->current и уже с таким вот поинтом можно работать:
inP = transform(«world», “current”, inP);
Так же попутно вычисляем вектор от точки поверхности к локатору:
vector lv = vector(P-inP);
Полученный вектор lv мыиспользуем вместо вектора L для получения спекуляра и диффуза.
// Specular
Nf = faceforward( normalize(N), I );
vector V = -normalize( I );
vector H = normalize(-normalize(lv)+V);color spec = Ks * spec_color;
spec *= smoothstep(softness, 1-softness, pow(max(0, Nf.H), 10/roughness));// summ all components
Nf = faceforward( normalize(N), I );color ddd = mix(color(pow(normalize(lv).N,2)), diffuse(Nf), filterstep(rRadius, rrr));
CI = Kd * ddd + spec;
Обратим внимание на два пункта: спекуляр считается для всего глаза от локатора, а вот диффуз от локатора используется только внутри радиуса радужки (ddd – смешанный диффуз)
Так же не забываем о том, что спекуляр надо считать до выполнения дисплейса/бампа, а вот диффуз после, это даст эффект того, что глаз как бы из двух слоев, внутренний вогнутый внутрь с картинкой глаза, и внешний по которому бегает спекуляр.
Плюс к этому сразу было сделано еще одно дополнение. Координаты центра глаза не жестко забиты, а входящими параметрами, это позволяет перемещать глаз по UV и необходимо когда была прицеплена текстура ранее и не по цетру размаплена была. И раз уж было сделано смещение, то сразу добавил scale по обоим осям, на всякий случай…
Вот собственно и все, что я хотел рассказать.
©Nikopol.VFX