Шейдер глаза 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

Leave a Reply

*
To prove you're a person (not a spam script), type the security word shown in the picture.
Anti-Spam Image

What's a blog without spam? WP-Hashcash.