Шейдери · Процедурна генерація · GLSL
📅 Березень 2026 ⏱ ≈ 12 хв читання 🎯 Середній — Просунутий · Останнє оновлення: 23 червня 2026 р.

Процедурні текстури в GLSL — шум, fBm, мармур, деревина, деформація домену

Процедурна текстура обчислюється повністю з математики — без файлів зображень, без пропускної здатності пам'яті, з нескінченною роздільністю та безшовним замощенням без затрат. Починаючи з кількох примітивів шуму GLSL, можна синтезувати мармурові прожилки, текстуру деревини, шкіру рептилії, хмарні масиви та інопланетний ландшафт — усе всередині одного фрагментного шейдера.

1. Примітиви шуму — значеннєвий проти градієнтного шуму

Усі процедурні текстури починаються з функції шуму, що відображає координати на гладкі псевдовипадкові значення. У роботі в реальному часі домінують дві родини:

// --- Значеннєвий шум (GLSL) ---
float hash21(vec2 p) {
  p = fract(p * vec2(127.1, 311.7));
  p += dot(p, p.yx + 19.19);
  return fract(p.x * p.y);
}

float valueNoise(vec2 p) {
  vec2  i = floor(p), f = fract(p);
  float a = hash21(i),
        b = hash21(i + vec2(1,0)),
        c = hash21(i + vec2(0,1)),
        d = hash21(i + vec2(1,1));
  vec2 u = f * f * (3.0 - 2.0 * f); // крива smoothstep
  return mix(mix(a,b,u.x), mix(c,d,u.x), u.y);
}
Квінтичні криві згасання: заміна 3t²−2t³ на 6t⁵ − 15t⁴ + 10t³ (покращення Кена Перліна) гарантує, що перша й друга похідні дорівнюють нулю на межах клітин, даючи помітно гладкіший результат.

2. Градієнтний шум Перліна в GLSL

Ключова операція: у кожному вузлі ґратки вибираємо псевдовипадковий градієнт і скалярно множимо його на вектор від цього вузла до точки вибірки. Гладка інтерполяція чотирьох скалярних добутків дає значення шуму.

// Градієнтний шум — квінтичне згасання, аналітичні похідні за бажанням
vec2 gradHash(vec2 p) {
  float n = dot(p, vec2(127.1, 311.7));
  n = fract(sin(n) * 43758.5453);
  return normalize(vec2(
    fract(n * 127.1) * 2.0 - 1.0,
    fract(n * 311.7) * 2.0 - 1.0
  ));
}

float perlinNoise(vec2 p) {
  vec2  i = floor(p), f = fract(p);
  vec2  u = f*f*f*(f*(f*6.0-15.0)+10.0); // квінтичне згасання

  float a = dot(gradHash(i + vec2(0,0)), f - vec2(0,0)),
        b = dot(gradHash(i + vec2(1,0)), f - vec2(1,0)),
        c = dot(gradHash(i + vec2(0,1)), f - vec2(0,1)),
        d = dot(gradHash(i + vec2(1,1)), f - vec2(1,1));

  return mix(mix(a,b,u.x), mix(c,d,u.x), u.y) * 0.5 + 0.5; // перевідображення [-1,1]→[0,1]
}

Шум Перліна має відоме обмеження: його спектр потужності має пік на фіксованій просторовій частоті. Поєднання кількох октав (наступний розділ) додає багатомасштабних деталей, притаманних природним явищам.

3. Дробовий броунівський рух (fBm)

fBm підсумовує кілька масштабованих копій тієї самої функції шуму, кожну з вищою частотою й нижчою амплітудою. Визначальний параметр — показник Герста H, що контролює шорсткість.

fBm(x) = Σi=0..N-1 amplitudei · noise(lacunarityi · x)

Стандартні значення: lacunarity = 2.0 (подвоєння частоти кожної октави)
amplitude = 0.5 (вдвічі менша амплітуда кожної октави, H = 0.5)

Шорсткіший ландшафт: amplitude = 0.6–0.7 (H ближче до 0, більше високочастотної енергії)
Гладкіші хмари: amplitude = 0.4–0.45 (H ближче до 1)
float fbm(vec2 p, int octaves) {
  float value = 0.0;
  float amp   = 0.5;
  float freq  = 1.0;
  for (int i = 0; i < octaves; i++) {
    value += amp * perlinNoise(p * freq);
    freq  *= 2.0;     // лакунарність
    amp   *= 0.5;     // персистентність (підсилення)
  }
  return value;
}

// Використання у карті висот ландшафту (6 октав ≈ 11 обчислень шуму)
float h = fbm(uv * 4.0, 6);

4. Шум Worley / клітинний шум

Шум Worley (Стівен Ворлі, 1996) ґрунтується на відстані: для кожного фрагмента знаходимо найближчу й другу за близькістю опорні точки, розставлені псевдовипадково у клітинах регулярної сітки. Відстані F1 та F2 дають природну клітинну структуру — кам'яні плитки, луску рептилії, потріскану грязь.

