Войти
АртСтатьи

Немного о генерации текстур

Внимание! Этот документ ещё не опубликован.

Автор:

(это первая моя статья, и возможно, что тут непонятная муть или что она тут нафиг никому не сдалась)

Под текстурой понимается картинка, которая будет заполнять всю плоскость.
Зачастую размер картинки значительно меньше видимого куска заполняемой поверхности, поэтому при взгляде на такую поверхность возникает неприятное ощущение зацикленности.
Поэтому в некоторых играх вместо одной картинки применяется несколько, и для каждого квадратика-текстуроместа выбирается своя картинка.
Например, для такого набора из 4 картинок:
src | Немного о генерации текстур
Может получиться заполнение:
result | Немного о генерации текстур

Понятно, что даже если картинка одна, то она (чтобы не резать глаза швами) должна совмещаться сама с собой по краям, то есть для наглядности проще представить, что мы текстурируем не квадрат, а тор (красная линия - линия склейки квадрата):
tor2 | Немного о генерации текстур

Но для заполнения без периодов нам надо задуматься о генерации набора из N картинок так, чтобы каждая с каждой была согласована по краям. Для такой конструкции пришлось построить неоднородное топологическоие пространство, выглядит оно как 4 листа, склеенными границами, и заклеенными в тор. То есть в ближайшей окрестности общей линии склейки наше пространство выглядит так:
tor1 | Немного о генерации текстур

Точка, принадлежащая этому пространству, описывается так:

  TPoint = record   
    x, y: integer;
    l: integer; // номер слоя
  end;

Самая нетривиальная часть генератора текстур - это функция, которая ищет расстояние между двумя точками этого пространства, она потом пригодится: например, для текстуры воды интенсивность пиксела будет задана как сумма косинусов расстояний до точек из некоего заранее взятого набора.

Кратчайшая линия между двумя точками этого пространства может идти как напрямую между ними, если они из одного слоя (синяя линия), так и "отразившись от края" (общей линии склейки), для точек из разных слоев (красная линия), или даже, пройдя через разрез и выйдя с другой стороны (зелёная линия):
2points | Немного о генерации текстур
Для того, чтобы перебрать все эти случаи, нарисуем все возможные образы первой точки при отражениях и сдвигах:
синяя - это исходная точка
зелёные точки - образы при отражениях
красные точки - образы при сдвигах
серые - образы при комбинациях сдвигов и отражений, не имеют никакого смысла, добавлены, чтобы была лучше видна центральная матрица образов размером 3х3
images | Немного о генерации текстур
Искомое расстояние - минимальное из расстояний второй точки до всевозможных образов.
Видно, что если точка находится в левой верхней четверти, то влево и вверх имеет смысл только отражать, а вправо и вниз имеет смысл только сдвигать. Для других четвертей наблюдание аналогично.
Короче, так или иначе выделяется центральная матрица 3х3, до точек которой мы и будем смотреть расстояние.

  function PointDist(const p1, p2: TPoint): extended;
  var
    tx, ty: array [0 .. 2] of integer;
    i, j: integer;
    aDist: extended;
  begin
    // строим образы первой точки относительно второй
    // по икс
    // Half - половина стороны кварата
    // Size - сторона квадрата
    if P1.x <  Half then tx[0] :=        - 1 - P1.x else tx[0] := P1.x - Size;
    tx[1] := P1.x;
    if P1.x >= Half then tx[2] := 2*Size - 1 - P1.x else tx[2] := P1.x + Size;
    // по игрек
    if P1.y <  Half then ty[0] :=        - 1 - P1.y else ty[0] := P1.y - Size;
    ty[1] := P1.y;
    if P1.y >= Half then ty[2] := 2*Size - 1 - P1.y else ty[2] := P1.y + Size;

    Result := -1;
    // перебираем все образы точек
    for i := 0 to 2 do for j := 0 to 2 do
      if not odd(i and j) or (P1.l = P2.l) then begin // для точек из разного уровня не надо проверять центральный элемент
        aDist := TG.DistFunc(tx[i] - P2.x, ty[j] - P2.y); // применяем функцию метрики для разницы координат, чаще применяется sqrt(sqr(dx)+sqr(dy))
        if (aDist < Result) or (Result < 0) then Result := aDist;
      end;
  end;

А теперь, после того, как мы определили расстояние на этом топологическом пространстве, можно уже говорить о том, как генерировать текстуру.
Для генерации текстуры сначала по этому пространсту раскидывается некоторое количество точек.

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

sqrt(sqr(dx)*10 + sqr(dy));

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

abs(dx)+abs(dy);

Таким образом, для генерации текстуры надо определить функцию метрики, функцию получения "интенсивности пиксела" исходя из набора расстояний до точек из набора, а также функцию получения цвета пиксела из его интенсивности.
Дальше, при заполнении пространства текстурой можно сделать ограничение: чтобы для соседних квадратов выбиралась разная текстура, с разной чётностью номера. Тогда не требуется согласования для слоёв одной чётности. Пространство для такого условия выглядит, как N/2-листный тор, сшитый по красным линиям:
tor3 | Немного о генерации текстур

В общем, получается, что для генерации текстур нужна такая структура:

  TColor = cardinal;

  TDistFunc = function(dx, dy: extended): extended; // расстояние по координатам
  TColorFunc = function(e: extended): TColor;        // цвет по числу
  TTxrFunc = function (Dists: array of extended): extended; // из набора чисел делает одно

  TTextureGenerator = record
    destDC: hDC;    // куда рисовать
    oSize: integer; // логарифм размера клетки, например 7 соответствует 128х128
    oCnt: integer;  // логарифм количества слоев
    pCount: integer; // количество точек
    oddLevels: boolean; // согласовывать ли уровни одной чётности
    // ну и функции
    DistFunc: TDistFunc;
    ColorFunc: TColorFunc;
    TxrFunc: TTxrFunc;
  end;

Полный модуль генератора текстур (с учётом случая, когда не надо согласовывать слои одной чётности, он ужасен и я не стал его комментировать) вместе с демонстрационным проектом для Delphi (изначально настроен на текстуру дерева) вот: TexGen
Проект ничего не делает, кромы вывода текстуры, но видоизменить его, чтобы параметры выбирались пользователем и чтобы результат сохранялся в файл, нетрудно.
Примеры работы генератора:
example1 | Немного о генерации текстур
example2 | Немного о генерации текстур
example3 | Немного о генерации текстур
Подобную генерацию текстур и заполнение полигонов я применил для своей игры FT, причём я все сгенерированные текстуры приводил к двуцветному изображению, чтобы сделать изображение более чётким. Мнение о графике игры неоднозначно, некоторые нашли её отвратительной, но некоторые нашли в этом стиль.

#графика, #генераторы, #текстуры

2 января 2011 (Обновление: 5 янв 2011)

Комментарии [8]