Пользователь
0,0
рейтинг
24 мая 2013 в 02:07

Разработка → Смешивание текстур ландшафта tutorial



В данной статье я расскажу об алгоритме смешивания текстур, который позволяет привести внешний вид ландшафта ближе к естественному. Этот алгоритм легко может быть использован как в шейдерах 3D игр, так и в 2D играх.

Статья рассчитана на начинающих разработчиков игр.

Один из наиболее распространенных способов текстурирования ландшафта заключается в смешивании текстур нескольких слоев. Эти слои накладываются друг на друга, формируя тем самым внешний вид ландшафта. При этом каждый слой дополнительно имеет карту прозрачности, которая определяет степень присутствия текстуры на ландшафте. Она измеряется в процентах. Естественно, что в каждой точке ландшафта сумма степеней прозрачности по всем слоям составляет сто процентов, так как ландшафт не может быть прозрачным. В отличие от текстур тайлов, карта прозрачности растягивается целиком на весь ландшафт и поэтому имеет довольно низкий уровень детализации.

Теперь перейдем к самому интересному — алгоритмам смешивания текстур. Для простоты и наглядности наш ландшафт будет состоять из песка переходящего в крупные булыжники.



Самый простой способ смешивания — перемножить цвет текстуры и прозрачность, а результаты просуммировать.
float3 blend(float4 texture1, float a1, float4 texture2, float a2)
{
	return texture1.rgb * a1 + texture2.rgb * a2;
}

Именно такая техника используется в Unity 3D в стандартном редакторе ландшафта. В глаза сразу бросается плавный, но неестественный переход. Камни выглядят равномерно испачканными песком, а ведь так не бывает в реальности. Песок не прилипает к камням, наоборот, он осыпается и заполняет щели между ними, оставляя верхушки камней более чистыми.

Давайте попробуем смоделировать эту ситуацию в самом обыкновенном Excel с его таблицами и графиками. Так как мы хотим, чтобы песок «проваливался» между булыжниками, для каждой текстуры нам понадобится карта ее глубины. В данном примере я сгенерировал ее из самой текстуры в оттенках серого и поместил в альфа-канал.

Первым делом рассмотрим упрощенную модель карты глубин песка и камней.


Синей линией условно обозначена карта высот песка, а красной — булыжников. Можно заметить, что верхушки булыжников выступают над уровнем песка. Учитывая этот факт, попробуем выводить пиксели той текстуры, которая находится выше.
float3 blend(float4 texture1, float a1, float4 texture2, float a2)
{
	return texture1.a > texture2.a ? texture1.rgb : texture2.rgb;
}



Отлично! Верхушки камней остаются чистыми, тогда как песок кажется осыпавшимся в щели между ними. Но мы еще не учли степени прозрачности слоев. Для этого мы просто сложим карту высот с картой прозрачности.
float3 blend(float4 texture1, float a1, float4 texture2, float a2)
{
	return texture1.a + a1 > texture2.a + a2 ? texture1.rgb : texture2.rgb;
}

За счет суммирования менее прозрачная текстура будет находиться выше, чем обычно.




Так мы получили более естественный переход от песка к камням. Видно как песчинки начинают заполнять промежутки между булыжниками, постепенно скрывая их за собой. Но так как отрисовка происходит попиксельно, на границе между текстурами стали проступать артефакты. Чтобы как-то их сгладить, мы будем брать не один, а несколько пикселей в глубину и смешивать их.
float3 blend(float4 texture1, float a1, float4 texture2, float a2)
{
	float depth = 0.2;
	float ma = max(texture1.a + a1, texture2.a + a2) - depth;

	float b1 = max(texture1.a + a1 - ma, 0);
	float b2 = max(texture2.a + a2 - ma, 0);

	return (texture1.rgb * b1 + texture2.rgb * b2) / (b1 + b2);
}

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


А потом нормализуем его для того, чтобы получить новые степени прозрачности.




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

В заключение я хочу рассказать о том, для чего был разработан этот алгоритм и как мы его используем.

Шейдер разрабатывался для инди-игры Steam Squad в жанре изометрической 2D стратегии. В качестве фреймворка для разработки мы используем Unity 3D. А так как среда разработки Unity чрезвычайно гибка, мы сделали свое расширение — редактор уровней. По большому счету, редактор является упрощенной копией стандартного редактора ландшафтов с элементами взятыми из редактора игры Titan Quest.

При рисовании текстурой по ландшафту происходит пересчет соответствующей ей карты прозрачности. А так как сумма всех степеней прозрачности должна составлять 100%, карты прозрачности всех остальных слоев нормализуются. Как это выглядит в самом редакторе можно посмотреть на коротком видео.