vec2 worley(vec2 p) {
  vec2  i = floor(p);
  vec2  f = fract(p);
  float F1 = 1e9, F2 = 1e9;

  for (int dy = -1; dy <= 1; dy++) {
    for (int dx = -1; dx <= 1; dx++) {
      vec2 nb     = i + vec2(dx, dy);
      vec2 jitter = hash21(nb) * 0.9; // випадково зміщуємо опорну точку в межах клітини
      vec2 r      = vec2(dx, dy) + jitter - f;
      float d = dot(r, r);             // квадрат евклідової відстані
      if (d < F1) { F2 = F1; F1 = d; }
      else if (d < F2) { F2 = d; }
    }
  }
  return sqrt(vec2(F1, F2));
}

// Корисні комбінації:
// F1         — заповнені клітини (камені)
// F2 - F1    — межі клітин (тріщини, шкіра рептилії)
// 1.0 - F1   — інвертоване (бульбашки, піна)
Продуктивність: перевірка лише сусідства 3×3 (9 клітин) гарантує знаходження найближчої точки для помірно зміщених опорних точок. На деяких GPU використання масиву перестановочних хешів замість sin/fract вдвічі зменшує кількість математичних операцій.

5. Мармур — турбулентність і синусоїдальні смуги

Справжній мармур складається з майже паралельних прожилок, викривлених геологічним тиском. У шейдері: створюємо синусоїдальні смуги вздовж однієї осі, потім збурюємо вхідну позицію fBm-турбулентністю перед обчисленням синуса.

turbulence(x) = Σ amplitudei · |noise(lacunarityi · x)|

marble(x) = 0.5 + 0.5 · sin( x.y · stripesPerUnit + turb · strength )
float turbulence(vec2 p, int oct) {
  float v = 0.0, amp = 0.5, freq = 1.0;
  for (int i = 0; i < oct; i++) {
    v    += amp * abs(perlinNoise(p * freq) * 2.0 - 1.0); // абсолютне значення
    freq *= 2.0;  amp *= 0.5;
  }
  return v;
}

float marblePattern(vec2 uv) {
  float turb = turbulence(uv, 6);
  return 0.5 + 0.5 * sin(uv.y * 8.0  +  turb * 5.0);
}

// Колір: лінійна інтерполяція від темного мармуру до яскравого кольору прожилки
vec3 marble = mix(vec3(0.1,0.12,0.15), vec3(0.9,0.87,0.82), marblePattern(uv));

Налаштуйте частоту смуг (8.0), силу турбулентності (5.0) та палітру кольорів, щоб отримати білий каррарський мармур, verde antico або чорно-золоті різновиди.

6. Текстура деревини — радіальні кільця зі збуренням fBm

Текстура деревини — це концентричні річні кільця з центром на осі росту. Відстань від центру задає смугастий візерунок; збурення fBm порушує ідеальну симетрію:

float woodPattern(vec2 uv) {
  // Радіальна відстань від осі росту (точка в початку координат)
  float radius = length(uv);

  // Збурюємо за допомогою fBm, щоб кільця не були ідеальними колами
  radius += fbm(uv * 3.0, 4) * 0.4;

  // Синусоїдальні кільця: кількість кілець пропорційна ringFreq
  float ring = fract(radius * 6.0);

  // smoothstep для створення чергування темних/світлих смуг
  return smoothstep(0.2, 0.3, ring) - smoothstep(0.7, 0.8, ring);
}

// Землиста палітра деревини
vec3 darkWood  = vec3(0.25, 0.14, 0.06);
vec3 lightWood = vec3(0.65, 0.45, 0.22);
vec3 wood = mix(darkWood, lightWood, woodPattern(uv));

Додавання дрібнозернистого шуму вищої частоти поверх візерунка кілець імітує мікротекстуру деревних волокон, помітну на рендерах великим планом.

7. Деформація домену — техніка Iñigo Quílez

Деформація домену (Quílez, 2003) — один із найпотужніших інструментів процедурних текстур: збурюємо вхідні координати шумом перед повторним запитом шуму. Дворівнева деформація створює надзвичайно складні, органічні візерунки з простої математики:

f(p) = fBm(p) ← звичайний fBm
g(p) = fBm(p + f(p)*k) ← вхід деформовано один раз
h(p) = fBm(p + g(p)*k) ← вхід деформовано двічі

h використовується як кінцевий візерунок; k керує силою деформації (≈ 1–4)
vec2 fbm2(vec2 p, int oct) {
  return vec2(fbm(p, oct), fbm(p + vec2(3.7, 1.3), oct)); // 2D-шум
}

float domainWarp(vec2 p) {
  vec2  q = fbm2(p, 5);                      // перша деформація
  vec2  r = fbm2(p + q * 2.0, 5);            // друга деформація
  return fbm(p + r * 2.0, 5);                // кінцеве обчислення
}

// Використовується для: хмар, туманностей, полум'я, лави, інопланетного ландшафту
Зауваження щодо продуктивності: подвійна деформація домену викликає fBm тричі. За 5 октав кожного разу це 15 обчислень шуму на фрагмент. На мобільних GPU середнього класу тримайте кількість октав нижчою (3–4) або використовуйте значеннєвий шум замість градієнтного.

