На этапе планирования ролика The Blacksmith мы никогда не рассматривали всерьез создание специального шейдера для кожи. Нам требовалось простое решение, которое позволило бы сделать мимику антагониста более реалистичной. Недолго думая, мы попробовали добавить в проект карты морщин на основе блендшейпов.
Мы решили, что для детализации мимики персонажа выгоднее всего будет просто добавить карты нормалей и карты затенений в стандартный шейдер. Кроме того, мы должны были найти способ ограничить влияние эмоций на соответствующие участки лица.
Wrinkle Maps Driver
Мы создали специальный компонент для определения слоев морщин (по одному слою на блендшейп в меше), в состав которого включили текстурные карты, модификаторы силы воздействия, а также набор масок для наложения на текстуры лица. При этом каждый слой морщин мог воздействовать на различные участки лица (от одного до четырех).
Итак, нам нужна была возможность смешивать до четырех выражений лица в любой момент времени. Для этого требовалось целых 11 текстур, включая две базовые текстуры, восемь текстур детализации и одну текстуру маски. Поэтому, чтобы избежать дополнительных сложностей, мы решили создавать карты морщин на стадии пре-рендера. Для этой цели нам идеально подошел формат ARGB2101010. Благодаря ему нам удалось запаковать нормали в два 10-битных канала, а затенения – в третий канал. В каждом фрейме наш компонент определял блендшейпы с наибольшим влиянием и соответствующим образом распределял воздействие слоев рендера.
Когда все морщины были скомпонованы в экранном пространстве, нам осталось только перенаправить данные из карт нормалей и затенений в стандартный шейдер, который мы использовали для лицевого рендеринга. Другими словами, нам нужно было всего лишь добавить несколько строк в основную функцию шейдера поверхности.
// Sample occlusion and normals from screen-space buffer when wrinkle maps are active
#ifdef WRINKLE_MAPS
float3 normalOcclusion =
tex2D(_NormalAndOcclusion, IN.screenPos.xy / IN.screenPos.w).rgb;
o.Occlusion = normalOcclusion.r;
#ifdef _NORMALMAP
o.Normal.xy = normalOcclusion.gb * 2.f — 1.f;
o.Normal.z = sqrt(saturate(1.f — dot(o.Normal.xy, o.Normal.xy)));
#endif
#endif
Конечные результаты
На примере блендшейпа со злым выражением лица мы можем увидеть все мельчайшие детали, которые появляются на картинке благодаря картам морщин:
Кроме того, мы добавили несколько режимов отладки, которые значительно упростили визуализацию карт нормалей и затенений, а также позволили точно определять, за что отвечает каждый компонент на финальном изображении.
Попробуйте сами
Мы специально создали тестовый проект, который можно скачать на Asset Store. По сути, это всего несколько эмоций на примере лица антагониста из ролика The Blacksmith, но они могут послужить хорошим отправным пунктом для ваших проектов.
Мы решили, что для детализации мимики персонажа выгоднее всего будет просто добавить карты нормалей и карты затенений в стандартный шейдер. Кроме того, мы должны были найти способ ограничить влияние эмоций на соответствующие участки лица.
Wrinkle Maps Driver
Мы создали специальный компонент для определения слоев морщин (по одному слою на блендшейп в меше), в состав которого включили текстурные карты, модификаторы силы воздействия, а также набор масок для наложения на текстуры лица. При этом каждый слой морщин мог воздействовать на различные участки лица (от одного до четырех).
Итак, нам нужна была возможность смешивать до четырех выражений лица в любой момент времени. Для этого требовалось целых 11 текстур, включая две базовые текстуры, восемь текстур детализации и одну текстуру маски. Поэтому, чтобы избежать дополнительных сложностей, мы решили создавать карты морщин на стадии пре-рендера. Для этой цели нам идеально подошел формат ARGB2101010. Благодаря ему нам удалось запаковать нормали в два 10-битных канала, а затенения – в третий канал. В каждом фрейме наш компонент определял блендшейпы с наибольшим влиянием и соответствующим образом распределял воздействие слоев рендера.
Когда все морщины были скомпонованы в экранном пространстве, нам осталось только перенаправить данные из карт нормалей и затенений в стандартный шейдер, который мы использовали для лицевого рендеринга. Другими словами, нам нужно было всего лишь добавить несколько строк в основную функцию шейдера поверхности.
// Sample occlusion and normals from screen-space buffer when wrinkle maps are active
#ifdef WRINKLE_MAPS
float3 normalOcclusion =
tex2D(_NormalAndOcclusion, IN.screenPos.xy / IN.screenPos.w).rgb;
o.Occlusion = normalOcclusion.r;
#ifdef _NORMALMAP
o.Normal.xy = normalOcclusion.gb * 2.f — 1.f;
o.Normal.z = sqrt(saturate(1.f — dot(o.Normal.xy, o.Normal.xy)));
#endif
#endif
Конечные результаты
На примере блендшейпа со злым выражением лица мы можем увидеть все мельчайшие детали, которые появляются на картинке благодаря картам морщин:
Кроме того, мы добавили несколько режимов отладки, которые значительно упростили визуализацию карт нормалей и затенений, а также позволили точно определять, за что отвечает каждый компонент на финальном изображении.
Попробуйте сами
Мы специально создали тестовый проект, который можно скачать на Asset Store. По сути, это всего несколько эмоций на примере лица антагониста из ролика The Blacksmith, но они могут послужить хорошим отправным пунктом для ваших проектов.
beeruser
o.Normal.xy = normalOcclusion.gb * 2.f — 1.f;
o.Normal.z = sqrt(saturate(1.f — dot(o.Normal.xy, o.Normal.xy)));
Я так понимаю на негативный Z вы забили?