Главная Статьи Ссылки Скачать Скриншоты Юмор Почитать Tools Проекты Обо мне Гостевая Форум |
Иногда возникает ситуация, когда нужно совместить несколько карт нормалей. Обычно, чаще всего подобная ситуация возникает в случае карт детализации нормалей - карт, добавляющих нормалям мелкую детализацию. Т.е. у нас помимо основной карты нормалей есть есть также детализированная карта нормалей и мы хотим при рендеринге наложить их друг на друга.
Рис 1. Основная карта нормалей
Рис 2. Карта детализации
Самый простой вариант наложения карт номралей - просто сложить нормали или использовать режим overlay из Photoshop. К сожалению это дает не самый хороший результат.
Рис 3. Результат линейного наложения нормалей.
Рис 4. Результат наложения нормалей через режим overlay.
vec3 blendLinear ( vec4 n1, vec4 n2 )
{
vec3 r = (n1.xyz + n2.xyz) * 2 - 2;
return normalize ( r );
}
vec3 blendOverlay ( vec4 n1, vec4 n2 )
{
n1 = n1*4 - 2;
vec4 a = -sign ( n1 );
vec4 b = sign ( sign ( n1 ) + vec4 ( 0.5 ) );
b = 0.5 * ( b + vec4 ( 1.0 ) );
n1 = 2*a + n1;
n2 = n2*a + b;
vec3 r = n1.xyz * n2.xyz - a.xyz;
return normalize ( r );
}
Давайте рассмотрим почему качество наложения нормалей получается невысокое. Для этого вспомним откуда вообще возникают карты нормалей - это нормированные вектора, получаюшщиеся из дифференцирования карты высот (heightmap) -
n'=(hx, hy,1)
И вот карты высот (как и их прозводные мы как раз можем складывать. Поэтому если мы поделим вектор нормали на его z-компоненту, то мы получим вектор
n'=(hx, hy,1).
И мы моржем просто сложить эти вектора и после этого пронормировать. Ниже приведено изображение, полученное этим способом.
Рис. 5. Результат наложения нормалей через восстановление производных
Ниже приведен соответствующий код.
vec3 blendPd ( vec4 n1, vec4 n2 )
{
n1 = n1*2 - 1;
n2 = n2.xyzz*vec4(2, 2, 2, 0) + vec4(-1, -1, -1, 0);
vec3 r = n1.xyz*n2.z + n2.xyw*n1.z;
return normalize ( r );
}
Есть два упрощения данного варианта - whiteout blending и UDN blend.
Рис 6. Результат применения whiteout blending.
Рис 7. Результат применения UDN blend.
Ниже приводится соответствующий код.
vec3 blendWhiteout ( vec4 n1, vec4 n2 )
{
n1 = n1*2 - 1;
n2 = n2*2 - 1;
vec3 r = vec3 ( n1.xy + n2.xy, n1.z*n2.z );
return normalize ( r );
}
vec3 blendUdn ( vec4 n1, vec4 n2 )
{
vec3 c = vec3 ( 2, 1, 0 );
vec3 r = n2.xyz*c.yyz + n1.xyz;
r = r*c.xxx - c.xxy;
return normalize ( r );
}
Однако кроме данного подхода, основанного на дифференцировании карты высот, есть и другой подход. В его основе лежит довольно простая идея, но требующая более сложной математики.
Каждая карта нормалей по своей сути задает поворот, который переводит геометрическую нормаль к грани (0,0,1) в некоторую другую нормаль n1. Аналогично вторая карта нормалей задает поворот (0,0,1) в нормаль n2. Тогда результатом совмещения обоих карт нормалей будет комбинация этих двух поворотов.
Любой поворот в 3D может быть представлен при помощи кватерниона. Если у нас есть поворот, переводяшщий вектор v0 в вектор v1, то соответствующий кватернион задается формулой
Тогда каждая карта нормалей в точке задает свой единичный кватернион. Причем один из векторов имеет вид (0,0,1), что упрощает выкладки. Тогда композиция этих поворотов (т.е. произведение соответствующих кватернионов) и будет задавать итоговый поворот. Выкладки довольно громоздки, поэтому ниже приводится только итоговый результат.
Данный метод получил название reoriented normal mapping (RNM). В демо от Unity усполльзуется метод, основанный на похожей идее. Ниже приводятся результаты этого метода и нескольких его вариаций.
Рис. 8. Reoriented normal mapping
Рис 9. Модификация Reoriented normal mapping (unity)
vec3 blendRnm ( vec4 n1, vec4 n2 )
{
vec3 t = n1.xyz*vec3( 2, 2, 2) + vec3(-1, -1, 0);
vec3 u = n2.xyz*vec3(-2, -2, 2) + vec3( 1, 1, -1);
vec3 r = t*dot(t, u) - u*t.z;
return normalize ( r );
}
vec3 blendUnity ( vec4 n1, vec4 n2 )
{
n1 = n1.xyzz*vec4(2, 2, 2, -2) + vec4(-1, -1, -1, 1);
n2 = n2*2 - 1;
vec3 r;
r.x = dot(n1.zxx, n2.xyz);
r.y = dot(n1.yzy, n2.xyz);
r.z = dot(n1.xyw, -n2.xyz);
return normalize ( r );
}
По этой ссылке можно скачать весь исходный код к этой статье.