Андрей Мишкинис @AndreyMI
карма
51,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (27)

  • +10
    Исходника шейдера не будет?
    • +6
      Оригинальный шейдер является частью коммерческого проекта, поэтому его исходников не будет.
      Но если вы хотите повторить эксперимент в Unity 3D, то создайте сцену с плоскостью и назначьте ей следующий шейдер:
      Код шейдера из статьи
      Shader "Custom/Demo" {
      	Properties {
      		_Tex1 ("Texture 1 (RGB) Depth (A)", 2D) = "white" {}
      		_Tex2 ("Texture 2 (RGB) Depth (A)", 2D) = "white" {}
      	}
      	SubShader {
      		Tags { "RenderType"="Opaque" }
      		LOD 200
      		
      		CGPROGRAM
      		#pragma surface surf Lambert
      
      		sampler2D _Tex1;
      		sampler2D _Tex2;
      
      		struct Input {
      			float2 uv_Tex1;
      			float2 uv_Tex2;
      		};
      
      		float3 blend(float4 texture1, float a1, float4 texture2, float a2)
      		{
      			float depth = 0.2;
      			float ma = max(texture1.a + a1, texture2.a + a2) - depth;
      			float b1 = max(texture1.a + a1 - ma, 0);
      			float b2 = max(texture2.a + a2 - ma, 0);
      			return (texture1.rgb * b1 + texture2.rgb * b2) / (b1 + b2);
      		}
      
      		void surf (Input IN, inout SurfaceOutput o) {
      			half4 c1 = tex2D (_Tex1, IN.uv_Tex1);
      			half4 c2 = tex2D (_Tex2, IN.uv_Tex2);
      			o.Albedo = blend(c1, IN.uv_Tex2.x, c2, 1 - IN.uv_Tex2.x);
      		}
      		ENDCG
      	} 
      	FallBack "Diffuse"
      }
      

      • 0
        Нашу реализацию данного алгоритма можно найти на Unity3D Asset Store
  • +3
    Как это разширяется на >2 леера?
    Филлрейт явно сильно выше (особенно если надо еще и нормалмапы смешивать)

    Но решение интересное

    • +2
      Как это разширяется на >2 леера?
      Последовательно расширяется. Смешали 2 компоненты — результат смешиваем с третьей. Поскольку максимум — ассоциативная и коммутативная функция, порядок не важен.
    • +1
      Очень просто. Добавьте еще один слой в графики:)
      Или, как уже заметил mayorovp, последовательное расширение.



      В шейдере для своей игрушки я смешиваю 4 разных текстуры вместе с картами нормалей и одну карту шума.
      После игр с математическими операциями мне даже удалось уложиться в ограничения SM2.0.
      • +1
        Тоесть для 3ех лееров это будет выглядеть вот так?

        float3 blend(float4 texture1, float a1, float4 texture2, float a2,float4 texture3, float a3)
        {
            float depth = 0.2;
            float ma = max(max(texture1.a + a1, texture2.a + a2),texture3.a + a3) - depth;
        
            float b1 = max(texture1.a + a1 - ma, 0);
            float b2 = max(texture2.a + a2 - ma, 0);
            float b3 = max(texture3.a + a3 - ma, 0);
        
            return (texture1.rgb * b1 + texture2.rgb * b2+texture3.rgb * b3) / (b1 + b2+b3);
        }
        
        • +1
          Совершенно верно
    • 0
      Филрейт будет больше относительно какого метода?
  • +1
    float3 blend(float4 texture1, float a1, float4 texture2, float a2) { return texture1.a > texture2.a ? texture1.rgb : texture2.rgb; }
    texture1.a и texture2.a — это что за переменная? откуда берется?
    • +2
      texture1 и texture2 — это цвета пикселя на текстуре. В коде вызовы texture2D убраны.
  • +2
    Красиво, но ?: в шейдере? Как так можно?
    // второй шейдер
    mix(texture1.rgb, texture2.rgb, step(texture1.a, texture2.a));
    // третий шейдер
    mix(texture1.rgb, texture2.rgb, step(texture1.a + a1, texture2.a + a2));
    
    • 0
      В основном для наглядности, в конечном шейдере нет условных переходов.

      Не вижу mix в Cg, наверно lerp?
      • 0
        Да, lerp. Я на GLSL написал.
  • +3
    Увидев картинку в шапке, рассчитывал на статью побольше.

    Допустим нам надо сделать такой методикой островок камушков посреди песка. Как с этим справиться, используя Ваш алгоритм?
    То, что Вы показали очень частный случай и в нем-то всё хорошо, красиво и просто. Мой вопрос связан, по сути, с непосредственным использованием Вашего алгоритма на практике. Давайте для простоты решим что мы делаем стратегию в 2д.
    • +4
      Шейдер как раз и разрабатывался для изометрической 2D стратегии.
      С островками никаких проблем нет, мы просто рисуем их на карте прозрачности слоев.
      Карта прозрачности



      • +2
        Тоесть карту прозрачности готовите заранее, или на лету делаете?
        • 0
          Карта прозрачности готовится прямо в Unity в самописном редакторе.
          Но она легко может меняться даже непосредственно в игре.
          • +3
            Может быть тогда стоит написать в начале статьи, что код и реализация на Unity?

            Мне вот, как разработчику под собственный фреймворк, и пишущем на hlsl этот код шейдера немного непривычен :)
            В начале я даже было подумал что это псевдокод шейдера.
            • +2
              Я старался сделать упор именно на алгоритме, а не на конкретной реализации.
            • +1
              Это не целые шейдеры же, а только одна функция. А Cg и HLSL практическип одинаковы.
              А если заменить float3 на vec3 и float4 на vec4 (или написать #define float3 vec3 и #define float4 vec4), получится GLSL.
  • +7
    Я никогда графикой не занимался, поэтому прошу прощения, если вопрос тривиальный.
    Откуда Вы получили карту высот? Сгенерировали автоматически из текстур? Сделали руками «на глаз»? Или она была получена при генерировании текстуры?
    • +5
      В данном примере я сгенерировал ее из самой текстуры в оттенках серого и поместил в альфа-канал.

      Думаю речь идет о использовании информации о яркости пикселя.
      • +1
        Да, именно так
  • +2
    Круто! Идею понял, возьму себе на заметку… если вдруг буду что-то писать.
  • +4
    Думал будет урок фотошопа… Стыдно
  • 0
    Хм, если грамотно выразится, это называется сплатинг ландшафта.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.