Вместо предисловия
Мы с LLlypuK'ом в очередной раз затеяли захват мира долгостроящийся проект, но на этот раз мы не гарантируем его выполнение... и это скорее даже не сама цель... Целью можно назвать изучение такой среды как Unity3d, а профит в получении некоторых навыков. Если получится из этого еще и игру сделать, то мы не расстроимся, а даже наоборот. Если кому интересно, проект имеет рабочее название Voluntarium, но это все, что мы пока о нем можем сказать.
О чем же я буду говорить? О том, какие интересные (сугубо на мой личный взгляд) решения мы нашли, чем можем поделиться и так далее. Ну что-ж.... первый блин -поехали.
О чем же я буду говорить? О том, какие интересные (сугубо на мой личный взгляд) решения мы нашли, чем можем поделиться и так далее. Ну что-ж.... первый блин -поехали.
Медленно к сути
Задался я целью соорудить на базе стандартного редактора ландшафта некий шейдер, который изменил бы внешний вид игры в лучшую сторону, так как стандартный весьма уныл... все чего мы добились я показывать не стану, но об одном аспекте рассказать стоит. Те кто пользовался стандартным ландшафтом заметили, что при рисовании невозможно изменить размер рисуемых текстур... с этого и начнем.
Нам понадобятся
Текстуры
https://xgm.guru/files/192/123998/Ashen_Grass_small.tga
https://xgm.guru/files/192/123998/Ashen_Vines_small.tga
https://xgm.guru/files/192/123998/Ashen_Rock_small.tga
https://xgm.guru/files/192/123998/Ashen_Vines_small.tga
https://xgm.guru/files/192/123998/Ashen_Rock_small.tga
я вежливо позаимствовал их у старичка WC3... и слегка подправил
Исходники шейдеров
Можно скачать отсюда
Или же взять здесь... нам понадобятся эти шейдеры:
Или же взять здесь... нам понадобятся эти шейдеры:
DefaultResourcesExtra\TerrainShaders\Splats\FirstPass.shader
Shader "Nature/Terrain/Diffuse" {
Properties {
[HideInInspector] _Control ("Control (RGBA)", 2D) = "red" {}
[HideInInspector] _Splat3 ("Layer 3 (A)", 2D) = "white" {}
[HideInInspector] _Splat2 ("Layer 2 (B)", 2D) = "white" {}
[HideInInspector] _Splat1 ("Layer 1 (G)", 2D) = "white" {}
[HideInInspector] _Splat0 ("Layer 0 (R)", 2D) = "white" {}
// used in fallback on old cards & base map
[HideInInspector] _MainTex ("BaseMap (RGB)", 2D) = "white" {}
[HideInInspector] _Color ("Main Color", Color) = (1,1,1,1)
}
SubShader {
Tags {
"SplatCount" = "4"
"Queue" = "Geometry-100"
"RenderType" = "Opaque"
}
CGPROGRAM
#pragma surface surf Lambert
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;
void surf (Input IN, inout SurfaceOutput o) {
fixed4 splat_control = tex2D (_Control, IN.uv_Control);
fixed3 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;
}
ENDCG
}
Dependency "AddPassShader" = "Hidden/TerrainEngine/Splatmap/Lightmap-AddPass"
Dependency "BaseMapShader" = "Diffuse"
Dependency "Details0" = "Hidden/TerrainEngine/Details/Vertexlit"
Dependency "Details1" = "Hidden/TerrainEngine/Details/WavingDoublePass"
Dependency "Details2" = "Hidden/TerrainEngine/Details/BillboardWavingDoublePass"
Dependency "Tree0" = "Hidden/TerrainEngine/BillboardTree"
// Fallback to Diffuse
Fallback "Diffuse"
}
DefaultResourcesExtra\TerrainShaders\Splats\AddPass.shader
Shader "Hidden/TerrainEngine/Splatmap/Lightmap-AddPass" {
Properties {
_Control ("Control (RGBA)", 2D) = "black" {}
_Splat3 ("Layer 3 (A)", 2D) = "white" {}
_Splat2 ("Layer 2 (B)", 2D) = "white" {}
_Splat1 ("Layer 1 (G)", 2D) = "white" {}
_Splat0 ("Layer 0 (R)", 2D) = "white" {}
}
SubShader {
Tags {
"SplatCount" = "4"
"Queue" = "Geometry-99"
"IgnoreProjector"="True"
"RenderType" = "Opaque"
}
CGPROGRAM
#pragma surface surf Lambert decal:add
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;
void surf (Input IN, inout SurfaceOutput o) {
fixed4 splat_control = tex2D (_Control, IN.uv_Control);
fixed3 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;
}
ENDCG
}
Fallback off
}
Ну и базовое понимание, что такое шейдеры, UV координаты, и как это все работает в unity3d.
К делу
- Для начала создадим Terrain в Unity3d. Добавим в него три наши текстуры... и что нибудь ими нарисуем. У меня лично получилось что-то подобное:
Да... не очень красиво, но нам лишь для понимания сути.
Начнем с создания своего материала и двух шейдеров с тем кодом, который приведен выше.
Внесем в первый шейдер правки:
Начнем с создания своего материала и двух шейдеров с тем кодом, который приведен выше.
Внесем в первый шейдер правки:
//заменим строчку
Shader "Nature/Terrain/Diffuse"
//на эту
Shader "MyShader/Terrain"
//а эту
Dependency "AddPassShader" = "Hidden/TerrainEngine/Splatmap/Lightmap-AddPass"
//на эту
Dependency "AddPassShader" = "Hidden/Terrain/AddPass"
а во втором шейдере правки будут такими
//заменим строчку
Shader "Hidden/TerrainEngine/Splatmap/Lightmap-AddPass"
//на эту
Shader "Hidden/Terrain/AddPass"
Этим самым мы сменим путь, по которому их будет искать Unity3d в своей библиотеки шейдеров в рамках нашего проекта.
Стоит немного рассказать о роли данных шейдеров. Первый - это основной шейдер, который занимается смешиванием текстур на основе управляющей текстуры, а точнее он занимается смешиванием первых 4х текстур, для всех последующих четверок вызывается второй шейдер. Для этого в первом и создается зависимость:
Стоит немного рассказать о роли данных шейдеров. Первый - это основной шейдер, который занимается смешиванием текстур на основе управляющей текстуры, а точнее он занимается смешиванием первых 4х текстур, для всех последующих четверок вызывается второй шейдер. Для этого в первом и создается зависимость:
Dependency "AddPassShader" = "Hidden/Terrain/AddPass"
Как работает сам шейдер? Очень просто. Когда вы водите кистью по ландшафту, внутренний скрипт рисует в одном из каналов так называемой управляющей текстуры (в шейдере это текстура _Control). То есть красный канал - степень влияния первой текстуры, зеленый - второй, синий - третьей и альфа канал - степень влияния на итог текстуру четвертой текстуры. Для последующих текстур, создаются дополнительный управляющие текстуры (по одной на каждую дополнительную четверку). В самом шейдере просто перемножаются текстуры с их степенью влияния, складываются и выводится результат. Так как скрипт отслеживает редактирования всех контрольных текстур таким образом, чтоб сумма всех влияний в каждой точке не превышала 1, то у нас на экране не мешанина из пикселей, а вполне вменяемая картинка. Каждый проход шейдера накладывается на предыдущие аддитивным блендингом. За что отвечает вот эта строчка во втором шейдере:
#pragma surface surf Lambert decal:add //а точнее даже вот эта ее часть: decal:add
Назначьте вашему материалу шейдер MyShader -> Terrain, затем поменяйте у ландшафта стандартный материал, на ваш:
Начнем небольшие правки
Все правки вноситься будут в оба файла в аналогичные разделы, потому я не буду лишний раз писать, что изменения надо дублировать. Для начала развяжем себе руки и впишем после
#pragma surface surf Lambert
вот это
#pragma target 3.0
Этим самым мы указали, что наши шейдеры будут использовать третью модель шейдеров. Тем самым отсекли очень старые видеокарточки, но увеличили количество доступных в шейдере вычислений... так или иначе в конце нам это понадобится.
Затем в раздел Properties добавим следующее:
Затем в раздел Properties добавим следующее:
_Scale ("Texture Scale", Float) = 1
а после
sampler2D _Splat0,_Splat1,_Splat2,_Splat3;
добавим
float _Scale;
это будет наш масштаб. Его мы можем увидеть в свойствах нашего материала:
но его изменения нам пока ничего не дадут. Для того, чтоб он заработал, давайте изменим UV координаты наших текстур. Для этого заменим функцию
void surf (Input IN, inout SurfaceOutput o) {
fixed4 splat_control = tex2D (_Control, IN.uv_Control);
fixed3 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;
}
на
void surf (Input IN, inout SurfaceOutput o) {
float2 realUV0, realUV1, realUV2, realUV3;
realUV0 = IN.uv_Splat0 * _Scale;
realUV1 = IN.uv_Splat1 * _Scale;
realUV2 = IN.uv_Splat2 * _Scale;
realUV3 = IN.uv_Splat3 * _Scale;
fixed4 splat_control = tex2D (_Control, IN.uv_Control);
fixed3 col;
col = splat_control.r * tex2D (_Splat0, realUV0).rgb;
col += splat_control.g * tex2D (_Splat1, realUV1).rgb;
col += splat_control.b * tex2D (_Splat2, realUV2).rgb;
col += splat_control.a * tex2D (_Splat3, realUV3).rgb;
o.Albedo = col;
o.Alpha = 0.0;
}
Сохраним и вернемся в редактор. Если вы все сделали правильно, то при изменении добавленного нами параметра в материале, должны меняться и размеры текстур. Например вот так у меня теперь выглядит ландшафт для параметров 2 и 0.2 соответственно без изменений на самой сцене и неизменном положении камеры.
При большом удалении заметна "регулярность" текстур... что не так приятно... Избавимся от этого!
Следующий шаг
Я на самом деле изначально схитрил, объединив 4 тайла в одну текстуру... теперь моя идея заключается в следующем: порезать каждую текстуру на 4 куска, и вместо самой текстуры выводить рандомно взятый кусок.
Чтоб этого добиться введем параметр
Чтоб этого добиться введем параметр
_TileCount ("Texture grid size", Float) = 2.0
и переменную
float _TileCount;
так же как мы это делали со _Scale. Плюс... мы напишем еще две функции:
float2 rand2(float2 n)
{
float2 result;
result.x = frac(sin(fmod(dot(n.xy, float2(12.9898, 78.233)),3.14)) * 43758.5453);
result.y = frac(sin(fmod(dot(n.yx, float2(12.9898, 78.233)),3.14)) * 43758.5453);
return result;
}
float2 TextOffset(float2 n)
{
float2 result;
result = rand2(floor(n));
result = floor(result * _TileCount)/_TileCount;
return result;
}
//вставить их надо перед "void surf (Input IN, inout SurfaceOutput o) {"
Введенный нами параметр - это количество тайлов в текстуре, по одной стороне. То есть в нашем случае это 2. Текстура 2 на 2 тайла.
Первая - функция это псевдорандом для шейдеров. Взят из интернета, для наших целей он вполне сгодится. Его минус в том, что при передаче в него одинаковых векторов, мы получаем одинаковые значения... но так как у нас для одних и тех же ячеек в течении всей игры тайл должен быть одним и тем же... нас это вполне устроит. Возвращает эта функция вектор со случайными координатами.
Вторая функция интереснее. В нее мы будем передавать UV координаты самой текстуры. Сам ландшафт отдает их не от 0 до 1, а от 0 до n*1, где n - это во сколько раз сам ландшафт больше нашей текстуры... ну или сколько раз она в него войдет. Переданные UV мы обрезаем до целой части и передаем рандому. Обрезаем мы его для того, чтоб для всего квадрата выдавалось одно и то же значение (например для 2.4 и 2.8 это всегда будет 2). Рандом возвращает значения от 0 до 1. Мы умножаем полученное на количество тайлов по одной стороне текстуры. Тем самым расширяя диапозон до "от 0 до _TileCount". Полученное число мы обрезаем до целой части, получая "случайно" выбранный номер тайла... а затем снова делим его на _TileCount, получая смещение UV координат тайла в текстуре.
Далее нам следует изменить функцию surf, и добавить после
Первая - функция это псевдорандом для шейдеров. Взят из интернета, для наших целей он вполне сгодится. Его минус в том, что при передаче в него одинаковых векторов, мы получаем одинаковые значения... но так как у нас для одних и тех же ячеек в течении всей игры тайл должен быть одним и тем же... нас это вполне устроит. Возвращает эта функция вектор со случайными координатами.
Вторая функция интереснее. В нее мы будем передавать UV координаты самой текстуры. Сам ландшафт отдает их не от 0 до 1, а от 0 до n*1, где n - это во сколько раз сам ландшафт больше нашей текстуры... ну или сколько раз она в него войдет. Переданные UV мы обрезаем до целой части и передаем рандому. Обрезаем мы его для того, чтоб для всего квадрата выдавалось одно и то же значение (например для 2.4 и 2.8 это всегда будет 2). Рандом возвращает значения от 0 до 1. Мы умножаем полученное на количество тайлов по одной стороне текстуры. Тем самым расширяя диапозон до "от 0 до _TileCount". Полученное число мы обрезаем до целой части, получая "случайно" выбранный номер тайла... а затем снова делим его на _TileCount, получая смещение UV координат тайла в текстуре.
Далее нам следует изменить функцию surf, и добавить после
realUV0 = IN.uv_Splat0 * _Scale;
realUV1 = IN.uv_Splat1 * _Scale;
realUV2 = IN.uv_Splat2 * _Scale;
realUV3 = IN.uv_Splat3 * _Scale;
эти строчки
realUV0 = (frac(realUV0)/_TileCount + TextOffset(realUV0));
realUV1 = (frac(realUV1)/_TileCount + TextOffset(realUV1));
realUV2 = (frac(realUV2)/_TileCount + TextOffset(realUV2));
realUV3 = (frac(realUV3)/_TileCount + TextOffset(realUV3));
Что мы делаем этим? Во первых мы переходим к честным UV координатам от 0 до 1, вычленяя дробную часть из них функцией frac. Затем мы делим полученное на _TileCount, чтоб получить координаты не от 0 до 1 (то есть всей текстуры), а только одного тайла. Затем делаем поправку на смещение... и получаем нужные нам координаты. Сохраняем шейдеры, идем в редактор и ставим значение нового параметра в 2.
Если все сделали правильно, то должно получится что-то вроде:
Далее можно сделать еще одно. Заменить текстуры на следующие (в них не 4 тайла, а 16):
https://xgm.guru/files/192/123998/Ashen_Grass.tga
https://xgm.guru/files/192/123998/Ashen_Rock.tga
https://xgm.guru/files/192/123998/Ashen_Vines.tga
https://xgm.guru/files/192/123998/Ashen_Rock.tga
https://xgm.guru/files/192/123998/Ashen_Vines.tga
И изменить параметр отвечающий за количество текстур с 2, до 4... у меня получилось что-то такое:
Вместо послесловия
Собственно жду отзывов, как и от тех, кто крут в этом (а ведь я только учусь, не претендую на лавры), так и тех, кто только хочет заняться изучением unity3d и шейдеров в том числе. Будет интересны ваши отзывы, предложения и вопросы.
Ред. Mihahail
Я конечно понимаю, что тут все миллионеры и живут в столицах, где дешевый быстрый безлимитный интернет, но...