Как стать автором
Поиск
Написать публикацию
Обновить

Unity3D 3.х Terrain Bump Specular Shader

Время на прочтение4 мин
Количество просмотров16K
На данный момент Unity3D не поддерживает наложение на встроенный ландшафт карты нормалей и отражения(specular). Гугление по этому поводу принесло не очень впечатляющие результаты в виде вот этого шейдера и некоторых его модификаций. Воодушевившись картинкой и скачав архив меня постигло разочарование. Во-первых для работы шейдера на ландшафт необходимо вешать скрипт которым управляется шейдер (что очень неудобно), а во-вторых в данной реализации больше 4х карт нормалей нельзя назначить.
В этой статье я опишу процесс создания собственного шейдера для ландшафта, параллельно рассказав как работает стандартный шейдер.

Механизм отрисовки ландшафта в стандартных шейдерах юнити


Для отрисовки ландшафта в юнити используется два шейдера:
Hidden/TerrainEngine/Splatmap/Lightmap-FirstPass и Hidden/TerrainEngine/Splatmap/Lightmap-AddPass, скачать их можно здесь

Первый шейдер рисует первые 4 текстуры ландшафта. Второй шейдер рисует последовально остальные текстуры по 4 за раз, пока не закончатся текстуры.

Итак что передается в шейдер из движка:

	...
	struct Input {
		float2 uv_Control : TEXCOORD0;
		float2 uv_Splat0 : TEXCOORD1;
		float2 uv_Splat1 : TEXCOORD2;
		float2 uv_Splat2 : TEXCOORD3;
		float2 uv_Splat3 : TEXCOORD4;
	};

	sampler2D _Control;
	sampler2D _Splat0,_Splat1,_Splat2,_Splat3;
	...
	


_SplatX — текстура с материалом
_Control — управляющая карта. Это текстура в которой каждый из каналов задает яркость одного из материалов в определенной точке. Управляющая карта создается на основе карты материалов ландшафта (Alphamaps) для каждой четверки материалов в недрах движка. Именно потому что у управляющей текстуры 4 канала, шейдеры рендерят не больше чем по 4 материала за раз.




Разберем что дальше происходит в шейдере:

Он имеет единственную процедуру, в которой считается цвет текущей точки (o.Albedo), и он равен сумме произведений яркости точки из управляющей карты RGBA и ее цвета из текстуры материала.

	...
	void surf (Input IN, inout SurfaceOutput o) {
		half4 splat_control = tex2D (_Control, IN.uv_Control);
		half3 col;
		col  = splat_control.r * tex2D (_Splat0, IN.uv_Splat0).rgb;
		col += splat_control.g * tex2D (_Splat1, IN.uv_Splat1).rgb;
		col += splat_control.b * tex2D (_Splat2, IN.uv_Splat2).rgb;
		col += splat_control.a * tex2D (_Splat3, IN.uv_Splat3).rgb;
		o.Albedo = col;
		o.Alpha = 0.0;
	}
	...
	


Результат его работы можно увидеть ниже:



Засветка в данном случае произошла из-за наложения нескольких материалов(каналов) на управляющей карте, в реальных условия такого не должно быть, т.к. обычно один материал на ландшафте плавно переходит в другой, и довольно в редких случаях нужно накладывать один материал на другой.

Второй шейдер я рассматривать не буду, т.к. он практически идентичен, только используется для текстур с индексом выше 3.

Создание собственного шейдера

Так как в одном проходе может быть только 4 материала и не хочется прибегать к помощи скриптов для назначения нормалей шейдеру, будем засовывать нормали через ландшафт, как показано на картинке ниже.
Теперь каждая вторая текстура на ландшафте является нормалью к предыдущему материалу. Важно чтобы в инспекторе у этой текстуры был выставлен тип — нормаль. Кроме того у нас есть неиспользуемый канал A в текстуре материала, в который отлично помещается карта Specular.



Новая процедура surf:

