О чем этот пример

Визуализация процедурного шума — мощный инструмент для создания уникальных текстур, ландшафтов и атмосферы в играх. Этот пример демонстрирует, как с помощью всего нескольких строк кода в Phaser 3 сгенерировать не просто поле шума, а полноценную карту нормалей, которая сразу создаёт иллюзию объёма и рельефа. Мы разберём, как это работает и зачем может пригодиться в ваших проектах.

Версия Phaser: код и демо в этой статье рассчитаны на Phaser 3.90.0.

Живой запуск

Ниже встроен рабочий билд примера. Оригинальный источник: GitHub.

Исходный код


class Example extends Phaser.Scene
{
    create()
    {
        const { width, height } = this.scale;

        this.noise1 = this.add.noisesimplex2d({
            noiseCells: [ 3, 4 ],
            noiseIterations: 4,
            noiseValueFactor: 0.4 // Eliminates some regions where octaves add > 1.
        }, width * 1 / 4, height / 2, width / 2, height);

        this.noise2 = this.add.noisesimplex2d({
            noiseCells: [ 3, 4 ],
            noiseIterations: 4,
            noiseValueFactor: 0.4,
            noiseNormalMap: true,
            noiseNormalScale: 8
        }, width * 3 / 4, height / 2, width / 2, height);
    }
}

const config = {
    type: Phaser.WEBGL,
    parent: 'phaser-example',
    width: 1280,
    height: 720,
    scene: Example
};

const game = new Phaser.Game(config);

Суть примера: две визуализации одного шума

В примере создаются два объекта NoiseSimplex2D с идентичными параметрами шума, но разными режимами отображения. Они располагаются рядом на сцене для наглядного сравнения.

Первый объект (noise1) отображает сырые значения шума в градациях серого — это классическая карта высот. Второй объект (noise2) использует эти же значения для генерации карты нормалей, которая имитирует освещение рельефа, созданного этим шумом.

Ключевое отличие — всего один параметр в конфигурации второго объекта.

Разбираем конфигурацию шума: `noiseSimplex2d`

Метод this.add.noisesimplex2d() создаёт и добавляет на сцену 2D-текстуру, заполненную значениями Simplex-шума. Его первый аргумент — объект конфигурации.

{
    noiseCells: [ 3, 4 ],
    noiseIterations: 4,
    noiseValueFactor: 0.4,
    noiseNormalMap: true,
    noiseNormalScale: 8
}

- noiseCells: [3, 4] — Определяет, сколько "ячеек" или паттернов шума будет уложено по ширине (3) и высоте (4) текстуры. Меньшие значения дают более крупные, плавные структуры. - noiseIterations: 4 — Количество октав шума. Каждая следующая октава имеет бóльшую частоту и меньшую амплитуду, что добавляет детализацию. 4 итерации — хороший баланс между плавностью и сложностью. - noiseValueFactor: 0.4 — Множитель для итогового значения, который "прижимает" его к диапазону [0, 1]. Без этого октавы могут сложиться в значение больше 1, что приведёт к "пересветам" на текстуре. - noiseNormalMap: true — Флаг, который включает расчёт и применение карты нормалей к визуализации. Именно он превращает серую карту высот в иллюзию 3D-рельефа. - noiseNormalScale: 8 — Коэффициент, влияющий на "крутизну" воспринимаемого рельефа. Чем выше значение, тем более контрастными и резкими выглядят "склоны".

Геометрия размещения на сцене

Второй и последующие аргументы метода задают позицию и размер отображаемого объекта на Canvas.

this.add.noisesimplex2d(config, x, y, width, height)

В примере координаты и размеры рассчитываются динамически относительно размеров окна игры (this.scale.width и this.scale.height).

// Первый шум (обычный) занимает левую половину экрана по ширине
this.noise1 = this.add.noisesimplex2d(config1, width * 1 / 4, height / 2, width / 2, height);

// Второй шум (с нормалями) занимает правую половину
this.noise2 = this.add.noisesimplex2d(config2, width * 3 / 4, height / 2, width / 2, height);

- `xиy: Это координаты центра объекта. Размещая первый объект наwidth * 1 / 4, а второй наwidth * 3 / 4`, мы центрируем каждый в своей половине экрана. - width и height: Фактический размер текстуры на экране. Оба объекта растягиваются на половину ширины и полную высоту экрана, создавая два больших, сопоставимых прямоугольника.

Практическое применение в играх

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

1. **Динамический рельеф местности:** Создайте базовую плоскость (плитку) для травы, камня или льда и наложите на неё такую текстуру нормалей. Это мгновенно "оживит" плоскую поверхность, создав иллюзию неровностей, камней или волн. 2. **Процедурные небеса и облака:** Используйте шум с малым значением noiseNormalScale для создания лёгкой, турбулентной объёмности в текстурах облаков или туманности. 3. **Генерация карт для шейдеров:** Полученную текстуру можно передать в пользовательский шейдер как uniform для создания более сложных эффектов, например, динамического освещения лавой или воды на procedurally generated поверхности.

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

Что попробовать дальше

Пример наглядно показывает, как Phaser 3 из коробки предоставляет мощные инструменты для работы с процедурной генерацией. Включив noiseNormalMap: true, вы превращаете абстрактную карту высот в готовый к использованию визуальный актив с ощущением объёма. **Идеи для экспериментов:** 1. Попробуйте анимировать параметры (например, плавно меняйте noiseCells или noiseNormalScale) в функции update(). Это может создать эффект "плавления" рельефа или медленно движущихся облаков. 2. Создайте несколько слоёв шума с разными параметрами и наложите их друг на друга с помощью blend-режимов (setBlendMode). 3. Используйте сгенерированную текстуру не как самостоятельный объект, а как маску или карту нормалей для другого графического объекта, например, спрайта планеты.