Программирование игр, создание игрового движка, OpenGL, DirectX, физика, форум
GameDev.ru / Программирование / Статьи / Реализация процедурных текстур методом Шума Перлина.

Реализация процедурных текстур методом Шума Перлина.

Автор: Волошин Дмитрий aka WDW

Часть 1
Часть 2
Полезные линки

Часть 1

При написании данной статьи я не делал своей целью перевод англоязычных статей на эту тему, которых я перечитал довольно много в интернете или создание супер-мега-классного плагина для 3DSM с классной фотореалистичной графикой облаков, которая тем не менее рендерится минут 5, я хотел сделать динамичные красивые облака (возможно даже фейковые), которые  можно использовать в играх , не сильно влияя на ФПС.

Простейшим методом реализации разнообразных «случайных» текстур есть метод Кена Перлина (http://mrl.nyu.edu/perlin/) и названный в его честь. В этом методе генерируется несколько текстур «октав» и слагая их вместе мы получаем довольно интересную текстуру, вид которой зависят от нескольких параметров.

В этой статье я расскажу о 2-мерном случае, но это можно легко расширить до n-мерного случая.  Шум Перлина использует псевдо-рандомный генератор случайных чисел, который возвращает число от –1 до 1 по заданных параметрах
Причем это число постоянное для конкретных параметров.

_inline float CMyNoise::Noise2D(int x, int y)
{
  int n = x + y * 57;
  n = (n<<13) ^ n;
  return ( 1.0f - ( (n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) /
      1073741824.0f);
}

Обратите внимание на количество параметров, их 2 поскольку шум двухмерный, но их может быть и 1, и 3 и больше. Постарайтесь это понять, так что функция возвращает для 2-х параметров число в плоскости, для трех в пространстве и. т.д., может  это и неверно  с математической точки зрения, но именно это помогло мне понять “How does it work”.

Следующей важнейшей вещью есть формула интерполяции, которая используется для сглаживания значений шума. Чаще всего эта функция принимает 3 параметра – a и b – два параметра, между которыми мы производим интерполяцию и х – число от 0 до 1, от которого и зависит результат. Если х=0, то возвращается a, если х=1, возвращается b, в ином случае возвращается некоторое значение между а и b.

Есть несколько формул, которые мы можем использовать, но те которые дают хорошие результаты используют больше системных ресурсов. Наиболее  популярной формулой есть
result= a*(1-x) + b*x 

  //алгоритм «плазма» в ввиду своей простоты очень подходит для рилтайма
  ////////////////////////////***************************/////////////////////
  float ft = x * 3.1415927;
  float f = (1 - cosf(ft)) * 0.5;
  result= a*(1-f) + b*f;  
  //косинусная интерполяция, возвращает более «сглаженные значения»
  // но все знают быстроту функции cosf();
  ////////////////////////////////*****************************///////////////
  float fac1 = 3*powf(1-a, 2) - 2*powf(1-a,3);
  float fac2 = 3*powf(a, 2) - 2*powf(a, 3);
  result=x*fac1 + y*fac2;// James Long- использовал этот метод в своей статье
  ////////////////////********************************/////////////////// 
(Статья James Long: http://www.animeimaging.com/asp/PerlinNoise.aspx)

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

И также функция сглаживания, которая возвращает цвет пикселя в зависимости от исходного цвета и цвета соседних пикселей :

////////////////////////////////////////////////////////////////////////////
_inline float CMyNoise::SmoothedNoise2D(float x, float y)
{
    float corners = ( Noise2D(x-1, y-1)+Noise2D(x+1, y-1)+
         Noise2D(x-1, y+1)+Noise2D(x+1, y+1) ) / 16;
    float sides   = ( Noise2D(x-1, y)  +Noise2D(x+1, y)  +
         Noise2D(x, y-1)  +Noise2D(x, y+1) ) /  8;
    float center  =  Noise2D(x, y) / 4;
    return corners + sides + center;
  }
////////////////////////////////////////////////////////////////////////////

  //Функция, которая возвращает значение сглаженного и 
  //интерполированного шума 
float CMyNoise::CompileNoise(float x, float y) {
      float int_X    = int(x);//целая часть х
      float fractional_X = x - int_X;//дробь от х
//аналогично у
      float int_Y    = int(y);
      float fractional_Y = y - int_Y;
  //получаем 4 сглаженных значения
     float v1 = SmoothedNoise2D(int_X,     int_Y);
     float v2 = SmoothedNoise2D(int_X + 1, int_Y);
     float v3 = SmoothedNoise2D(int_X,     int_Y + 1);
     float v4 = SmoothedNoise2D(int_X + 1, int_Y + 1);
  //интерполируем значения 1 и 2 пары и производим интерполяцию между ними
      float i1 = Cosine_Interpolate(v1 , v2 , fractional_X);
      float i2 = Cosine_Interpolate(v3 , v4 , fractional_X);
  //я использовал косинусною интерполяцию ИМХО лучше 
  //по параметрам быстрота-//качество
      return Cosine_Interpolate(i1 , i2 , fractional_Y);
}

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

int CMyNoise::PerlinNoise_2D(float x,float y,float factor)
{
   float total = 0;
   // это число может иметь и другие значения хоть cosf(sqrtf(2))*3.14f 
   // главное чтобы было красиво и результат вас устраивал
   float persistence=0.5f;

   // экспериментируйте с этими значениями, попробуйте ставить 
   // например sqrtf(3.14f)*0.25f или что-то потяжелее для понимания J)
   float frequency = 0.25f;
   float amplitude=1;//амплитуда, в прямой зависимости от значения настойчивости

   // вводим фактор случайности, чтобы облака не были всегда одинаковыми
   // (Мы ведь помним что ф-ция шума когерентна?) 
    
   x+= (factor);
   y+= (factor);

   // NUM_OCTAVES - переменная, которая обозначает число октав,
   // чем больше октав, тем лучше получается шум
   for(int i=0;i<NUM_OCTAVES;i++)
   {
       total += CompileNoise(x*frequency, y*frequency) * amplitude;
       amplitude *= persistence;
       frequency*=2;
    }
    //здесь можно перевести значения цвета   по какой-то формуле
    //например:
    //total=sqrt(total);
    // total=total*total;
    // total=sqrt(1.0f/float(total)); 
    //total=255-total;-и.т.д все зависит от желаемого результата
    total=fabsf(total);
    int res=int(total*255.0f);//приводим цвет к значению 0-255…
    return res;
}

Пример интересных текстур:

Изображение

Изображение

Следующая функция принимает массив, который она заполняет шумом исходя из размера(псевдокод)

void CMyNoise::GenNoise(unsigned char pNoise[],int size)
{
  srand(GetTickCount());

  // случайное число, которое призвано внести
  // случайность в нашу текстуру
  float fac =Random(PI*2*10,PI*3*10);

  for(int i=0 ;i<size;i++)
  {
    for(int j=0 ;j<size;j++)
    {
       //проходим по всем элементам массива и заполняем их значениями   
       pNoise[i*size+j]=PerlinNoise_2D(float(i),float(j),fac);
    }
  }
}

Страницы: 1 2 Следующая »

18 января 2004

#Perlin, #математика


Обновление: 12 июня 2009

2001—2018 © GameDev.ru — Разработка игр