void surf (Input IN, inout SurfaceOutput o) {
	fixed4 splat_control = tex2D (_Control, IN.uv_Control);
	fixed3 col;
	fixed spec;
	
//Получим RGBA точки из первого материала
	fixed4 d1 = tex2D (_Splat0, IN.uv_Splat0);
//Получим RGBA точки из второго материала
	fixed4 d2 = tex2D (_Splat2, IN.uv_Splat2);

//Получим нормаль точки от первого материала
	fixed3 n1 = UnpackNormal( tex2D (_Splat1, IN.uv_Splat1) );
//Получим нормаль точки от второго материала
	fixed3 n2 = UnpackNormal( tex2D (_Splat3, IN.uv_Splat3) );
	
//Меняем цвет точки в соответствии с управляющей картой
	col = splat_control.r * d1.rgb;
//Интерполируем нормаль (ниже описано подробней)
	o.Normal =  normalize(lerp(fixed3(0.5,0.5,1), n1, clamp(splat_control.r + 0.3,0,1)));
//Меняем степень отблеска в обратно пропорционально альфа каналу материала, чтобы текстура без альфы не бликовала. "0.1" - максимальная степень отблеска, поменяйте на свой вкус.
	spec = (1 - d1.a) * splat_control.r * 0.1;
	
//Повторяем для второго материала и складываем с первым
	col += splat_control.b * d2.rgb;
	o.Normal += normalize(lerp(fixed3(0.5,0.5,1), n2, clamp(splat_control.b + 0.3,0,1)));
	spec += (1 - d2.a) * splat_control.b * 0.1;

//Немного убавляем яркость, чтобы соответствовало basemap'у
	o.Albedo = col * 0.5;
//Задаем отблеск
	o.Specular = spec;
	o.Gloss = spec;
	o.Alpha = 0.0;
}


В процедуре все должно быть понятно, так как кроме арифметических операций там больше почти ничего нет. Единственное, что хотелось бы разобрать — вот эту строчку:
o.Normal = lerp(fixed3(0.5,0.5,1), n1, clamp(splat_control.r + 0.3,0,1));
Нормаль — это единичный вектор перпендикулярный к поверхности. И так как нам нужно ее плавно уменьшать, мы не можем просто умножать ее на какой-то коэффициент. Для решения этой задачи я интерполирую текущую нормаль в т.н. «нулевую нормаль», при которой на текстуре не будет никакого рельефа.

Чтобы карта нормалей могла примениться, на мехе должны быть посчитанные тангенсы (вектор перпендикулярный нормали и параллельный поверхности, направленный в сторону увеличения координаты U на развертке). Обычно их считает ПО, в котором разрабатывается модель, но так как ландшафт строится в юнити «на лету», то тангенсов там нет.
Придется посчитать тангенсы внутри шейдера самим:

void vert (inout appdata_full v) {
	fixed3 T1 = float3(1, 0, 0);
	if (dot(T1,v.normal) > 0.99) {
		T1 = float3(0,1,0); //workaround
	}
	fixed3 Bi = cross(T1, v.normal);
	fixed3 newTangent = normalize(cross(v.normal, Bi));
	v.tangent.xyz = newTangent.xyz;
	if (dot(cross(v.normal,newTangent),Bi) < 0)
		v.tangent.w = -1.0f;
	else
		v.tangent.w = 1.0f;
}


Шейдер для последующих проходов практически идентичен.

Для тех кто создает ландшафт динамически, не забудьте исправить индексы материалов в коде. Они должны быть умножены на два, т.к. по нечётным индексам лежат нормали.

materials[x, z, material_number*2] = 1;

Минусы данного метода:
  • Незначительно повышается Draw Calls из-за большего количества проходов шейдера
  • Надо следить за тем чтобы не нарисовать на ландшафте неправильным материалом (нормалью)
  • Использует более 8 регистров, что делает невозможным компиляцию под Flash
  • Использует модель Shader 3.0, что делает невозможной работу на старом железе (из-за 64+ операций)

Кратко, для тех кому лень читать:
  • Шейдер рисует ландшафт с картами нормалей и спекулара
  • Текстуры на ландшафте должны чередоваться через одну. Диффузка / Нормаль / Диффузка / Нормаль и т. д.
  • Спекулар карта хранится в альфа канале диффузки

Результат со стандартной текстурой, и картами снятыми с нее (стало / было):


Скачать шейдер
Теги:
Хабы:
Всего голосов 22: ↑19 и ↓3+16
Комментарии24

Публикации

Ближайшие события