Процедурные текстуры в шейдере

Процедурные текстуры создаются с помощью математических функций и алгоритмов, а не загружаются как готовые изображения. Такой подход позволяет динамически генерировать текстуры различной сложности, изменять их в реальном времени и экономить память, особенно для больших сцен. В Three.js процедурные текстуры реализуются через шейдеры на GLSL, с использованием ShaderMaterial или модификации встроенных материалов через onBeforeCompile.

ShaderMaterial и GLSL

ShaderMaterial позволяет полностью контролировать вершины и фрагменты рендеринга. Основная структура материала выглядит так:

const material = new THREE.ShaderMaterial({
    uniforms: {
        u_time: { value: 0 },
        u_resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) }
    },
    vertexShader: /* glsl */`
        varying vec2 vUv;
        void main() {
            vUv = uv;
            gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        }
    `,
    fragmentShader: /* glsl */`
        uniform float u_time;
        uniform vec2 u_resolution;
        varying vec2 vUv;
        void main() {
            vec2 st = vUv;
            vec3 color = vec3(st.x, st.y, abs(sin(u_time)));
            gl_FragColor = vec4(color, 1.0);
        }
    `
});
  • uniforms позволяют передавать в шейдеры параметры с JavaScript.
  • vUv используется для передачи координат текстуры из вершинного шейдера во фрагментный.
  • Функция abs(sin(u_time)) демонстрирует динамическую анимацию цвета.

Фракталы и шумы

Для реалистичных процедурных текстур применяются фрактальные функции и шумы, такие как Perlin Noise или Simplex Noise. В GLSL можно подключить их через функции:

float random(vec2 st) {
    return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);
}

float noise(vec2 st) {
    vec2 i = floor(st);
    vec2 f = fract(st);

    float a = random(i);
    float b = random(i + vec2(1.0, 0.0));
    float c = random(i + vec2(0.0, 1.0));
    float d = random(i + vec2(1.0, 1.0));

    vec2 u = f*f*(3.0-2.0*f);
    return mix(a, b, u.x) +
           (c - a)* u.y * (1.0 - u.x) +
           (d - b) * u.x * u.y;
}

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

Комбинирование шумов

Для сложных текстур шумы комбинируются на разных масштабах, создавая фрактальные шумы:

float fbm(vec2 st) {
    float value = 0.0;
    float amplitude = 0.5;
    for (int i = 0; i < 5; i++) {
        value += amplitude * noise(st);
        st *= 2.0;
        amplitude *= 0.5;
    }
    return value;
}

Такой подход позволяет генерировать текстуры с разными деталями: крупные формы задаются на первых итерациях, мелкие — на последующих.

Применение к материалам Three.js

Процедурная текстура может быть напрямую встроена в ShaderMaterial, либо использоваться как data texture. Например, для генерации текстуры облаков:

const size = 256;
const data = new Uint8Array(size * size * 3);
for (let i = 0; i < size * size; i++) {
    const x = i % size;
    const y = Math.floor(i / size);
    const value = Math.floor(fbm(new THREE.Vector2(x, y) / 50.0) * 255);
    data[i * 3 + 0] = value;
    data[i * 3 + 1] = value;
    data[i * 3 + 2] = value;
}
const texture = new THREE.DataTexture(data, size, size, THREE.RGBFormat);
texture.needsUpdate = true;

Такой подход позволяет использовать процедурные данные как обычную текстуру для MeshStandardMaterial или других материалов Three.js.

Анимация процедурных текстур

Процедурные текстуры легко анимировать через uniforms. Например, смещение координат шума по времени создаёт эффект движения облаков или воды:

vec2 st = vUv;
st += vec2(u_time * 0.05, u_time * 0.1);
float n = fbm(st * 3.0);
gl_FragColor = vec4(vec3(n), 1.0);

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

Оптимизация

  • Вычисления в шейдере могут быть дорогими, особенно для больших текстур.
  • Для статических текстур лучше генерировать их заранее и сохранять в DataTexture.
  • Уменьшение количества итераций в fbm и использование lowp для переменных помогает снизить нагрузку на GPU.

Применения в реальных проектах

Процедурные текстуры активно применяются для:

  • Ландшафтов с бесшовными материалами (камни, песок, трава).
  • Водной поверхности и анимации волн.
  • Космических эффектов и облаков в сценах с планетами.
  • Стилевых эффектов, таких как пиксельный шум или абстрактные паттерны.

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