8. Повний демонстраційний шейдер

Фрагментний шейдер нижче поєднує все: мармур, кільця деревини, шкіру рептилії через Worley та шар хмар із деформацією домену. Виберіть візерунок через uniform uMode.

// --- Повна демонстрація процедурних текстур (GLSL ES 3.00) ---
#version 300 es
precision highp float;

uniform float uTime;
uniform  vec2 uResolution;
uniform int   uMode;        // 0=мармур 1=деревина 2=рептилія 3=хмара
out vec4 fragColor;

// ---- Допоміжні функції ---------------------------------------------
float hash(vec2 p) {
  p  = fract(p * vec2(127.1, 311.7));
  p += dot(p, p.yx + 19.19);
  return fract(p.x * p.y);
}
vec2 gradHash(vec2 p) {
  float n = fract(sin(dot(p, vec2(127.1,311.7))) * 43758.5453);
  return normalize(vec2(fract(n*127.1)*2.0-1.0, fract(n*311.7)*2.0-1.0));
}
float gnoise(vec2 p) {
  vec2 i=floor(p), f=fract(p);
  vec2 u=f*f*f*(f*(f*6.0-15.0)+10.0);
  float a=dot(gradHash(i         ),f         ),
        b=dot(gradHash(i+vec2(1,0)),f-vec2(1,0)),
        c=dot(gradHash(i+vec2(0,1)),f-vec2(0,1)),
        d=dot(gradHash(i+vec2(1,1)),f-vec2(1,1));
  return mix(mix(a,b,u.x),mix(c,d,u.x),u.y)*0.5+0.5;
}
float fbm(vec2 p) {
  float v=0.0,a=0.5;
  for(int i=0;i<6;i++){v+=a*gnoise(p);p*=2.0;a*=0.5;}
  return v;
}
float turb(vec2 p) {
  float v=0.0,a=0.5;
  for(int i=0;i<6;i++){v+=a*abs(gnoise(p)*2.0-1.0);p*=2.0;a*=0.5;}
  return v;
}
float worleyF1(vec2 p) {
  vec2 i=floor(p); vec2 f=fract(p); float F1=1e9;
  for(int dy=-1;dy<=1;dy++) for(int dx=-1;dx<=1;dx++){
    vec2 nb=i+vec2(dx,dy), r=vec2(dx,dy)+hash(nb)*0.9-f;
    F1=min(F1,dot(r,r));
  }
  return sqrt(F1);
}
float worleyF2mF1(vec2 p) {
  vec2 i=floor(p); vec2 f=fract(p); float F1=1e9,F2=1e9;
  for(int dy=-1;dy<=1;dy++) for(int dx=-1;dx<=1;dx++){
    vec2 nb=i+vec2(dx,dy), r=vec2(dx,dy)+hash(nb)*0.9-f;
    float d=dot(r,r);
    if(delse if(dreturn sqrt(F2)-sqrt(F1);
}

// ---- Візерунки ------------------------------------------------------
vec3 pMarble(vec2 uv) {
  float t = 0.5+0.5*sin(uv.y*8.0+turb(uv)*5.0);
  return mix(vec3(0.1,0.12,0.15), vec3(0.9,0.87,0.82),t);
}
vec3 pWood(vec2 uv) {
  float r = length(uv*0.8) + fbm(uv*3.0)*0.5;
  float ring = smoothstep(0.2,0.3,fract(r*6.0))-smoothstep(0.7,0.8,fract(r*6.0));
  return mix(vec3(0.25,0.14,0.06), vec3(0.65,0.45,0.22),ring);
}
vec3 pReptile(vec2 uv) {
  float cells = worleyF2mF1(uv*5.0);
  vec3  col   = mix(vec3(0.05,0.17,0.08), vec3(0.2,0.6,0.15),
                     smoothstep(0.0,0.25,cells));
  return col * (1.0 - worleyF1(uv*5.0)*0.6);
}
vec3 pCloud(vec2 uv) {
  vec2  q = vec2(fbm(uv+uTime*0.05), fbm(uv+vec2(3.7,1.3)));
  vec2  r = vec2(fbm(uv+q*2.0),       fbm(uv+q*2.0+vec2(1.7,9.2)));
  float f = fbm(uv + r*2.0);
  vec3  sky    = vec3(0.05,0.08,0.18);
  vec3  cloud  = vec3(0.8, 0.85, 1.0);
  return mix(sky, cloud, smoothstep(0.3,0.7,f));
}

// ---- Головна функція ------------------------------------------------
void main() {
  vec2 uv = (gl_FragCoord.xy - 0.5*uResolution) / uResolution.y * 3.0;
  vec3 col;
  if      (uMode==0) col = pMarble(uv);
  else if (uMode==1) col = pWood(uv);
  else if (uMode==2) col = pReptile(uv);
  else                col = pCloud(uv);
  fragColor = vec4(col, 1.0);
}

🌊 Ray Marching та SDF

Поєднуйте процедурні текстури з геометрією полів відстані — мармурові сфери, дерев'яні коробки та хмарні об'єми без жодного меша.

Читати про ray marching →